import * as radioGroupMachine from "@zag-js/radio-group";
import type { MachineInput } from "marko-zag";
import { cn } from "#lib/utils.ts";
import { stripOwnProps } from "#lib/native-attrs.ts";
import { radioGroup as styles } from "./classes.ts";

export interface RadioGroupItem {
  value: string;
  label: string;
  disabled?: boolean;
}

export interface RadioGroupItemAttrs {
  value: string;
  disabled?: boolean;
  content?: Marko.Body;
}

// The root is a native <div>; `value` is overridden to drop the native
// string-attribute type in favor of the machine's `string | null`.
// `valueChange` is additive sugar enabling Marko's two-way bind shorthand
// (<RadioGroup value:=myState/>); Zag's own `onValueChange` also works and
// receives the details object.
//
// Dual API: compound attr tags (`<@item>`) are the primary, Marko-idiomatic
// form; `items=` remains as sugar for the data-driven case. If both are
// supplied, attr tags win.
export type Input = Omit<MachineInput<"div", radioGroupMachine.Props>, "value"> & {
  items?: RadioGroupItem[];
  item?: Marko.AttrTag<RadioGroupItemAttrs>;
  value?: string | null;
  valueChange?: (value: string | null) => void;
};

<const/{ items }=input/>

// Normalize whichever source was supplied into one shape so the render path
// below never branches. Attr tags win if both are present (compound is
// primary per the compound-tag-dx report).
<const/itemTags=[...(input.item ?? [])].map((item) => ({ value: item.value, disabled: item.disabled, label: undefined as string | undefined, content: item.content }))/>
<const/normalizedItems=(itemTags.length > 0 ? itemTags : (items ?? []).map((item) => ({ value: item.value, disabled: item.disabled, label: item.label as string | undefined, content: undefined as Marko.Body | undefined })))/>

// <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=() => radioGroupMachine from=input/>

// Whatever is neither machine-owned (splitProps) nor component-owned
// (class, items, valueChange) passes through to the root div.
<const/nativeAttrs=() =>
  stripOwnProps(
    radioGroupMachine.splitProps(input)[1],
    "class",
    "items",
    "item",
    "valueChange",
  )
>

// Item anatomy: a <div> row carries Zag's item props (pointer handlers + data
// attrs). Zag emits `for` from getItemProps because upstream wraps the item in
// a <label>, but this wrapper is a div (item content can be block markup, which
// is not permitted inside a label) — drop it. The association instead:
//   - the hidden input is the labelable control and names itself via
//     aria-labelledby -> the item text part id (getItemTextProps),
//   - a stretched, aria-hidden <label for=hidden-input-id> spans the whole row
//     so clicking anywhere (control, text, or card content) forwards to the
//     input. html-validate: block content in a sibling div is permitted.
<const/itemRootProps=(value: string, disabled: boolean | undefined) => {
  const props = api().getItemProps({ value, disabled }) as Record<string, unknown>;
  delete props.for;
  return props;
}/>
<const/itemHitProps=(value: string, disabled: boolean | undefined) => {
  const hidden = api().getItemHiddenInputProps({ value, disabled }) as Record<string, unknown>;
  return { for: hidden.id as string };
}/>

<div ...nativeAttrs() ...api().getRootProps() data-slot="radio-group" class=cn(styles.root, input.class)>
  <for|item| of=normalizedItems by=(item) => item.value>
    <div
      ...itemRootProps(item.value, item.disabled)
      data-slot="radio-group-item-wrapper"
      class=styles.itemWrapper
    >
      <span
        ...api().getItemControlProps({ value: item.value, disabled: item.disabled })
        data-slot="radio-group-item"
        class=styles.item
      >
        <if=api().getItemState({ value: item.value, disabled: item.disabled }).checked>
          <span
            data-slot="radio-group-indicator"
            class=styles.indicator
          >
            <span class=styles.indicatorIcon/>
          </span>
        </if>
      </span>
      <input type="text" ...api().getItemHiddenInputProps({ value: item.value, disabled: item.disabled })>
      <if=item.content>
        <div
          ...api().getItemTextProps({ value: item.value, disabled: item.disabled })
          data-slot="radio-group-item-text"
          class=styles.itemText
        >
          <${item.content}/>
        </div>
      </if>
      <else>
        <span
          ...api().getItemTextProps({ value: item.value, disabled: item.disabled })
          data-slot="radio-group-item-text"
          class=styles.itemText
        >
          ${item.label}
        </span>
      </else>
      <label
        ...itemHitProps(item.value, item.disabled)
        data-slot="radio-group-item-hit"
        class=styles.itemHit
        aria-hidden="true"
      />
    </div>
  </for>
</div>

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