///|
pub(all) enum SwitchSize {
  Default
  Sm
} derive(Debug, Eq)

///|
const SwitchBaseStyle : String = "display:inline-flex;flex-shrink:0;align-items:center;--rui-control-base-bg:var(--rui-switch-unchecked,var(--rui-input,oklch(0.922 0 0)));--rui-control-base-fg:currentColor;--rui-control-base-border:transparent;--rui-control-base-shadow:0 1px 2px rgb(0 0 0 / 0.05);border:1px solid transparent;border-color:var(--rui-control-border,var(--rui-control-base-border,transparent));border-radius:9999px;margin:0;background:var(--rui-control-bg,var(--rui-control-base-bg,var(--rui-switch-unchecked,var(--rui-input,oklch(0.922 0 0)))));color:var(--rui-control-fg,var(--rui-control-base-fg,currentColor));box-shadow:var(--rui-control-shadow,var(--rui-control-base-shadow,0 1px 2px rgb(0 0 0 / 0.05)));cursor:pointer;appearance:none;-webkit-appearance:none;outline-offset:2px;vertical-align:middle"

///|
const SwitchCheckedStyle : String = "--rui-control-base-bg:var(--rui-primary,oklch(0.205 0 0))"

///|
const SwitchUncheckedStyle : String = "--rui-control-base-bg:var(--rui-switch-unchecked,var(--rui-input,oklch(0.922 0 0)))"

///|
const SwitchInvalidStyle : String = "border-color:var(--rui-destructive-border,var(--rui-destructive,oklch(0.577 0.245 27.325)));box-shadow:0 0 0 3px var(--rui-destructive-ring,oklch(0.577 0.245 27.325 / 0.2))"

///|
const SwitchSizeDefaultStyle : String = "width:2rem;height:1.15rem;padding:0"

///|
const SwitchSizeSmStyle : String = "width:1.5rem;height:0.875rem;padding:0"

///|
const SwitchThumbStyle : String = "display:block;flex-shrink:0;border-radius:9999px;pointer-events:none;transition:transform 150ms ease"

///|
const SwitchThumbSizeDefaultStyle : String = "width:1rem;height:1rem"

///|
const SwitchThumbSizeSmStyle : String = "width:0.75rem;height:0.75rem"

///|
const SwitchThumbCheckedDefaultStyle : String = "transform:translateX(calc(100% - 2px))"

///|
const SwitchThumbCheckedSmStyle : String = "transform:translateX(calc(100% - 2px))"

///|
const SwitchThumbUncheckedStyle : String = "transform:translateX(0)"

///|
const SwitchReadOnlyStyle : String = "cursor:default"

///|
const SwitchRtlCss : String =
  #|[data-slot="switch-thumb"][data-state="checked"]:dir(rtl) {
  #|  transform:translateX(calc(-100% + 2px)) !important;
  #|}
  #|

///|
#warnings("-alert_xss_vulnerable")
fn switch_rtl_stylesheet() -> @html.Html {
  // `:dir()` follows the nearest inherited direction without runtime script.
  let attrs = @html.Attrs::build()
    .data_set("rui-style", "switch-rtl")
    .inner_html(SwitchRtlCss)
  @html.style_tag(attrs~, ([] : Array[@html.Html]))
}

///|
fn switch_size_style(size : SwitchSize) -> String {
  match size {
    Default => SwitchSizeDefaultStyle
    Sm => SwitchSizeSmStyle
  }
}

///|
fn switch_size_value(size : SwitchSize) -> String {
  match size {
    Default => "default"
    Sm => "sm"
  }
}

///|
fn switch_thumb_size_style(size : SwitchSize) -> String {
  match size {
    Default => SwitchThumbSizeDefaultStyle
    Sm => SwitchThumbSizeSmStyle
  }
}

///|
fn switch_state_style(checked : Bool) -> String {
  if checked {
    SwitchCheckedStyle
  } else {
    SwitchUncheckedStyle
  }
}

///|
fn switch_thumb_state_style(checked : Bool, size : SwitchSize) -> String {
  if !checked {
    "background:var(--rui-switch-thumb-unchecked,var(--rui-background,oklch(1 0 0)));\{SwitchThumbUncheckedStyle}"
  } else {
    let position = match size {
      Default => SwitchThumbCheckedDefaultStyle
      Sm => SwitchThumbCheckedSmStyle
    }
    "background:var(--rui-switch-thumb-checked,var(--rui-background,oklch(1 0 0)));\{position}"
  }
}

///|
fn switch_invalid_style(invalid : Bool) -> String {
  if invalid {
    SwitchInvalidStyle
  } else {
    ""
  }
}

///|
fn switch_read_only_style(read_only : Bool) -> String {
  if read_only {
    SwitchReadOnlyStyle
  } else {
    ""
  }
}

///|
/// Render one switch state for the incremental wrapper.
///
/// As a native button with `role="switch"`, mouse, Space, and Enter all request
/// the next value. No hidden form input is created.
fn render_switch(
  checked? : Bool = false,
  size? : SwitchSize = Default,
  disabled? : Bool = false,
  read_only? : Bool = false,
  invalid? : Bool = false,
  id? : String,
  class? : String,
  title? : String,
  aria_label? : String,
  on_checked_change? : @cmd.Emit[Bool],
  attrs? : @html.Attrs,
  style? : Array[String] = [],
) -> @html.Html {
  let state = if checked { "checked" } else { "unchecked" }
  let control_attrs = ui_attrs(attrs)
    .data_set("slot", "switch")
    .data_set("size", switch_size_value(size))
    .data_set("state", state)
  ignore(control_attrs.role("switch"))
  ignore(control_attrs.aria_checked(ui_bool(checked)))
  ignore(control_attrs.aria_disabled(ui_bool(disabled)))
  ignore(control_attrs.aria_readonly(ui_bool(read_only)))
  ignore(control_attrs.data_set(state, ""))
  if disabled {
    ignore(control_attrs.data_set("disabled", ""))
  }
  if read_only {
    ignore(control_attrs.data_set("readonly", ""))
  }
  if invalid {
    ignore(control_attrs.aria_invalid("true").data_set("invalid", ""))
  }
  if aria_label is Some(label) {
    ignore(control_attrs.aria_label(label))
  }
  if !disabled && !read_only && on_checked_change is Some(emit) {
    ignore(control_attrs.on_click(_ => emit(!checked)))
  }
  let thumb_attrs = @html.Attrs::build()
    .data_set("slot", "switch-thumb")
    .data_set("state", state)
    .data_set(state, "")
  if disabled {
    ignore(thumb_attrs.data_set("disabled", ""))
  }
  if read_only {
    ignore(thumb_attrs.data_set("readonly", ""))
  }
  ignore(thumb_attrs.aria_hidden("true"))
  let thumb = @html.span(
    style=[
      UiBoxSizing,
      SwitchThumbStyle,
      switch_thumb_size_style(size),
      switch_thumb_state_style(checked, size),
    ],
    attrs=thumb_attrs,
    "",
  )
  let control = @html.button(
    style=ui_styles(
      [
        UiBoxSizing,
        UiTransition,
        SwitchBaseStyle,
        switch_size_style(size),
        switch_state_style(checked),
        switch_read_only_style(read_only),
        switch_invalid_style(invalid),
        ui_disabled_visual_style(disabled),
      ],
      style,
    ),
    id?,
    class?,
    title?,
    type_="button",
    disabled~,
    attrs=control_attrs,
    thumb,
  )
  @html.fragment([switch_rtl_stylesheet(), control])
}

///|
/// Build a black-box switch with component-local incremental state.
#cfg(target="js")
pub fn switch(
  default_checked? : Bool = false,
  size? : SwitchSize = Default,
  disabled? : Bool = false,
  read_only? : Bool = false,
  invalid? : Bool = false,
  id? : String,
  class? : String,
  title? : String,
  aria_label? : String,
  on_checked_change? : @cmd.Emit[Bool],
  attrs? : @html.Attrs,
  style? : Array[String] = [],
) -> @rabbita.Val[@html.Html] {
  let (checked, set_checked) = @rabbita.create_variable(default_checked)
  checked.view(checked => {
    render_switch(
      checked~,
      size~,
      disabled~,
      read_only~,
      invalid~,
      id?,
      class?,
      title?,
      aria_label?,
      on_checked_change=@cmd.Emit(next => {
        let update = set_checked(_ => next)
        if on_checked_change is Some(notify) {
          @cmd.batch([update, notify(next)])
        } else {
          update
        }
      }),
      attrs?,
      style~,
    )
  })
}

///|
#cfg(not(target="js"))
pub fn switch(
  default_checked? : Bool = false,
  size? : SwitchSize = Default,
  disabled? : Bool = false,
  read_only? : Bool = false,
  invalid? : Bool = false,
  id? : String,
  class? : String,
  title? : String,
  aria_label? : String,
  on_checked_change? : @cmd.Emit[Bool],
  attrs? : @html.Attrs,
  style? : Array[String] = [],
) -> @rabbita.Val[@html.Html] {
  ignore(on_checked_change)
  @rabbita.Val::constant(
    render_switch(
      checked=default_checked,
      size~,
      disabled~,
      read_only~,
      invalid~,
      id?,
      class?,
      title?,
      aria_label?,
      attrs?,
      style~,
    ),
  )
}