///|
priv enum ComboboxIntent {
  Choose(String)
  Remove(String)
  SetQuery(String)
  SetOpen(Bool)
  Clear
}

///|
/// Decode gestures as intentions, then read controlled inputs at dispatch time.
/// A host batch may have decoded several gestures from the same rendered frame.
fn combobox_dispatch(
  context : UiContext,
  options : @minimoon.Val[Array[ChoiceOption]],
  state : @minimoon.Val[ComboboxState],
  on_change : @minimoon.Emit[ComboboxState],
  multiple~ : Bool,
  disabled~ : Bool,
  on_query_change? : @minimoon.Emit[String],
  on_values_change? : @minimoon.Emit[Array[String]],
  on_open_change? : @minimoon.Emit[Bool],
) -> @minimoon.Emit[ComboboxIntent] {
  let (_, emit) = @minimoon.create_state_with_input(
    input=@minimoon.Val::map2(options, state, (options, state) => {
      (options, state)
    }),
    init=(_, _) => @minimoon.no_cmd(()),
    update=(_, input, intent : ComboboxIntent, _) => {
      let (options, state) = input
      if disabled && !(intent is SetOpen(false)) {
        return @minimoon.no_cmd(())
      }
      let next = match intent {
        SetOpen(open) => { ..state, open, }
        SetQuery(query) => { ..state, query, }
        Clear => { ..state, values: [], query: "", }
        Remove(key) =>
          { ..state, values: state.values.filter(value => value != key), }
        Choose(key) => {
          guard state.open &&
            options.any(option => option.key == key && !option.disabled) else {
            return @minimoon.no_cmd(())
          }
          let values = if !multiple {
            [key]
          } else if state.values.contains(key) {
            state.values.filter(value => value != key)
          } else {
            let next = state.values.copy()
            next.push(key)
            next
          }
          { ..state, values, open: multiple, }
        }
      }
      (
        (),
        @minimoon.batch([
          on_change(next),
          if next.query != state.query {
            on_query_change
            .map(emit => emit(next.query))
            .unwrap_or(@minimoon.none)
          } else {
            @minimoon.none
          },
          if next.values != state.values {
            on_values_change
            .map(emit => emit(next.values))
            .unwrap_or(@minimoon.none)
          } else {
            @minimoon.none
          },
          if next.open != state.open {
            on_open_change
            .map(emit => emit(next.open))
            .unwrap_or(@minimoon.none)
          } else {
            @minimoon.none
          },
        ]),
      )
    },
    subscriptions=(_, _, emit) => context.page.on_hide(emit(SetOpen(false))),
  )
  emit
}