use std::sync::{Arc, Mutex};

use lurq::{
  app::{Tree, component::Component, ctx::Ctx},
  core::Signal,
  layout::{
    Alignment, Constraints, Size,
    quad::QuadContent,
    text_style::{FontWeight, TextStyle},
  },
  node::{Element, color::Color},
};

use super::PassLayoutExt;

fn rt() -> Tree {
  Tree::new()
}

#[derive(lurq::DevtoolsInspectable)]
struct Shared<T>(Arc<T>);

impl<T> Clone for Shared<T> {
  fn clone(&self) -> Self {
    Self(self.0.clone())
  }
}

impl<T> PartialEq for Shared<T> {
  fn eq(&self, other: &Self) -> bool {
    Arc::ptr_eq(&self.0, &other.0)
  }
}

impl<T> std::fmt::Debug for Shared<T> {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    f.debug_tuple("Shared").field(&(Arc::as_ptr(&self.0) as usize)).finish()
  }
}

struct TextCounterHost;

impl Component for TextCounterHost {
  type Props = Shared<Mutex<Option<Signal<i32>>>>;

  fn create(_ctx: &mut Ctx) -> Self {
    Self
  }

  fn render(&self, ctx: &mut Ctx) -> impl Into<Element> {
    lurq::components::Row::new()
      .align_items(Alignment::Center)
      .child(ctx.mount::<TextCounter>(ctx.props::<Self::Props>().clone()))
  }
}

struct TextCounter {
  count: Signal<i32>,
}

impl Component for TextCounter {
  type Props = Shared<Mutex<Option<Signal<i32>>>>;

  fn create(ctx: &mut Ctx) -> Self {
    let count = ctx.signal(9);
    *ctx.props::<Self::Props>().0.lock().unwrap() = Some(count.clone());
    Self { count }
  }

  fn render(&self, _ctx: &mut Ctx) -> impl Into<Element> {
    lurq::components::Text::styled(
      &self.count.get().to_string(),
      TextStyle {
        font_size: 24.0,
        weight: FontWeight::Bold,
        color: Color::from_hex("#1e293b"),
        ..TextStyle::default()
      },
    )
  }
}

#[derive(Clone, Debug, lurq::DevtoolsInspectable)]
struct ErrorLabelProps {
  error: Signal<bool>,
}

impl PartialEq for ErrorLabelProps {
  fn eq(&self, other: &Self) -> bool {
    self.error.id() == other.error.id()
  }
}

struct ErrorLabelHost;

impl Component for ErrorLabelHost {
  type Props = ErrorLabelProps;

  fn create(_ctx: &mut Ctx) -> Self {
    Self
  }

  fn render(&self, ctx: &mut Ctx) -> impl Into<Element> {
    let label = if ctx.props::<Self::Props>().error.get() {
      "ERROR!!!!!!!!!!!!!"
    } else {
      "HEX PRIVATE KEY"
    };

    lurq::components::Column::new().child(lurq::components::Text::styled(
      label,
      TextStyle {
        font_size: 10.0,
        weight: FontWeight::Bold,
        color: Color::from_hex("#f05d5e"),
        ..TextStyle::default()
      },
    ))
  }
}

struct KeyedErrorLabelHost;

impl Component for KeyedErrorLabelHost {
  type Props = ErrorLabelProps;

  fn create(_ctx: &mut Ctx) -> Self {
    Self
  }

  fn render(&self, ctx: &mut Ctx) -> impl Into<Element> {
    let props = ctx.props::<Self::Props>().clone();
    let key = if props.error.get() {
      "private_key-invalid"
    } else {
      "private_key-valid"
    };

    ctx.mount_keyed::<ErrorLabelHost>(key, props)
  }
}

struct NestedKeyedErrorLabelRoot;

impl Component for NestedKeyedErrorLabelRoot {
  type Props = ErrorLabelProps;

  fn create(_ctx: &mut Ctx) -> Self {
    Self
  }

  fn render(&self, ctx: &mut Ctx) -> impl Into<Element> {
    lurq::components::Column::new().child(ctx.mount::<KeyedErrorLabelHost>(ctx.props::<Self::Props>().clone()))
  }
}

#[test]
fn text_height_equals_line_height() {
  let mut rt = rt();
  let style = TextStyle {
    font_size: 24.0,
    ..TextStyle::default()
  };
  let node = lurq::components::Text::styled("0", style.clone());
  rt.set_root(node);
  let r = rt.pass_layout(Constraints::loose(Size::new(400.0, 400.0))).unwrap();
  let expected_height = style.font_size * style.line_height;
  assert!(
    (r.size.height - expected_height).abs() < 1.0,
    "text height should be ~{} (font_size * line_height), got {}",
    expected_height,
    r.size.height
  );
}

#[test]
fn signal_dirty_component_requests_redraw() {
  let error = Signal::new(false);
  let mut rt = rt();
  rt.mount_root::<ErrorLabelHost>(&mut lurq::app::App::new(), ErrorLabelProps { error: error.clone() });
  let _ = rt.pass_layout(Constraints::loose(Size::new(400.0, 100.0))).unwrap();
  rt.clear_needs_redraw();
  assert!(!rt.needs_redraw());

  error.set(true);

  assert!(rt.needs_redraw());
}

#[test]
fn mounted_child_text_content_updates_after_signal_rerender() {
  let error = Signal::new(false);
  let mut rt = rt();
  rt.mount_root::<ErrorLabelHost>(&mut lurq::app::App::new(), ErrorLabelProps { error: error.clone() });

  let initial = rt.pass_layout(Constraints::loose(Size::new(400.0, 100.0))).unwrap();
  let initial_quads = rt.resolve_quads(&initial);
  assert!(text_quads_contain(&initial_quads, "HEX PRIVATE KEY"));

  error.set(true);

  let updated = rt.pass_layout(Constraints::loose(Size::new(400.0, 100.0))).unwrap();
  let updated_quads = rt.resolve_quads(&updated);
  assert!(text_quads_contain(&updated_quads, "ERROR!!!!!!!!!!!!!"));
  assert!(!text_quads_contain(&updated_quads, "HEX PRIVATE KEY"));
}

#[test]
fn keyed_mounted_child_updates_after_signal_rerender() {
  let error = Signal::new(false);
  let mut rt = rt();
  rt.mount_root::<KeyedErrorLabelHost>(&mut lurq::app::App::new(), ErrorLabelProps { error: error.clone() });

  let initial = rt.pass_layout(Constraints::loose(Size::new(400.0, 100.0))).unwrap();
  let initial_quads = rt.resolve_quads(&initial);
  assert!(text_quads_contain(&initial_quads, "HEX PRIVATE KEY"));
  #[cfg(feature = "devtools")]
  assert_eq!(snapshot_child_key(&rt).as_deref(), Some("private_key-valid"));

  error.set(true);

  let updated = rt.pass_layout(Constraints::loose(Size::new(400.0, 100.0))).unwrap();
  let updated_quads = rt.resolve_quads(&updated);
  assert!(text_quads_contain(&updated_quads, "ERROR!!!!!!!!!!!!!"));
  assert!(!text_quads_contain(&updated_quads, "HEX PRIVATE KEY"));
  #[cfg(feature = "devtools")]
  assert_eq!(snapshot_child_key(&rt).as_deref(), Some("private_key-invalid"));
}

#[test]
fn nested_dirty_component_updates_keyed_child_after_signal_rerender() {
  let error = Signal::new(false);
  let mut rt = rt();
  rt.mount_root::<NestedKeyedErrorLabelRoot>(&mut lurq::app::App::new(), ErrorLabelProps { error: error.clone() });

  let initial = rt.pass_layout(Constraints::loose(Size::new(400.0, 100.0))).unwrap();
  let initial_quads = rt.resolve_quads(&initial);
  assert!(text_quads_contain(&initial_quads, "HEX PRIVATE KEY"));
  #[cfg(feature = "devtools")]
  assert_eq!(snapshot_child_key(&rt).as_deref(), Some("private_key-valid"));

  error.set(true);

  let updated = rt.pass_layout(Constraints::loose(Size::new(400.0, 100.0))).unwrap();
  let updated_quads = rt.resolve_quads(&updated);
  assert!(text_quads_contain(&updated_quads, "ERROR!!!!!!!!!!!!!"));
  assert!(!text_quads_contain(&updated_quads, "HEX PRIVATE KEY"));
  #[cfg(feature = "devtools")]
  assert_eq!(snapshot_child_key(&rt).as_deref(), Some("private_key-invalid"));
}

fn text_quads_contain(quads: &[lurq::layout::quad::Quad], expected: &str) -> bool {
  quads.iter().any(|quad| match &quad.content {
    QuadContent::Text { text, .. } => text == expected,
    _ => false,
  })
}

#[cfg(feature = "devtools")]
fn snapshot_child_key(rt: &Tree) -> Option<String> {
  let snapshot = lurq::app::devtools::DevToolsSnapshot::from_tree(rt);
  find_snapshot_key(snapshot.root.as_ref()?).map(str::to_owned)
}

#[cfg(feature = "devtools")]
fn find_snapshot_key(node: &lurq::app::devtools::DevToolsNode) -> Option<&str> {
  if matches!(node.key.as_deref(), Some("private_key-valid" | "private_key-invalid")) {
    return node.key.as_deref();
  }

  node.children.iter().find_map(find_snapshot_key)
}

#[test]
fn nowrap_text_keeps_intrinsic_single_line_width() {
  let mut rt = rt();
  let style = TextStyle {
    font_size: 14.0,
    ..TextStyle::default()
  };
  rt.set_root(lurq::components::Text::styled("x: 1279  y: 450  |  entered: true", style).nowrap());
  let r = rt.pass_layout(Constraints::loose(Size::new(120.0, 80.0))).unwrap();

  assert!(
    r.size.width > 120.0,
    "nowrap text should keep its intrinsic width instead of wrapping to the parent constraint"
  );

  let quads = rt.resolve_quads(&r);
  let text_quad = quads
    .iter()
    .find(|quad| matches!(quad.content, QuadContent::Text { .. }))
    .expect("nowrap text should emit a text quad");
  assert_eq!(text_quad.width, r.size.width);
}

#[test]
fn changed_text_content_invalidates_ancestor_layout_cache() {
  let count = Arc::new(Mutex::new(None));
  let mut rt = rt();
  rt.mount_root::<TextCounterHost>(&mut lurq::app::App::new(), Shared(count.clone()));

  let one_digit = rt.pass_layout(Constraints::loose(Size::new(400.0, 100.0))).unwrap();
  let one_digit_width = one_digit.children[0].result.size.width;
  let one_digit_height = one_digit.children[0].result.size.height;

  count.lock().unwrap().as_ref().unwrap().set(10);

  let two_digit = rt.pass_layout(Constraints::loose(Size::new(400.0, 100.0))).unwrap();
  let two_digit_width = two_digit.children[0].result.size.width;
  let two_digit_height = two_digit.children[0].result.size.height;

  assert!(
    two_digit_width > one_digit_width,
    "updated text should be remeasured wider than the previous single digit: {} <= {}",
    two_digit_width,
    one_digit_width
  );
  assert!(
    (two_digit_height - one_digit_height).abs() < 1.0,
    "updated text should stay single-line height: {} vs {}",
    two_digit_height,
    one_digit_height
  );
}

#[test]
fn text_vertically_centered_in_row_with_rects() {
  let mut rt = rt();
  let node = lurq::components::Row::new()
    .spacing(12.0)
    .align_items(Alignment::Center)
    .child(lurq::components::Rect::new(36.0, 36.0))
    .child(lurq::components::Text::styled(
      "0",
      TextStyle {
        font_size: 24.0,
        weight: FontWeight::Bold,
        color: Color::from_hex("#1e293b"),
        ..TextStyle::default()
      },
    ))
    .child(lurq::components::Rect::new(36.0, 36.0));
  rt.set_root(node);
  let r = rt.pass_layout(Constraints::loose(Size::new(400.0, 100.0))).unwrap();

  let row_height = r.size.height;
  assert!(
    (row_height - 36.0).abs() < 1.0,
    "row height should be 36 (max child), got {}",
    row_height
  );

  let text_child = &r.children[1];
  let text_height = text_child.result.size.height;
  let text_center_y = text_child.offset.y + text_height / 2.0;
  let row_center_y = row_height / 2.0;

  assert!(
    (text_center_y - row_center_y).abs() < 1.0,
    "text center ({}) should match row center ({}), text_y={}, text_h={}",
    text_center_y,
    row_center_y,
    text_child.offset.y,
    text_height
  );
}

#[test]
fn text_vertically_centered_in_fixed_height_row() {
  let mut rt = rt();
  let node = lurq::components::Row::new()
    .align_items(Alignment::Center)
    .child(lurq::components::Text::styled(
      "Layout",
      TextStyle {
        font_size: 11.0,
        weight: FontWeight::Bold,
        color: Color::from_hex("#f8fafc"),
        ..TextStyle::default()
      },
    ))
    .width(200.0)
    .height(38.0);
  rt.set_root(node);
  let r = rt.pass_layout(Constraints::loose(Size::new(400.0, 100.0))).unwrap();

  let row = &r;
  let text_child = &row.children[0];
  let text_height = text_child.result.size.height;
  let text_center_y = text_child.offset.y + text_height / 2.0;

  assert!(
    text_height < row.size.height,
    "text child should keep intrinsic height inside fixed-height row, text_h={}, row_h={}",
    text_height,
    row.size.height
  );
  assert!(
    (text_center_y - row.size.height / 2.0).abs() < 1.0,
    "text center ({}) should match row center ({})",
    text_center_y,
    row.size.height / 2.0
  );
}
