import * as pinInputMachine from "@zag-js/pin-input";
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 { inputOtp as styles } from "./classes.ts";

// MachineInput = native <div> attributes ∩ the machine's full Props, with
// `id` made optional (generated below, overridable). `valueChange` and
// `valueCompleteChange` are additive sugar enabling Marko's two-way bind
// shorthand; Zag's own `onValueChange`/`onValueComplete` also work and
// receive the details object.
export type Input = MachineInput<"div", pinInputMachine.Props> & {
  valueChange?: (value: string[]) => void;
  valueCompleteChange?: (value: string[]) => void;
  groupSize?: number;
  // Sugar alias for the machine's `count` prop.
  length?: number;
};

<zag/api=() => pinInputMachine from=input
  count=input.count ?? input.length
  onValueComplete(details: pinInputMachine.ValueChangeDetails) {
    input.onValueComplete?.(details);
    input.valueCompleteChange?.(details.value);
  }
/>

// Whatever is neither machine-owned (splitProps) nor component-owned
// (class, valueChange, valueCompleteChange, groupSize, length) passes
// through to the root div: aria-*, data-*, form attributes...
<const/nativeAttrs=() =>
  stripOwnProps(
    pinInputMachine.splitProps(input)[1],
    "class",
    "valueChange",
    "valueCompleteChange",
    "groupSize",
    "length",
  )
>

// Zag emits React-style camelCase `autoCapitalize`/`enterKeyHint` from
// getInputProps, which ship as literal inert attributes (autoCapitalize is
// also disallowed on type=password, html-validate input-attributes). Strip
// them; Marko does not dedupe spread attrs.
<const/slotInputProps=(index: number) => {
  const { autoCapitalize, enterKeyHint, inputMode, ...rest } = api().getInputProps({ index }) as Record<string, unknown>;
  return rest;
}/>

<div
  ...nativeAttrs()
  ...api().getRootProps()
  data-slot="input-otp"
  class=cn(styles.root, input.class)
>
  <input type="text" ...api().getHiddenInputProps()>
  <div data-slot="input-otp-group" class=styles.group>
    <for|index| of=api().items>
      <if=(input.groupSize && index > 0 && index % input.groupSize === 0)>
        // No inline `[&_svg:not([class*='size-'])]:size-N` here: every style
        // layer's rule for the separator anchor below already carries one
        // (sera 3.5, the other 7 → 4). An inline copy would shadow it on
        // BOTH paths — on the import path by equal-specificity source order,
        // and on the copy path because apply-style-map merges as
        // `twMerge(mapped, original)` so an authored utility beats the
        // injected one. Upstream's InputOTPSeparator likewise carries no
        // classes; all sizing is the style layer's job.
        <div data-slot="input-otp-separator" class=styles.separator>
          // `unsized`: the style layer sizes this icon through shadcn's
          // `[&_svg:not([class*='size-'])]` hatch, which only reaches an
          // <svg> that has NO size class. Icon's default `size-4` would
          // silently disable it (upstream's lucide-react MinusIcon emits no
          // size class, which is why the hatch works there).
          <Icon name="Minus" unsized/>
        </div>
      </if>
      <div data-slot="input-otp-slot" class=styles.slot>
        // Zag emits React-style camelCase `autoCapitalize`/`enterKeyHint`,
        // which ship as literal inert attributes (autoCapitalize is also
        // disallowed on type=password, html-validate input-attributes).
        // Strip them; Marko does not dedupe spread attrs.
        <input type="text"
          ...slotInputProps(index)
          data-slot="input-otp-input"
          class=styles.input
        />
        <div class=styles.caret>
          <div class=styles.caretLine></div>
        </div>
      </div>
    </for>
  </div>
</div>

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