// Guide: /docs/creating-components · adapter internals: /docs/zag-adapter
// Authoring rules: notes/component-authoring.md (v2 pattern)
import * as checkboxMachine from "@zag-js/checkbox";
import type { MachineInput } from "marko-zag";
import Icon from "../icon/icon.marko";
import { cn } from "#lib/utils.ts";
import { stripOwnProps } from "#lib/native-attrs.ts";
import { checkbox as styles } from "./classes.ts";

// MachineInput = native <input type="text"> attributes ∩ the machine's full Props, with
// `id` made optional (generated below, overridable). `checkedChange` is
// additive sugar enabling Marko's two-way bind shorthand
// (<Checkbox checked:=myState/>); Zag's own `onCheckedChange` also works and
// receives the details object.
export type Input = MachineInput<"input", checkboxMachine.Props> & {
  checkedChange?: (checked: boolean | "indeterminate") => void;
};

// <zag> wires the machine in one tag and returns `api` as a GETTER —
// call it at every use site. Getters are the serializable currency of
// Marko's resumability: the api object itself must never become reactive
// state. See /docs/creating-components.
<zag/api=() => checkboxMachine from=input/>

// Whatever is neither machine-owned (splitProps) nor component-owned
// (class, checkedChange) passes through to the hidden input: aria-*,
// data-*, form attributes...
//
// NOTE: Zag's CheckboxProps has no `indeterminate` input — indeterminate is
// expressed as `checked="indeterminate"` (CheckedState = boolean |
// "indeterminate"). Because `indeterminate` isn't in checkboxMachine.props,
// <zag>'s `from=` (picking checkboxMachine.props) never forwards it to the
// machine, so a stray `indeterminate` attribute would otherwise fall
// through splitProps into nativeAttrs and land as a bogus static HTML
// attribute on the native <input type="text">. Strip it defensively here; the supported
// API for tri-state is `checked`.
// The source is widened with an explicit optional `indeterminate` rather than
// `as typeof input`: `Input` deliberately does NOT declare the prop (it is not
// part of the supported API), so `keyof Input` cannot name it and
// `stripOwnProps`'s `keyof Source` constraint rejects the key. Naming it here
// is what makes the defensive strip above both legal and honest.
<const/hiddenInputProps=(): Marko.NativeTags["input"]["input"] => ({
  ...stripOwnProps(
    checkboxMachine.splitProps(input)[1] as Input & { indeterminate?: unknown },
    "class",
    "checkedChange",
    "indeterminate",
    "checked",
  ),
  ...api().getHiddenInputProps(),
})>

// The root <label> WRAPS the hidden input, so Zag's `for` (htmlFor) pointing
// at it is redundant — html-validate flags label[for] whose target is already
// a descendant. Drop it; the wrapping association is the association.
<const/rootProps=() => {
  const props = api().getRootProps() as Record<string, unknown>;
  delete props.for;
  return props;
}/>

<label
  ...rootProps()
  data-slot="checkbox-root"
  class=cn(
    styles.root,
    input.disabled && styles.rootDisabled,
    input.class,
  )
>
  <input type="text" ...hiddenInputProps()>
  <span
    ...api().getControlProps()
    data-slot="checkbox"
    class=styles.control
  >
    <span ...api().getIndicatorProps() data-slot="checkbox-indicator" class=styles.indicator>
      // The shared <Icon> hardcodes the lucide wrapper's stroke-width of 2;
      // this indicator has always been drawn at 3 for weight at 14px, so the
      // override rides in as a Tailwind arbitrary property on `class`
      // (a `stroke-width=` attribute would be clobbered — icon.marko spreads
      // `...rest` BEFORE its own stroke-width attribute).
      <if=api().indeterminate>
        <Icon name="Minus" class=styles.icon/>
      </if>
      <else if=api().checked>
        <Icon name="Check" class=styles.icon/>
      </else>
    </span>
  </span>
  <${input.content}/>
</label>

// Parents can receive the api getter: <Checkbox/checkboxApi/> then
// `checkboxApi().checked` — it stays live across machine updates.
<return=api>
