///|
/// APG Combobox Pattern
/// https://www.w3.org/WAI/ARIA/apg/patterns/combobox/
///
/// A combobox is a composite widget that combines an input (text or search)
/// with a listbox, allowing users to type to filter options or navigate
/// through a list of options.
///
/// Keyboard Interaction:
/// - Down Arrow: Open listbox and move to first/next option
/// - Up Arrow: Open listbox and move to last/previous option
/// - Enter: Select current option, close listbox
/// - Escape: Close listbox, clear selection
/// - Alt+Down Arrow: Open listbox without moving focus
/// - Alt+Up Arrow: Close listbox
///
/// ARIA:
/// - role="combobox" on input container
/// - aria-expanded on combobox
/// - aria-haspopup="listbox"
/// - aria-autocomplete (none, list, both, inline)
/// - aria-activedescendant on combobox (points to selected option)
/// - role="listbox" on popup
/// - role="option" on each option

///|
/// Combobox option data
pub(all) struct ComboboxOption {
  id : String
  label : String
  disabled : Bool
}

///|
/// Create a combobox option
pub fn combobox_option(
  id : String,
  label : String,
  disabled? : Bool,
) -> ComboboxOption {
  { id, label, disabled: disabled.unwrap_or(false) }
}

///|
/// Autocomplete behavior
pub(all) enum AutocompleteType {
  None // No autocomplete
  List // Filter list based on input
  Both // Filter list and complete input text
  Inline // Complete input text only
}

///|
/// Create a combobox input element.
///
/// Parameters:
/// - id: Input ID
/// - listbox_id: ID of the associated listbox
/// - expanded: Whether listbox is visible
/// - value: Current input value
/// - autocomplete: Autocomplete behavior
/// - aria_label: Accessible label
/// - aria_labelledby: ID of labelling element
/// - active_descendant: ID of the currently active option
/// - on_input: Input handler
/// - on_keydown: Keydown handler
/// - on_focus: Focus handler
/// - on_blur: Blur handler
pub fn[E] combobox_input(
  id : String,
  listbox_id : String,
  expanded : Bool,
  value : String,
  autocomplete? : AutocompleteType,
  aria_label? : String,
  aria_labelledby? : String,
  active_descendant? : String,
  on_input? : @luna.EventHandler[E],
  on_keydown? : @luna.EventHandler[E],
  on_focus? : @luna.EventHandler[E],
  on_blur? : @luna.EventHandler[E],
) -> @luna.Node[E, String] {
  let ac_str = match autocomplete {
    Some(None) => "none"
    Some(List) => "list"
    Some(Both) => "both"
    Some(Inline) => "inline"
    _ => "list"
  }
  let attrs : Array[(String, @luna.Attr[E, String])] = [
    ("type", @luna.attr_static("text")),
    ("id", @luna.attr_static(id)),
    ("role", @luna.attr_static("combobox")),
    ("aria-haspopup", @luna.attr_static("listbox")),
    (
      "aria-expanded",
      @luna.attr_static(if expanded { "true" } else { "false" }),
    ),
    ("aria-controls", @luna.attr_static(listbox_id)),
    ("aria-autocomplete", @luna.attr_static(ac_str)),
    ("value", @luna.attr_static(value)),
    ("autocomplete", @luna.attr_static("off")),
  ]
  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 active_descendant is Some(aid) {
    attrs.push(("aria-activedescendant", @luna.attr_static(aid)))
  }
  if on_input is Some(handler) {
    attrs.push(("input", @luna.attr_handler(handler)))
  }
  if on_keydown is Some(handler) {
    attrs.push(("keydown", @luna.attr_handler(handler)))
  }
  if on_focus is Some(handler) {
    attrs.push(("focus", @luna.attr_handler(handler)))
  }
  if on_blur is Some(handler) {
    attrs.push(("blur", @luna.attr_handler(handler)))
  }
  @luna.h("input", attrs) <| []
}

///|
/// Create a dynamic combobox input with signal-based state.
pub fn[E] combobox_input_dyn(
  id : String,
  listbox_id : String,
  expanded : @resource.Signal[Bool],
  value : @resource.Signal[String],
  autocomplete? : AutocompleteType,
  aria_label? : String,
  active_descendant : @resource.Signal[String],
  on_input? : @luna.EventHandler[E],
  on_keydown? : @luna.EventHandler[E],
  on_focus? : @luna.EventHandler[E],
  on_blur? : @luna.EventHandler[E],
) -> @luna.Node[E, String] {
  let ac_str = match autocomplete {
    Some(None) => "none"
    Some(List) => "list"
    Some(Both) => "both"
    Some(Inline) => "inline"
    _ => "list"
  }
  let attrs : Array[(String, @luna.Attr[E, String])] = [
    ("type", @luna.attr_static("text")),
    ("id", @luna.attr_static(id)),
    ("role", @luna.attr_static("combobox")),
    ("aria-haspopup", @luna.attr_static("listbox")),
    (
      "aria-expanded",
      @luna.attr_dynamic(fn() { if expanded.get() { "true" } else { "false" } }),
    ),
    ("aria-controls", @luna.attr_static(listbox_id)),
    ("aria-autocomplete", @luna.attr_static(ac_str)),
    ("value", @luna.attr_dynamic(fn() { value.get() })),
    ("autocomplete", @luna.attr_static("off")),
    (
      "aria-activedescendant",
      @luna.attr_dynamic(fn() { active_descendant.get() }),
    ),
  ]
  if aria_label is Some(label) {
    attrs.push(("aria-label", @luna.attr_static(label)))
  }
  if on_input is Some(handler) {
    attrs.push(("input", @luna.attr_handler(handler)))
  }
  if on_keydown is Some(handler) {
    attrs.push(("keydown", @luna.attr_handler(handler)))
  }
  if on_focus is Some(handler) {
    attrs.push(("focus", @luna.attr_handler(handler)))
  }
  if on_blur is Some(handler) {
    attrs.push(("blur", @luna.attr_handler(handler)))
  }
  @luna.h("input", attrs) <| []
}

///|
/// Create a combobox listbox container.
///
/// Parameters:
/// - id: Listbox ID
/// - labelled_by: ID of the element that labels this listbox
/// - visible: Whether listbox is visible
/// - children: Option elements
pub fn[E] combobox_listbox(
  id : String,
  labelled_by : String,
  visible : Bool,
  children : Array[@luna.Node[E, String]],
) -> @luna.Node[E, String] {
  let attrs : Array[(String, @luna.Attr[E, String])] = [
    ("role", @luna.attr_static("listbox")),
    ("id", @luna.attr_static(id)),
    ("aria-labelledby", @luna.attr_static(labelled_by)),
  ]
  if !visible {
    attrs.push(("hidden", @luna.attr_static("")))
  }
  @luna.h("ul", attrs) <| children
}

///|
/// Create a dynamic combobox listbox with signal-based visibility.
pub fn[E] combobox_listbox_dyn(
  id : String,
  labelled_by : String,
  visible : @resource.Signal[Bool],
  children : Array[@luna.Node[E, String]],
) -> @luna.Node[E, String] {
  let attrs : Array[(String, @luna.Attr[E, String])] = [
    ("role", @luna.attr_static("listbox")),
    ("id", @luna.attr_static(id)),
    ("aria-labelledby", @luna.attr_static(labelled_by)),
  ]
  @luna.show(fn() { visible.get() }, fn() { @luna.h("ul", attrs) <| children })
}

///|
/// Create a combobox option element.
///
/// Parameters:
/// - id: Option ID
/// - selected: Whether this option is the active descendant
/// - disabled: Whether option is disabled
/// - on_click: Click handler
/// - children: Option content
pub fn[E] combobox_option_el(
  id : String,
  selected : Bool,
  disabled? : Bool,
  on_click? : @luna.EventHandler[E],
  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("option")),
    ("id", @luna.attr_static(id)),
    (
      "aria-selected",
      @luna.attr_static(if selected { "true" } else { "false" }),
    ),
  ]
  if is_disabled {
    attrs.push(("aria-disabled", @luna.attr_static("true")))
  }
  if on_click is Some(handler) && !is_disabled {
    attrs.push(("click", @luna.attr_handler(handler)))
  }
  @luna.h("li", attrs) <| children
}

///|
/// Create a dynamic combobox option with signal-based selection.
pub fn[E] combobox_option_dyn(
  id : String,
  option_id : String,
  active_id : @resource.Signal[String],
  disabled? : Bool,
  on_click? : @luna.EventHandler[E],
  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("option")),
    ("id", @luna.attr_static(id)),
    (
      "aria-selected",
      @luna.attr_dynamic(fn() {
        if active_id.get() == option_id {
          "true"
        } else {
          "false"
        }
      }),
    ),
  ]
  if is_disabled {
    attrs.push(("aria-disabled", @luna.attr_static("true")))
  }
  if on_click is Some(handler) && !is_disabled {
    attrs.push(("click", @luna.attr_handler(handler)))
  }
  @luna.h("li", attrs) <| children
}

///|
/// Filter options based on input text.
fn filter_options(
  options : Array[ComboboxOption],
  query : String,
) -> Array[ComboboxOption] {
  if query == "" {
    return options.copy()
  }
  let query_lower = query.to_lower()
  let result : Array[ComboboxOption] = []
  for opt in options {
    if opt.label.to_lower().contains(query_lower) {
      result.push(opt)
    }
  }
  result
}

///|
/// Create a combobox keyboard handler.
/// Handles arrow navigation, Enter selection, Escape to close.
pub fn make_combobox_handler(
  is_open : () -> Bool,
  filtered_options : () -> Array[ComboboxOption],
  current_index : () -> Int,
  open_listbox : () -> Unit,
  close_listbox : () -> Unit,
  set_index : (Int) -> Unit,
  on_select : (ComboboxOption) -> Unit,
) -> @luna.EventHandler[@js.Any] {
  @luna.handler(fn(e : @js.Any) {
    let key : String = e._get("key").cast()
    let options = filtered_options()
    let count = options.length()
    let curr = current_index()
    match key {
      "ArrowDown" => {
        let _ = e._call("preventDefault", [])
        if !is_open() {
          open_listbox()
          if count > 0 {
            set_index(0)
          }
        } else if count > 0 {
          let next = if curr >= count - 1 { 0 } else { curr + 1 }
          set_index(next)
        }
      }
      "ArrowUp" => {
        let _ = e._call("preventDefault", [])
        if !is_open() {
          open_listbox()
          if count > 0 {
            set_index(count - 1)
          }
        } else if count > 0 {
          let next = if curr <= 0 { count - 1 } else { curr - 1 }
          set_index(next)
        }
      }
      "Enter" =>
        if is_open() && curr >= 0 && curr < count {
          let _ = e._call("preventDefault", [])
          let opt = options[curr]
          if !opt.disabled {
            on_select(opt)
            close_listbox()
          }
        }
      "Escape" =>
        if is_open() {
          let _ = e._call("preventDefault", [])
          close_listbox()
        }
      "Home" =>
        if is_open() && count > 0 {
          let _ = e._call("preventDefault", [])
          set_index(0)
        }
      "End" =>
        if is_open() && count > 0 {
          let _ = e._call("preventDefault", [])
          set_index(count - 1)
        }
      _ => ()
    }
  })
}

///|
/// Create a complete interactive combobox with filtering.
///
/// Parameters:
/// - id: Base ID for the component
/// - options: Available options
/// - placeholder: Placeholder text for input
/// - aria_label: Accessible label
/// - on_select: Called when an option is selected
pub fn combobox_interactive(
  id : String,
  options : Array[ComboboxOption],
  placeholder? : String,
  aria_label? : String,
  on_select : (ComboboxOption) -> Unit,
) -> @luna.Node[@js.Any, String] {
  let input_value = @resource.signal("")
  let is_open = @resource.signal(false)
  let active_index = @resource.signal(-1)
  let active_option_id = @resource.signal("")
  let input_id = id + "-input"
  let listbox_id = id + "-listbox"
  // Filtered options based on input
  let get_filtered = fn() -> Array[ComboboxOption] {
    filter_options(options, input_value.get())
  }
  // Update active option ID when index changes
  let update_active_id = fn(idx : Int) {
    active_index.set(idx)
    let filtered = get_filtered()
    if idx >= 0 && idx < filtered.length() {
      active_option_id.set(id + "-option-" + filtered[idx].id)
    } else {
      active_option_id.set("")
    }
  }
  // Keyboard handler
  let keydown_handler = make_combobox_handler(
    fn() { is_open.get() },
    get_filtered,
    fn() { active_index.get() },
    fn() {
      is_open.set(true)
      update_active_id(0)
    },
    fn() {
      is_open.set(false)
      active_index.set(-1)
      active_option_id.set("")
    },
    update_active_id,
    fn(opt) {
      input_value.set(opt.label)
      on_select(opt)
    },
  )
  // Input handler for filtering
  let input_handler = @luna.handler(fn(e : @js.Any) {
    let target = e._get("target")
    let val : String = target._get("value").cast()
    input_value.set(val)
    is_open.set(true)
    // Reset selection when typing
    let filtered = get_filtered()
    if filtered.length() > 0 {
      update_active_id(0)
    } else {
      update_active_id(-1)
    }
  })
  // Focus handler - open listbox
  let focus_handler = @luna.handler(fn(_ : @js.Any) { is_open.set(true) })
  // Blur handler - close listbox (with delay for click handling)
  let blur_handler = @luna.handler(fn(_ : @js.Any) {
    // Use setTimeout to allow click events on options to fire first
    let window = @js.global_this()
    let _ = window._call("setTimeout", [
      @js.any(fn() { is_open.set(false) }),
      @js.any(200),
    ])
  })
  // Build input
  let input_attrs : Array[(String, @luna.Attr[@js.Any, String])] = [
    ("type", @luna.attr_static("text")),
    ("id", @luna.attr_static(input_id)),
    ("role", @luna.attr_static("combobox")),
    ("aria-haspopup", @luna.attr_static("listbox")),
    (
      "aria-expanded",
      @luna.attr_dynamic(fn() { if is_open.get() { "true" } else { "false" } }),
    ),
    ("aria-controls", @luna.attr_static(listbox_id)),
    ("aria-autocomplete", @luna.attr_static("list")),
    ("value", @luna.attr_dynamic(fn() { input_value.get() })),
    ("autocomplete", @luna.attr_static("off")),
    (
      "aria-activedescendant",
      @luna.attr_dynamic(fn() { active_option_id.get() }),
    ),
    ("input", @luna.attr_handler(input_handler)),
    ("keydown", @luna.attr_handler(keydown_handler)),
    ("focus", @luna.attr_handler(focus_handler)),
    ("blur", @luna.attr_handler(blur_handler)),
  ]
  if placeholder is Some(p) {
    input_attrs.push(("placeholder", @luna.attr_static(p)))
  }
  if aria_label is Some(label) {
    input_attrs.push(("aria-label", @luna.attr_static(label)))
  }
  // Build listbox with reactive content using For node
  let build_listbox = fn() -> @luna.Node[@js.Any, String] {
    @luna.h(
      "ul",
      [
        ("role", @luna.attr_static("listbox")),
        ("id", @luna.attr_static(listbox_id)),
        ("aria-labelledby", @luna.attr_static(input_id)),
        ("class", @luna.attr_static("combobox-listbox")),
      ],
    ) <| [
      @luna.Node::For(render=fn() {
        let filtered = get_filtered()
        let option_nodes : Array[@luna.Node[@js.Any, String]] = []
        for opt in filtered {
          let opt_id = id + "-option-" + opt.id
          option_nodes.push(
            combobox_option_dyn(
              opt_id,
              opt_id,
              active_option_id,
              disabled=opt.disabled,
              on_click=@luna.handler(fn(_ : @js.Any) {
                if !opt.disabled {
                  input_value.set(opt.label)
                  on_select(opt)
                  is_open.set(false)
                }
              }),
            ) <| [
              @luna.text(opt.label),
            ],
          )
        }
        option_nodes
      }),
    ]
  }
  // Caret icon (CSS-based, since Luna doesn't support SVG namespace yet)
  let caret_icon = @luna.h(
      "span",
      [
        ("class", @luna.attr_static("combobox-caret")),
        ("aria-hidden", @luna.attr_static("true")),
      ],
    ) <| []
  @luna.h("div", [("class", @luna.attr_static("combobox-container"))]) <| [
    @luna.h("div", [("class", @luna.attr_static("combobox-input-wrapper"))]) <| [
      @luna.h("input", input_attrs) <| [],
      caret_icon,
    ],
    @luna.show(fn() { is_open.get() }, build_listbox),
  ]
}

///|
/// Create a select-only combobox (no text input filtering).
/// This is useful when options should not be filtered by typing.
pub fn combobox_select_only(
  id : String,
  options : Array[ComboboxOption],
  initial_value? : String,
  aria_label? : String,
  on_select : (ComboboxOption) -> Unit,
) -> @luna.Node[@js.Any, String] {
  let init_label = initial_value.unwrap_or_else(fn() {
    if options.length() > 0 {
      options[0].label
    } else {
      ""
    }
  })
  let selected_label = @resource.signal(init_label)
  let is_open = @resource.signal(false)
  let active_index = @resource.signal(0)
  let button_id = id + "-button"
  let listbox_id = id + "-listbox"
  let item_count = options.length()
  // Keyboard handler for button
  let button_keydown = @luna.handler(fn(e : @js.Any) {
    let key : String = e._get("key").cast()
    match key {
      "ArrowDown" | "Enter" | " " => {
        let _ = e._call("preventDefault", [])
        is_open.set(true)
      }
      "ArrowUp" => {
        let _ = e._call("preventDefault", [])
        is_open.set(true)
        active_index.set(item_count - 1)
      }
      _ => ()
    }
  })
  // Keyboard handler for listbox
  let listbox_keydown = @luna.handler(fn(e : @js.Any) {
    let key : String = e._get("key").cast()
    let curr = active_index.get()
    match key {
      "ArrowDown" => {
        let _ = e._call("preventDefault", [])
        let next = if curr >= item_count - 1 { 0 } else { curr + 1 }
        active_index.set(next)
      }
      "ArrowUp" => {
        let _ = e._call("preventDefault", [])
        let next = if curr <= 0 { item_count - 1 } else { curr - 1 }
        active_index.set(next)
      }
      "Enter" | " " => {
        let _ = e._call("preventDefault", [])
        if curr >= 0 && curr < item_count {
          let opt = options[curr]
          if !opt.disabled {
            selected_label.set(opt.label)
            on_select(opt)
            is_open.set(false)
          }
        }
      }
      "Escape" => {
        let _ = e._call("preventDefault", [])
        is_open.set(false)
      }
      "Home" => {
        let _ = e._call("preventDefault", [])
        active_index.set(0)
      }
      "End" => {
        let _ = e._call("preventDefault", [])
        active_index.set(item_count - 1)
      }
      _ => ()
    }
  })
  // Build option items
  let option_items : Array[@luna.Node[@js.Any, String]] = []
  for i, opt in options {
    let idx = i
    let opt_id = id + "-option-" + i.to_string()
    option_items.push(
      @luna.h(
        "li",
        [
          ("role", @luna.attr_static("option")),
          ("id", @luna.attr_static(opt_id)),
          (
            "aria-selected",
            @luna.attr_dynamic(fn() {
              if active_index.get() == idx {
                "true"
              } else {
                "false"
              }
            }),
          ),
          (
            "click",
            @luna.attr_handler(
              @luna.handler(fn(_ : @js.Any) {
                if !opt.disabled {
                  selected_label.set(opt.label)
                  on_select(opt)
                  is_open.set(false)
                }
              }),
            ),
          ),
          ("keydown", @luna.attr_handler(listbox_keydown)),
          (
            "tabindex",
            @luna.attr_dynamic(fn() {
              if active_index.get() == idx {
                "0"
              } else {
                "-1"
              }
            }),
          ),
        ],
      ) <| [
        @luna.text(opt.label),
      ],
    )
  }
  // Build button
  let button_attrs : Array[(String, @luna.Attr[@js.Any, String])] = [
    ("type", @luna.attr_static("button")),
    ("id", @luna.attr_static(button_id)),
    ("aria-haspopup", @luna.attr_static("listbox")),
    (
      "aria-expanded",
      @luna.attr_dynamic(fn() { if is_open.get() { "true" } else { "false" } }),
    ),
    ("aria-controls", @luna.attr_static(listbox_id)),
    ("keydown", @luna.attr_handler(button_keydown)),
    (
      "click",
      @luna.attr_handler(
        @luna.handler(fn(_ : @js.Any) { is_open.set(!is_open.get()) }),
      ),
    ),
  ]
  if aria_label is Some(label) {
    button_attrs.push(("aria-label", @luna.attr_static(label)))
  }
  @luna.h("div", [("class", @luna.attr_static("combobox-select-container"))]) <| [
    @luna.h("button", button_attrs) <| [
      @luna.text_dyn(fn() { selected_label.get() }),
    ],
    @luna.show(fn() { is_open.get() }, fn() {
      @luna.h(
        "ul",
        [
          ("role", @luna.attr_static("listbox")),
          ("id", @luna.attr_static(listbox_id)),
          ("aria-labelledby", @luna.attr_static(button_id)),
          ("class", @luna.attr_static("combobox-listbox")),
        ],
      ) <| option_items
    }),
  ]
}