///|
/// APG Tree View Pattern
/// https://www.w3.org/WAI/ARIA/apg/patterns/treeview/
///
/// A tree view presents a hierarchical list of items that can be
/// expanded and collapsed.
///
/// Keyboard Interaction:
/// - Down Arrow: Move to next visible node
/// - Up Arrow: Move to previous visible node
/// - Right Arrow: Expand closed parent or move to first child
/// - Left Arrow: Collapse open parent or move to parent
/// - Home: Move to first node
/// - End: Move to last visible node
/// - Enter/Space: Activate/select node
///
/// ARIA:
/// - role="tree" on container
/// - role="treeitem" on each node
/// - role="group" on child container
/// - aria-expanded on parent nodes
/// - aria-selected on all nodes

///|
/// Tree node data structure
pub(all) struct TreeNode {
  id : String
  label : String
  children : Array[TreeNode]
  disabled : Bool
}

///|
/// Create a tree node
pub fn tree_node(
  id : String,
  label : String,
  children? : Array[TreeNode],
  disabled? : Bool,
) -> TreeNode {
  {
    id,
    label,
    children: children.unwrap_or([]),
    disabled: disabled.unwrap_or(false),
  }
}

///|
/// Check if a node has children
pub fn has_children(node : TreeNode) -> Bool {
  node.children.length() > 0
}

///|
/// Create the tree container.
///
/// Parameters:
/// - id: Tree ID
/// - aria_label: Accessible label
/// - aria_labelledby: ID of labelling element
/// - multiselectable: Whether multiple items can be selected
/// - children: Tree items
pub fn[E] tree(
  id? : String,
  aria_label? : String,
  aria_labelledby? : String,
  multiselectable? : Bool,
  children : Array[@luna.Node[E, String]],
) -> @luna.Node[E, String] {
  let attrs : Array[(String, @luna.Attr[E, String])] = [
    ("role", @luna.attr_static("tree")),
  ]
  if id is Some(i) {
    attrs.push(("id", @luna.attr_static(i)))
  }
  if aria_label is Some(label) {
    attrs.push(("aria-label", @luna.attr_static(label)))
  }
  if aria_labelledby is Some(lid) {
    attrs.push(("aria-labelledby", @luna.attr_static(lid)))
  }
  if multiselectable is Some(true) {
    attrs.push(("aria-multiselectable", @luna.attr_static("true")))
  }
  @luna.h("ul", attrs) <| children
}

///|
/// Create a tree item (leaf node or branch node).
///
/// Parameters:
/// - id: Item ID
/// - selected: Whether the item is selected
/// - expanded: Whether the item is expanded (for parent nodes)
/// - has_children: Whether this is a parent node
/// - focused: Whether this item has tabindex=0
/// - disabled: Whether the item is disabled
/// - on_click: Click handler
/// - on_keydown: Keydown handler
/// - label_content: Content for the label
/// - children: Child nodes (for parent nodes)
pub fn[E] treeitem(
  id? : String,
  selected : Bool,
  expanded? : Bool,
  has_children : Bool,
  focused : Bool,
  disabled? : Bool,
  on_click? : @luna.EventHandler[E],
  on_keydown? : @luna.EventHandler[E],
  label_content : Array[@luna.Node[E, String]],
  children? : Array[@luna.Node[E, String]],
) -> @luna.Node[E, String] {
  let is_disabled = disabled.unwrap_or(false)
  let attrs : Array[(String, @luna.Attr[E, String])] = [
    ("role", @luna.attr_static("treeitem")),
    (
      "aria-selected",
      @luna.attr_static(if selected { "true" } else { "false" }),
    ),
    ("tabindex", @luna.attr_static(if focused { "0" } else { "-1" })),
  ]
  if id is Some(i) {
    attrs.push(("id", @luna.attr_static(i)))
  }
  if has_children {
    if expanded is Some(exp) {
      attrs.push(
        ("aria-expanded", @luna.attr_static(if exp { "true" } else { "false" })),
      )
    }
  }
  if is_disabled {
    attrs.push(("aria-disabled", @luna.attr_static("true")))
  }
  if on_click is Some(handler) {
    if !is_disabled {
      attrs.push(("click", @luna.attr_handler(handler)))
    }
  }
  if on_keydown is Some(handler) {
    attrs.push(("keydown", @luna.attr_handler(handler)))
  }
  // Build content
  let content : Array[@luna.Node[E, String]] = label_content.copy()
  // Add child group if expanded
  if has_children {
    if expanded is Some(true) && children is Some(kids) {
      content.push(
        @luna.h("ul", [("role", @luna.attr_static("group"))]) <| kids,
      )
    }
  }
  @luna.h("li", attrs) <| content
}

///|
/// Create a dynamic tree item with signal-based state.
pub fn[E] treeitem_dyn(
  id? : String,
  selected : @resource.Signal[String],
  item_id : String,
  expanded? : @resource.Signal[Map[String, Bool]],
  has_children : Bool,
  focused_id : @resource.Signal[String],
  disabled? : Bool,
  on_click? : @luna.EventHandler[E],
  on_keydown? : @luna.EventHandler[E],
  label_content : Array[@luna.Node[E, String]],
  children? : Array[@luna.Node[E, String]],
) -> @luna.Node[E, String] {
  let is_disabled = disabled.unwrap_or(false)
  let attrs : Array[(String, @luna.Attr[E, String])] = [
    ("role", @luna.attr_static("treeitem")),
    (
      "aria-selected",
      @luna.attr_dynamic(fn() {
        if selected.get() == item_id {
          "true"
        } else {
          "false"
        }
      }),
    ),
    (
      "tabindex",
      @luna.attr_dynamic(fn() {
        if focused_id.get() == item_id {
          "0"
        } else {
          "-1"
        }
      }),
    ),
  ]
  if id is Some(i) {
    attrs.push(("id", @luna.attr_static(i)))
  }
  if has_children {
    if expanded is Some(exp_map) {
      attrs.push(
        (
          "aria-expanded",
          @luna.attr_dynamic(fn() {
            if exp_map.get().get(item_id).unwrap_or(false) {
              "true"
            } else {
              "false"
            }
          }),
        ),
      )
    }
  }
  if is_disabled {
    attrs.push(("aria-disabled", @luna.attr_static("true")))
  }
  if on_click is Some(handler) {
    if !is_disabled {
      attrs.push(("click", @luna.attr_handler(handler)))
    }
  }
  if on_keydown is Some(handler) {
    attrs.push(("keydown", @luna.attr_handler(handler)))
  }
  // Build content - children need to be rendered conditionally
  let content : Array[@luna.Node[E, String]] = label_content.copy()
  if has_children {
    if expanded is Some(exp_map) && children is Some(kids) {
      content.push(
        @luna.show(fn() { exp_map.get().get(item_id).unwrap_or(false) }, fn() {
          @luna.h("ul", [("role", @luna.attr_static("group"))]) <| kids
        }),
      )
    }
  }
  @luna.h("li", attrs) <| content
}

///|
/// Flatten tree nodes into a list of visible node IDs for navigation.
/// Only includes expanded nodes' children.
pub fn flatten_visible_nodes(
  nodes : Array[TreeNode],
  expanded : Map[String, Bool],
) -> Array[String] {
  let result : Array[String] = []
  for node in nodes {
    result.push(node.id)
    if has_children(node) && expanded.get(node.id).unwrap_or(false) {
      let children_flat = flatten_visible_nodes(node.children, expanded)
      for child_id in children_flat {
        result.push(child_id)
      }
    }
  }
  result
}

///|
/// Find parent ID for a node
pub fn find_parent_id(
  nodes : Array[TreeNode],
  target_id : String,
  parent_id : String?,
) -> String? {
  for node in nodes {
    if node.id == target_id {
      return parent_id
    }
    if has_children(node) {
      let found = find_parent_id(node.children, target_id, Some(node.id))
      if found is Some(_) {
        return found
      }
    }
  }
  None
}

///|
/// Find a node by ID
pub fn find_node(nodes : Array[TreeNode], target_id : String) -> TreeNode? {
  for node in nodes {
    if node.id == target_id {
      return Some(node)
    }
    if has_children(node) {
      let found = find_node(node.children, target_id)
      if found is Some(_) {
        return found
      }
    }
  }
  None
}

///|
/// Create tree view keyboard handler.
pub fn make_tree_handler(
  nodes : Array[TreeNode],
  focused_id : @resource.Signal[String],
  expanded : @resource.Signal[Map[String, Bool]],
  selected : @resource.Signal[String],
  on_activate : (String) -> Unit,
) -> @luna.EventHandler[@js.Any] {
  @luna.handler(fn(e : @js.Any) {
    // Stop event propagation to prevent multiple handlers firing
    let _ = e._call("stopPropagation", [])
    let key : String = e._get("key").cast()
    let current_id = focused_id.get()
    let exp_map = expanded.get()
    let visible = flatten_visible_nodes(nodes, exp_map)
    let current_index = {
      let mut idx = 0
      for i, id in visible {
        if id == current_id {
          idx = i
          break
        }
      }
      idx
    }
    match key {
      "ArrowDown" => {
        let _ = e._call("preventDefault", [])
        if current_index < visible.length() - 1 {
          let next_id = visible[current_index + 1]
          focused_id.set(next_id)
          focus_tree_item(next_id)
        }
      }
      "ArrowUp" => {
        let _ = e._call("preventDefault", [])
        if current_index > 0 {
          let prev_id = visible[current_index - 1]
          focused_id.set(prev_id)
          focus_tree_item(prev_id)
        }
      }
      "ArrowRight" => {
        let _ = e._call("preventDefault", [])
        if find_node(nodes, current_id) is Some(node) {
          if has_children(node) {
            let is_expanded = exp_map.get(current_id).unwrap_or(false)
            if !is_expanded {
              // Expand
              let new_map = exp_map.copy()
              new_map.set(current_id, true)
              expanded.set(new_map)
            } else if node.children.length() > 0 {
              // Move to first child
              let first_child = node.children[0]
              focused_id.set(first_child.id)
              focus_tree_item(first_child.id)
            }
          }
        }
      }
      "ArrowLeft" => {
        let _ = e._call("preventDefault", [])
        if find_node(nodes, current_id) is Some(node) {
          if has_children(node) && exp_map.get(current_id).unwrap_or(false) {
            // Collapse
            let new_map = exp_map.copy()
            new_map.set(current_id, false)
            expanded.set(new_map)
            // Move to parent
          } else if find_parent_id(nodes, current_id, None) is Some(parent_id) {
            focused_id.set(parent_id)
            focus_tree_item(parent_id)
          }
        }
      }
      "Home" => {
        let _ = e._call("preventDefault", [])
        if visible.length() > 0 {
          let first_id = visible[0]
          focused_id.set(first_id)
          focus_tree_item(first_id)
        }
      }
      "End" => {
        let _ = e._call("preventDefault", [])
        if visible.length() > 0 {
          let last_id = visible[visible.length() - 1]
          focused_id.set(last_id)
          focus_tree_item(last_id)
        }
      }
      "Enter" | " " => {
        let _ = e._call("preventDefault", [])
        selected.set(current_id)
        on_activate(current_id)
      }
      _ => ()
    }
  })
}

///|
/// Focus a tree item by ID
fn focus_tree_item(item_id : String) -> Unit {
  let doc = @js.global_this()._get("document")
  let el = doc._call("getElementById", [@js.any(item_id)])
  let _ = el._call("focus", [])
}

///|
/// Render a single tree node (helper to ensure proper closure capture).
fn render_single_tree_node(
  node_id : String,
  node_label : String,
  node_children : Array[TreeNode],
  node_disabled : Bool,
  focused_id : @resource.Signal[String],
  expanded : @resource.Signal[Map[String, Bool]],
  selected : @resource.Signal[String],
  keydown_handler : @luna.EventHandler[@js.Any],
  on_select : (String) -> Unit,
) -> @luna.Node[@js.Any, String] {
  let has_kids = node_children.length() > 0
  // Build child nodes recursively
  let child_nodes = if has_kids {
    Some(
      render_tree_nodes(
        node_children, focused_id, expanded, selected, keydown_handler, on_select,
      ),
    )
  } else {
    None
  }
  treeitem_dyn(
    id=node_id,
    selected,
    node_id,
    expanded~,
    has_kids,
    focused_id,
    disabled=node_disabled,
    on_click=@luna.handler(fn(_) {
      focused_id.set(node_id)
      on_select(node_id)
    }),
    on_keydown=keydown_handler,
    [@luna.text(node_label)],
    children=child_nodes.unwrap_or([]),
  )
}

///|
/// Render tree nodes recursively.
fn render_tree_nodes(
  nodes : Array[TreeNode],
  focused_id : @resource.Signal[String],
  expanded : @resource.Signal[Map[String, Bool]],
  selected : @resource.Signal[String],
  keydown_handler : @luna.EventHandler[@js.Any],
  on_select : (String) -> Unit,
) -> Array[@luna.Node[@js.Any, String]] {
  let items : Array[@luna.Node[@js.Any, String]] = []
  for node in nodes {
    // Call helper function to ensure proper closure capture
    items.push(
      render_single_tree_node(
        node.id,
        node.label,
        node.children,
        node.disabled,
        focused_id,
        expanded,
        selected,
        keydown_handler,
        on_select,
      ),
    )
  }
  items
}

///|
/// Create a complete interactive tree view.
///
/// Parameters:
/// - id: Tree ID
/// - nodes: Tree node data
/// - aria_label: Accessible label
/// - on_select: Called when a node is selected
pub fn tree_interactive(
  id : String,
  nodes : Array[TreeNode],
  aria_label? : String,
  on_select : (String) -> Unit,
) -> @luna.Node[@js.Any, String] {
  // Initialize state
  let first_id = if nodes.length() > 0 { nodes[0].id } else { "" }
  let focused_id = @resource.signal(first_id)
  let expanded : @resource.Signal[Map[String, Bool]] = @resource.signal({})
  let selected = @resource.signal("")
  // Create keydown handler
  let keydown_handler = make_tree_handler(
    nodes, focused_id, expanded, selected, on_select,
  )
  // Render tree items
  let tree_items = render_tree_nodes(
    nodes, focused_id, expanded, selected, keydown_handler, on_select,
  )
  // Build tree container
  let attrs : Array[(String, @luna.Attr[@js.Any, String])] = [
    ("role", @luna.attr_static("tree")),
    ("id", @luna.attr_static(id)),
  ]
  if aria_label is Some(label) {
    attrs.push(("aria-label", @luna.attr_static(label)))
  }
  @luna.h("ul", attrs) <| tree_items
}