///|
pub fn combobox_controlled(
  context : UiContext,
  id~ : String,
  label~ : String,
  options~ : @minimoon.Val[Array[ChoiceOption]],
  value~ : @minimoon.Val[String],
  on_change~ : @minimoon.Emit[String],
) -> @minimoon.Val[@minimoon.Node] {
  let (query, set_query) = @minimoon.create_variable("")
  let (open, set_open) = @minimoon.create_variable(false)
  let choose = form_dispatch(
    @minimoon.Val::map2(options, open, (options, open) => (options, open)),
    (input, key : String) => {
      let (options, open) = input
      if open && options.any(option => option.key == key && !option.disabled) {
        @minimoon.batch([on_change(key), set_open(_ => false)])
      } else {
        @minimoon.none
      }
    },
  )
  let content = @minimoon.Val::view3(query, options, value, (
    query,
    options,
    value,
  ) => {
    let results = options.filter(option => {
      query == "" || option.label.contains(query) || option.key.contains(query)
    })
    ui_container("combobox", [
      @minimoon.input(
        value=query,
        on_input=set_query.map(value => _ => value),
        event_key=id + "/search",
        placeholder="Search",
      ),
      ui_container(
        "combobox-list",
        semantics=@minimoon.semantics(role=@minimoon.ListboxRole),
        results.map(option => {
          @minimoon.button(
            disabled=option.disabled,
            event_key=id + "/option/" + option.key,
            on_tap=if option.disabled {
              @minimoon.none
            } else {
              choose(option.key)
            },
            semantics=@minimoon.semantics(
              role=@minimoon.OptionRole,
              selected=value == option.key,
            ),
            option.label,
          )
        }),
      ),
      if results.is_empty() {
        @minimoon.text("No results")
      } else {
        @minimoon.nothing()
      },
    ])
  })
  overlay_controlled(
    context,
    id~,
    title=label,
    trigger_label=label,
    content~,
    open~,
    on_change=set_open.map(value => _ => value),
    kind=PopoverSurface,
  )
}

///|
pub fn combobox(
  context : UiContext,
  id~ : String,
  label~ : String,
  options~ : @minimoon.Val[Array[ChoiceOption]],
  initial_value? : String = "",
) -> @minimoon.Val[@minimoon.Node] {
  let (value, emit) = @minimoon.create_variable(initial_value)
  combobox_controlled(
    context,
    id~,
    label~,
    options~,
    value~,
    on_change=emit.map(value => _ => value),
  )
}

///|
pub fn command(
  id~ : String,
  items~ : @minimoon.Val[Array[MenuItem]],
  placeholder? : String = "Search commands",
) -> @minimoon.Val[@minimoon.Node] {
  let (query, emit_query) = @minimoon.create_variable("")
  let results = @minimoon.Val::map2(items, query, (items, query) => {
    items.filter(item => query == "" || item.label.contains(query))
  })
  let (expanded, emit_expanded) = menu_dispatch(
    id + "/results",
    results,
    @minimoon.none,
  )
  @minimoon.Val::view3(items, query, expanded, (items, query, expanded) => {
    let results = items.filter(item => query == "" || item.label.contains(query))
    ui_container("command", id~, [
      @minimoon.input(
        value=query,
        on_input=emit_query.map(value => _ => value),
        event_key=id + "/search",
        placeholder~,
      ),
      render_menu(id + "/results", results, expanded, emit_expanded),
      if results.is_empty() {
        @minimoon.text("No results")
      } else {
        @minimoon.nothing()
      },
    ])
  })
}