import * as tabsMachine from "@zag-js/tabs";
import type { MachineInput } from "marko-zag";
import { tabsListVariants, type TabsListVariants } from "./variants.ts";
import { cn } from "#lib/utils.ts";
import { stripOwnProps } from "#lib/native-attrs.ts";
import { tabs as styles } from "./classes.ts";

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

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

export interface TabsPanelAttrs {
  value: string;
  content?: Marko.Body;
}

// The single normalized shapes the render path consumes. Both sources
// (attr tags and `items=`) widen into these, so no narrowing is needed.
export interface NormalizedTrigger {
  value: string;
  disabled?: boolean;
  label?: string;
  content?: Marko.Body;
}

export interface NormalizedPanel {
  value: string;
  content?: Marko.Body;
}

// MachineInput = native <div> attributes ∩ the machine's full Props, with
// `id` made optional (generated below, overridable). `valueChange` is
// additive sugar enabling Marko's two-way bind shorthand
// (<Tabs value:=myState/>); Zag's own `onValueChange` also works and
// receives the details object.
//
// Dual API: compound attr tags (`<@trigger>`/`<@panel>`) are the primary,
// Marko-idiomatic form; `items=` + `content` remain as sugar for the
// data-driven case. If both are supplied, attr tags win.
// `content` SHADOWs the native `content` attribute (folded into
// MachineInput's Props argument) instead of intersecting with it.
export type Own = {
  items?: TabItem[];
  /**
   * The fallback panel content: for every panel without its own `<@panel>`
   * body, it is rendered once with THAT panel's `value` as its parameter
   * (`<Tabs|value|>…</Tabs>`), and Zag shows only the active panel, so branch
   * on the parameter to render each panel's content. It is not the currently
   * active value.
   */
  content?: Marko.Body<[string]>;
  trigger?: Marko.AttrTag<TabsTriggerAttrs>;
  panel?: Marko.AttrTag<TabsPanelAttrs>;
  valueChange?: (value: string) => void;
};

export type Input = MachineInput<"div", tabsMachine.Props & Own> & TabsListVariants;

<const/{ items, content }=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).
//
// NOTE: `trigger` and `panel` normalize INDEPENDENTLY — the "attr tags win"
// rule applies per tag name, not to the component as a whole. Mixing
// `<@trigger>` tags with `items=` (and no `<@panel>` tags) therefore yields
// attr-tag-derived triggers paired with items=-derived panels — a hybrid;
// supply both tag names (or neither) to stay on one source.
<const/triggerTags=[...(input.trigger ?? [])]/>
<const/panelTags=[...(input.panel ?? [])]/>
// Normalized to ONE shape (not a union of the two sources): `label` and
// `content` are both optional here, so the render path can read either
// without narrowing. A union of the attr-tag shape and the items= shape
// would make `.content` invalid on one arm and `.label` invalid on the
// other, which is exactly what the render path below needs to read.
<const/triggers: NormalizedTrigger[]=(triggerTags.length > 0 ? triggerTags : (items ?? []).map((item) => ({ value: item.value, disabled: item.disabled, label: item.label })))/>
<const/panels: NormalizedPanel[]=(panelTags.length > 0 ? panelTags : (items ?? []).map((item) => ({ value: item.value })))/>

// <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=() => tabsMachine from=input
  defaultValue=input.defaultValue ?? triggers[0]?.value
/>

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

<div
  ...nativeAttrs()
  ...api().getRootProps()
  data-slot="tabs"
  class=cn(styles.root, input.class)
>
  <div
    ...api().getListProps()
    data-slot="tabs-list"
    data-variant=(input.variant ?? "default")
    class=tabsListVariants({ variant: input.variant })
  >
    <for|trigger| of=triggers>
      <button type="button"
        ...api().getTriggerProps({ value: trigger.value, disabled: trigger.disabled })
        data-slot="tabs-trigger"
        class=styles.trigger
      >
        <if=trigger.content>
          <${trigger.content}/>
        </if>
        <else>
          ${trigger.label}
        </else>
      </button>
    </for>
  </div>
  <for|panel| of=panels>
    <div ...api().getContentProps({ value: panel.value }) data-slot="tabs-content" class=styles.content>
      <if=panel.content>
        <${panel.content}/>
      </if>
      <else>
        <${content}(panel.value)/>
      </else>
    </div>
  </for>
</div>

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