///|
/// HTML+CSS Bridge API
/// Provides differential updates from HTML/CSS changes to LayoutTree

// =============================================================================
// Tree Update Operations
// =============================================================================

///|
/// Represents a change to the layout tree
pub(all) enum TreeChange {
  AddNode(String, Int, LayoutNode) // parent_id, index, node
  RemoveNode(String) // node_id
  MoveNode(String, String, Int) // node_id, new_parent_id, index
  UpdateStyle(String) // node_id
  ReplaceChildren(String, Array[LayoutNode]) // node_id, children
}

// =============================================================================
// Style Update from CSS
// =============================================================================

///|
/// Apply computed CSS values to a LayoutNode's style
/// Returns true if any changes were made
pub fn LayoutNode::apply_css_values(
  self : LayoutNode,
  values : @cascade.CascadedValues,
) -> Bool {
  let mut changed = false

  // Width
  match values.get_value("width") {
    Some(v) =>
      match parse_dimension(v) {
        Some(dim) =>
          if self.style.width != dim {
            self.style.width = dim
            changed = true
          }
        None => ()
      }
    None => ()
  }

  // Height
  match values.get_value("height") {
    Some(v) =>
      match parse_dimension(v) {
        Some(dim) =>
          if self.style.height != dim {
            self.style.height = dim
            changed = true
          }
        None => ()
      }
    None => ()
  }

  // Display
  match values.get_value("display") {
    Some(v) =>
      match parse_display(v) {
        Some(d) =>
          if self.style.display != d {
            self.style.display = d
            changed = true
          }
        None => ()
      }
    None => ()
  }

  // Flex direction
  match values.get_value("flex-direction") {
    Some(v) =>
      match parse_flex_direction(v) {
        Some(d) =>
          if self.style.flex_direction != d {
            self.style.flex_direction = d
            changed = true
          }
        None => ()
      }
    None => ()
  }

  // Flex wrap
  match values.get_value("flex-wrap") {
    Some(v) =>
      match parse_flex_wrap(v) {
        Some(w) =>
          if self.style.flex_wrap != w {
            self.style.flex_wrap = w
            changed = true
          }
        None => ()
      }
    None => ()
  }

  // Flex grow
  match values.get_value("flex-grow") {
    Some(v) =>
      match try_parse_double(v) {
        Some(n) =>
          if (self.style.flex_grow - n).abs() > 0.0001 {
            self.style.flex_grow = n
            changed = true
          }
        None => ()
      }
    None => ()
  }

  // Flex shrink
  match values.get_value("flex-shrink") {
    Some(v) =>
      match try_parse_double(v) {
        Some(n) =>
          if (self.style.flex_shrink - n).abs() > 0.0001 {
            self.style.flex_shrink = n
            changed = true
          }
        None => ()
      }
    None => ()
  }

  // Justify content
  match values.get_value("justify-content") {
    Some(v) =>
      match parse_alignment(v) {
        Some(a) =>
          if self.style.justify_content != a {
            self.style.justify_content = a
            changed = true
          }
        None => ()
      }
    None => ()
  }

  // Align items
  match values.get_value("align-items") {
    Some(v) =>
      match parse_alignment(v) {
        Some(a) =>
          if self.style.align_items != a {
            self.style.align_items = a
            changed = true
          }
        None => ()
      }
    None => ()
  }

  // Margin (simplified - full implementation would handle all sides)
  match values.get_value("margin") {
    Some(v) =>
      match parse_dimension(v) {
        Some(@types.Length(n)) => {
          let new_margin = @types.Rect::all(@types.Dimension::Length(n))
          self.style.margin = new_margin
          changed = true
        }
        _ => ()
      }
    None => ()
  }

  // Padding
  match values.get_value("padding") {
    Some(v) =>
      match parse_dimension(v) {
        Some(@types.Length(n)) => {
          let new_padding = @types.Rect::all(@types.Dimension::Length(n))
          self.style.padding = new_padding
          changed = true
        }
        _ => ()
      }
    None => ()
  }

  // Gap
  match values.get_value("gap") {
    Some(v) =>
      match parse_dimension(v) {
        Some(@types.Length(n)) => {
          self.style.row_gap = @types.Length(n)
          self.style.column_gap = @types.Length(n)
          changed = true
        }
        _ => ()
      }
    None => ()
  }
  if changed {
    self.mark_dirty()
  }
  changed
}

// =============================================================================
// CSS Value Parsers (simplified)
// =============================================================================

///|
fn try_parse_double(s : String) -> Double? {
  Some(@strconv.parse_double(s)) catch {
    _ => None
  }
}

///|
fn parse_dimension(value : String) -> @types.Dimension? {
  let v = value.trim().to_string()
  if v == "auto" {
    return Some(@types.Auto)
  }
  if v.has_suffix("px") && v.length() > 2 {
    let num_str = v[:v.length() - 2].to_string() catch { _ => "" }
    match try_parse_double(num_str) {
      Some(n) => return Some(@types.Length(n))
      None => ()
    }
  }
  if v.has_suffix("%") && v.length() > 1 {
    let num_str = v[:v.length() - 1].to_string() catch { _ => "" }
    match try_parse_double(num_str) {
      Some(n) => return Some(@types.Percent(n / 100.0))
      None => ()
    }
  }
  // Try parsing as plain number (treat as px)
  match try_parse_double(v) {
    Some(n) => Some(@types.Length(n))
    None => None
  }
}

///|
fn parse_display(value : String) -> @types.Display? {
  match value.trim().to_string() {
    "block" => Some(@types.Block)
    "flex" => Some(@types.Flex)
    "inline-flex" => Some(@types.InlineFlex)
    "grid" => Some(@types.Grid)
    "inline-grid" => Some(@types.InlineGrid)
    "none" => Some(@types.Display::None)
    _ => None
  }
}

///|
fn parse_flex_direction(value : String) -> @types.FlexDirection? {
  match value.trim().to_string() {
    "row" => Some(@types.Row)
    "row-reverse" => Some(@types.RowReverse)
    "column" => Some(@types.Column)
    "column-reverse" => Some(@types.ColumnReverse)
    _ => None
  }
}

///|
fn parse_flex_wrap(value : String) -> @types.FlexWrap? {
  match value.trim().to_string() {
    "nowrap" => Some(@types.NoWrap)
    "wrap" => Some(@types.Wrap)
    "wrap-reverse" => Some(@types.WrapReverse)
    _ => None
  }
}

///|
fn parse_alignment(value : String) -> @types.Alignment? {
  match value.trim().to_string() {
    "flex-start" | "start" => Some(@types.FlexStart)
    "flex-end" | "end" => Some(@types.FlexEnd)
    "center" => Some(@types.Center)
    "space-between" => Some(@types.SpaceBetween)
    "space-around" => Some(@types.SpaceAround)
    "space-evenly" => Some(@types.SpaceEvenly)
    "stretch" => Some(@types.Stretch)
    "baseline" => Some(@types.Baseline)
    _ => None
  }
}

// =============================================================================
// LayoutTree HTML Bridge
// =============================================================================

///|
/// Update a specific node's style and propagate dirty flags
pub fn LayoutTree::update_node_style(
  self : LayoutTree,
  node_id : String,
  values : @cascade.CascadedValues,
) -> Bool {
  // Find node by id
  let mut found_uid : Int? = None
  self.node_map.each(fn(uid, node) {
    if node.id == node_id {
      found_uid = Some(uid)
    }
  })
  match found_uid {
    Some(uid) =>
      match self.node_map.get(uid) {
        Some(node) => {
          let changed = node.apply_css_values(values)
          if changed {
            self.mark_node_dirty(uid)
          }
          changed
        }
        None => false
      }
    None => false
  }
}

///|
/// Batch update multiple nodes' styles
pub fn LayoutTree::batch_update_styles(
  self : LayoutTree,
  updates : Array[(String, @cascade.CascadedValues)],
) -> Int {
  let mut count = 0
  for update in updates {
    let (node_id, values) = update
    if self.update_node_style(node_id, values) {
      count += 1
    }
  }
  count
}

///|
/// Add a new node to the tree
pub fn LayoutTree::add_node(
  self : LayoutTree,
  parent_id : String,
  index : Int,
  node : LayoutNode,
) -> Bool {
  // Find parent by id
  let mut parent_uid : Int? = None
  self.node_map.each(fn(uid, n) {
    if n.id == parent_id {
      parent_uid = Some(uid)
    }
  })
  match parent_uid {
    Some(puid) =>
      match self.node_map.get(puid) {
        Some(parent) => {
          // Insert child at index
          let idx = if index < 0 {
            0
          } else if index > parent.children.length() {
            parent.children.length()
          } else {
            index
          }
          parent.children.insert(idx, node)

          // Update maps
          self.node_map[node.uid] = node
          self.parent_map[node.uid] = puid

          // Add all descendants to maps
          add_subtree_to_maps(node, puid, self.parent_map, self.node_map)

          // Mark parent dirty
          parent.mark_dirty()
          self.mark_node_dirty(puid)
          true
        }
        None => false
      }
    None => false
  }
}

///|
fn add_subtree_to_maps(
  node : LayoutNode,
  parent_uid : Int,
  parent_map : Map[Int, Int],
  node_map : Map[Int, LayoutNode],
) -> Unit {
  node_map[node.uid] = node
  parent_map[node.uid] = parent_uid
  for child in node.children {
    add_subtree_to_maps(child, node.uid, parent_map, node_map)
  }
}

///|
/// Remove a node from the tree
pub fn LayoutTree::remove_node(self : LayoutTree, node_id : String) -> Bool {
  // Find node by id
  let mut found_uid : Int? = None
  self.node_map.each(fn(uid, node) {
    if node.id == node_id {
      found_uid = Some(uid)
    }
  })
  match found_uid {
    Some(uid) =>
      match self.parent_map.get(uid) {
        Some(parent_uid) =>
          match self.node_map.get(parent_uid) {
            Some(parent) => {
              // Find and remove child
              let mut idx = -1
              for i, child in parent.children {
                if child.uid == uid {
                  idx = i
                  break
                }
              }
              if idx >= 0 {
                let _ = parent.children.remove(idx)

                // Remove from maps
                remove_subtree_from_maps(uid, self.parent_map, self.node_map)

                // Mark parent dirty
                parent.mark_dirty()
                self.mark_node_dirty(parent_uid)
                true
              } else {
                false
              }
            }
            None => false
          }
        None => false
      }
    None => false
  }
}

///|
fn remove_subtree_from_maps(
  uid : Int,
  parent_map : Map[Int, Int],
  node_map : Map[Int, LayoutNode],
) -> Unit {
  match node_map.get(uid) {
    Some(node) => {
      for child in node.children {
        remove_subtree_from_maps(child.uid, parent_map, node_map)
      }
      node_map.remove(uid)
      parent_map.remove(uid)
    }
    None => ()
  }
}

// =============================================================================
// HTML Element to LayoutNode Conversion
// =============================================================================

///|
/// Create a LayoutNode from HTML Element with computed styles
pub fn LayoutNode::from_html_element(
  element : @html.Element,
  computed_styles : @cascade.CascadedValues,
) -> LayoutNode {
  // Create node with element id or generate from tag
  let node_id = match element.id {
    Some(id) => id
    None => element.tag
  }
  let node = LayoutNode::create_with_id(node_id)

  // Apply computed styles
  let _ = node.apply_css_values(computed_styles)

  // Handle replaced elements with intrinsic sizing
  let tag_lower = element.tag.to_lower()
  if tag_lower == "img" {
    setup_image_node(node, element)
  } else if tag_lower == "canvas" {
    setup_canvas_node(node, element)
  }

  // Recursively convert children
  for html_child in element.children {
    match html_child {
      @html.Element(child_elem) => {
        // For now, use empty styles for children (should be computed separately)
        let child_node = LayoutNode::from_html_element(
          child_elem,
          @cascade.CascadedValues::new(),
        )
        let _ = node.add_child(child_node)

      }
      @html.Text(_) =>
        // Text nodes could be handled with MeasureFunc
        ()
    }
  }
  node
}

///|
/// Setup an img node with intrinsic sizing
/// If width/height attributes are present, use them as resolved size
/// Otherwise, set as Pending with placeholder dimensions
fn setup_image_node(node : LayoutNode, element : @html.Element) -> Unit {
  // Try to get dimensions from HTML attributes
  let width : Double? = match element.attributes.get("width") {
    Some(v) => parse_html_dimension(v)
    None => None
  }
  let height : Double? = match element.attributes.get("height") {
    Some(v) => parse_html_dimension(v)
    None => None
  }
  match (width, height) {
    (Some(w), Some(h)) => {
      // Both dimensions specified - use as resolved
      node.intrinsic_state = Some(Resolved(width=w, height=h))
      node.measure = Some(create_fixed_measure(w, h))
      // Also set explicit style dimensions to prevent flex stretch
      node.style.width = @types.Length(w)
      node.style.height = @types.Length(h)
    }
    (Some(w), None) => {
      // Only width - need height from image load (Pending)
      // Use width as placeholder, set pending for height calculation
      node.intrinsic_state = Some(
        Pending(
          placeholder_width=w,
          placeholder_height=default_placeholder_height,
        ),
      )
      node.measure = Some(create_fixed_measure(w, default_placeholder_height))
      node.style.width = @types.Length(w)
    }
    (None, Some(h)) => {
      // Only height - need width from image load (Pending)
      node.intrinsic_state = Some(
        Pending(
          placeholder_width=default_placeholder_width,
          placeholder_height=h,
        ),
      )
      node.measure = Some(create_fixed_measure(default_placeholder_width, h))
      node.style.height = @types.Length(h)
    }
    (None, None) => {
      // No dimensions - fully Pending
      node.intrinsic_state = Some(
        Pending(
          placeholder_width=default_placeholder_width,
          placeholder_height=default_placeholder_height,
        ),
      )
      node.measure = Some(
        create_fixed_measure(
          default_placeholder_width, default_placeholder_height,
        ),
      )
    }
  }
}

///|
/// Setup a canvas node with intrinsic sizing
/// Canvas defaults to 300x150 (HTML spec), but can be overridden by width/height attributes
fn setup_canvas_node(node : LayoutNode, element : @html.Element) -> Unit {
  // Canvas default intrinsic size (HTML spec)
  let default_width = 300.0
  let default_height = 150.0

  // Try to get dimensions from HTML attributes
  let width : Double = match element.attributes.get("width") {
    Some(v) =>
      match parse_html_dimension(v) {
        Some(w) => w
        None => default_width
      }
    None => default_width
  }
  let height : Double = match element.attributes.get("height") {
    Some(v) =>
      match parse_html_dimension(v) {
        Some(h) => h
        None => default_height
      }
    None => default_height
  }

  // Canvas always has resolved intrinsic size (doesn't need to load like images)
  node.intrinsic_state = Some(Resolved(width~, height~))
  node.measure = Some(create_fixed_measure(width, height))

  // Set style dimensions if not already set (canvas dimensions are used as default)
  // Only set if the style doesn't already have explicit dimensions
  match node.style.width {
    @types.Auto => node.style.width = @types.Length(width)
    _ => ()
  }
  match node.style.height {
    @types.Auto => node.style.height = @types.Length(height)
    _ => ()
  }
}

///|
/// Parse HTML dimension attribute (e.g., "300", "300px")
fn parse_html_dimension(value : String) -> Double? {
  // Remove "px" suffix if present
  let num_str : String = if value.has_suffix("px") && value.length() > 2 {
    value[:value.length() - 2].to_string() catch {
      _ => return None
    }
  } else {
    value
  }
  try_parse_double(num_str)
}

// =============================================================================
// Convenience Functions for Tree Building
// =============================================================================

///|
/// Build a complete LayoutTree from HTML Document with stylesheets
pub fn LayoutTree::from_html_document(
  doc : @html.Document,
  viewport_width : Double,
  viewport_height : Double,
) -> LayoutTree {
  // For now, create tree without CSS cascade (simplified)
  let root = LayoutNode::from_html_element(
    doc.root,
    @cascade.CascadedValues::new(),
  )
  LayoutTree::new(root, viewport_width, viewport_height)
}

///|
/// Find node by string id
pub fn LayoutTree::find_node_by_id(
  self : LayoutTree,
  node_id : String,
) -> LayoutNode? {
  let mut result : LayoutNode? = None
  self.node_map.each(fn(_uid, node) {
    if node.id == node_id {
      result = Some(node)
    }
  })
  result
}