///|
/// Controlled native controls adapted from RUI 0.1.1 (MIT). No validation engine.
pub(all) struct ChoiceOption {
  key : String
  label : String
  disabled : Bool
  group : String
} derive(Eq, Debug)

///|
pub fn choice_option(
  key~ : String,
  label~ : String,
  disabled? : Bool = false,
  group? : String = "",
) -> ChoiceOption {
  guard key != "" else { abort("choice key must not be empty") }
  { key, label, disabled, group, }
}

///|
fn validate_choices(options : Array[ChoiceOption]) -> Unit {
  let seen : Map[String, Bool] = Map([])
  for option in options {
    guard option.key != "" && !seen.contains(option.key) else {
      abort("choice keys must be unique and nonempty")
    }
    seen[option.key] = true
  }
}

///|
pub struct FieldState {
  id : String
  label : String
  description : String
  error : String
  required : Bool
  disabled : Bool
} derive(Eq, Debug)

///|
pub fn field_state(
  id~ : String,
  label~ : String,
  description? : String = "",
  error? : String = "",
  required? : Bool = false,
  disabled? : Bool = false,
) -> FieldState {
  guard id != "" else { abort("field id must not be empty") }
  { id, label, description, error, required, disabled, }
}

///|
pub fn FieldState::semantics(
  self : FieldState,
  role? : @minimoon.SemanticRole = @minimoon.TextboxRole,
) -> @minimoon.Semantics {
  @minimoon.semantics(
    role~,
    label=self.label,
    labelled_by=self.id + "-label",
    described_by=self.id +
      (if self.error != "" { "-error" } else { "-description" }),
    required=self.required,
    invalid=self.error != "",
    disabled=self.disabled,
  )
}

///|
pub fn[C : @minimoon.IsChildren] field(
  state : FieldState,
  orientation? : Orientation = Vertical,
  children : C,
) -> @minimoon.Node {
  ui_container(
    "field",
    [
      @minimoon.label(
        for_id=state.id,
        id=state.id + "-label",
        class="mmui-field-label",
        state.label + (if state.required { " *" } else { "" }),
      ),
      ui_container("field-content", children.to_nodes()),
      field_description(id=state.id + "-description", state.description),
      if state.error == "" {
        @minimoon.nothing()
      } else {
        field_error(id=state.id + "-error", state.error)
      },
    ],
    class=orientation_class(orientation),
  )
}

///|
pub fn[Input : Eq] field_with_input(
  state : @minimoon.Val[FieldState],
  input : @minimoon.Val[Input],
  render : (FieldState, Input) -> @minimoon.Node,
) -> @minimoon.Val[@minimoon.Node] {
  @minimoon.Val::map2(state, input, (field_state, value) => {
    field(field_state, render(field_state, value))
  })
}

///|
/// Bind application-owned text and validation to one native input.
/// Create this component once in the page/component builder, not inside a view.
/// Validation and submitted/touched policy remain application-owned; focus is
/// component-owned and survives value, validation and option updates.
pub fn text_field(
  state : @minimoon.Val[FieldState],
  value : @minimoon.Val[String],
  on_input : @minimoon.Emit[String],
  options? : @minimoon.Val[InputOptions] = @minimoon.Val::constant(
    input_options(),
  ),
  orientation? : Orientation = Vertical,
  on_confirm? : @minimoon.Emit[String],
  on_focus? : @minimoon.Emit[@minimoon.FocusDetail],
  on_blur? : @minimoon.Emit[@minimoon.InputBlurDetail],
) -> @minimoon.Val[@minimoon.Node] {
  let (focused, update_focus) = @minimoon.create_variable(false)
  @minimoon.Val::view4(state, value, options, focused, (
    state,
    value,
    options,
    focused,
  ) => {
    let options = {
      ..options,
      invalid: options.invalid || state.error != "",
      disabled: options.disabled || state.disabled,
    }
    let semantics = @minimoon.semantics(
      role=@minimoon.TextboxRole,
      label=state.label,
      labelled_by=state.id + "-label",
      described_by=state.id +
        (if state.error != "" { "-error" } else { "-description" }),
      required=state.required,
      invalid=options.invalid,
      disabled=options.disabled || options.read_only,
    )
    field(
      state,
      orientation~,
      input(
        id=state.id,
        value~,
        on_input~,
        options~,
        focused~,
        semantics~,
        on_confirm?,
        on_focus=input_focus_event(true, update_focus, on_focus),
        on_blur=input_focus_event(false, update_focus, on_blur),
      ),
    )
  })
}

///|
pub fn[C : @minimoon.IsChildren] field_label(
  for_id~ : String,
  children : C,
) -> @minimoon.Node {
  label(for_id~, children)
}

///|
pub fn[C : @minimoon.IsChildren] form(
  on_submit~ : @minimoon.Emit[Map[String, Json]],
  on_reset? : @minimoon.Cmd,
  values? : Map[String, Json] = Map([]),
  submit_key? : String = "",
  reset_key? : String = "",
  id? : String = "",
  children : C,
) -> @minimoon.Node {
  @minimoon.form(
    on_submit=@minimoon.Emit(native_values => {
      let combined = native_values.copy()
      for name, value in values {
        combined[name] = value
      }
      on_submit(combined)
    }),
    on_reset?,
    submit_key~,
    reset_key~,
    id~,
    class="mmui-form",
    children,
  )
}

///|
pub fn[C : @minimoon.IsChildren] form_field(
  state : FieldState,
  children : C,
) -> @minimoon.Node {
  field(state, children)
}

///|
pub fn[C : @minimoon.IsChildren] form_label(
  for_id~ : String,
  children : C,
) -> @minimoon.Node {
  label(for_id~, children)
}