import * as dateInputMachine from "@zag-js/date-input";
import { parse } from "@zag-js/date-input";
import type { MachineInput } from "marko-zag";
import { cn } from "#lib/utils.ts";
import { stripOwnProps } from "#lib/native-attrs.ts";
import { dateInput as styles } from "./classes.ts";

// `value`/`placeholderValue` (and their `xxxChange` sugar) use plain ISO
// strings instead of Zag's DateValue so consumers don't need to depend on
// @internationalized/date directly — mirrors the date-picker component.
export type Input = Omit<
  MachineInput<"div", dateInputMachine.Props>,
  "value" | "defaultValue" | "placeholderValue" | "defaultPlaceholderValue"
> & {
  value?: string[];
  defaultValue?: string[];
  placeholderValue?: string;
  defaultPlaceholderValue?: string;
  valueChange?: (value: string[]) => void;
  placeholderValueChange?: (value: string) => void;
  focusChange?: (focused: boolean) => void;
};

<zag/api=() => dateInputMachine from=input props=(picked) => ({
  ...picked,
  value: input.value ? parse(input.value) : undefined,
  defaultValue: input.defaultValue ? parse(input.defaultValue) : undefined,
  placeholderValue: input.placeholderValue ? parse(input.placeholderValue) : undefined,
  defaultPlaceholderValue: input.defaultPlaceholderValue ? parse(input.defaultPlaceholderValue) : undefined,
})
  onValueChange(details: dateInputMachine.ValueChangeDetails) {
    input.onValueChange?.(details);
    input.valueChange?.(details.value.map((date) => date.toString()));
  }
  onPlaceholderChange(details: dateInputMachine.PlaceholderChangeDetails) {
    input.onPlaceholderChange?.(details);
    input.placeholderValueChange?.(details.placeholderValue.toString());
  }
  onFocusChange(details: dateInputMachine.FocusChangeDetails) {
    input.onFocusChange?.(details);
    input.focusChange?.(details.focused);
  }
/>

// Number of date groups: 2 for range selection, 1 otherwise.
<const/groupCount=input.selectionMode === "range" ? 2 : 1>

// Whatever is neither machine-owned (splitProps) nor component-owned
// (class, valueChange, placeholderValueChange, focusChange) passes through
// to the root div: aria-*, data-*, form attributes...
// The four date props are deliberately remapped to ISO strings on this
// component's public Input (see the note at the top), so `input` does not
// satisfy `splitProps`'s `Partial<DateInputProps>` constraint. They are
// machine-owned keys, so `splitProps` would strip them from the leftover
// anyway — drop them first and the call type-checks on the keys it actually
// partitions. `parse()` above is what feeds the real DateValues to the machine.
<const/splitSource=() => {
  const { value, defaultValue, placeholderValue, defaultPlaceholderValue, ...rest } = input;
  return rest;
}/>
<const/nativeAttrs=() =>
  stripOwnProps(
    dateInputMachine.splitProps(splitSource())[1],
    "class",
    "valueChange",
    "placeholderValueChange",
    "focusChange",
  )
>

<div
  ...nativeAttrs()
  ...api().getRootProps()
  data-slot="date-input"
  class=cn(styles.root, input.class)
>
  <div
    ...api().getControlProps()
    data-slot="date-input-control"
    class=cn(
      styles.control,
      styles.controlFocus,
      styles.controlInvalid,
      styles.controlDisabled,
    )
  >
    <for|groupIndex| of=Array.from({ length: groupCount }, (_, i) => i)>
      <if=(groupCount > 1 && groupIndex > 0)>
        <span data-slot="date-input-range-separator" class=styles.rangeSeparator>
          –
        </span>
      </if>
      // Zag points the label's `for` at the hidden <input type="text">, but a type=hidden
      // input is not a labelable element (html-validate valid-for). The
      // visible control is the segment group, which ARIA names with
      // aria-labelledby instead — so the label drops `for` and the group
      // references the label id. Marko does not dedupe spread attrs.
      <const/groupLabelProps=(index: number) => {
        const { for: _for, ...rest } = api().getLabelProps({ index }) as Record<string, unknown>;
        return rest;
      }/>
      <label ...groupLabelProps(groupIndex) class=styles.label>
        ${groupCount > 1 ? (groupIndex === 0 ? "Start date" : "End date") : "Date"}
      </label>
      <div
        ...api().getSegmentGroupProps({ index: groupIndex })
        aria-labelledby=api().getLabelProps({ index: groupIndex }).id
        data-slot="date-input-segment-group"
        class=styles.segmentGroup
      >
        <for|segment| of=api().getSegments({ index: groupIndex })>
          <span
            ...api().getSegmentProps({ segment, index: groupIndex })
            data-slot="date-input-segment"
            class=cn(
              styles.segment,
              styles.segmentPlaceholder,
              styles.segmentEditable,
            )
          >
            ${segment.isPlaceholder ? segment.placeholder : segment.text}
          </span>
        </for>
      </div>
      <input type="text" ...api().getHiddenInputProps({ index: groupIndex })>
    </for>
  </div>
</div>

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