import * as carouselMachine from "@zag-js/carousel";
import type { MachineInput } from "marko-zag";
import Button from "../button/button.marko";
import Icon from "../icon/icon.marko";
import { cn } from "#lib/utils.ts";
import { stripOwnProps } from "#lib/native-attrs.ts";
import { carousel as styles } from "./classes.ts";

export interface CarouselSlideAttrs {
  content?: Marko.Body;
}

// Dual API: compound attr tags (`<@slide>`) are the primary, Marko-idiomatic
// form for static/manual slide lists; `items=` + `content` remain as sugar
// for the data-driven, generic-T 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<T = unknown> = {
  items?: T[];
  /**
   * Render prop for `items=` entries.
   *
   * The item is `T | undefined`, not `T`: a bodyless `<@slide/>` produces an
   * entry with no datum, and the render path below deliberately still invokes
   * this body for it (see the comment at the call site — the render prop owns
   * placeholder slides). Declaring it as `T` promised a value this component
   * does not always pass.
   */
  content?: Marko.Body<[T | undefined, number]>;
  slide?: Marko.AttrTag<CarouselSlideAttrs>;
  pageChange?: (page: number) => void;
};

export type Input<T = unknown> = Omit<
  MachineInput<"div", carouselMachine.Props & Own<T>>,
  "slideCount"
>;

// Normalize whichever source was supplied into one shape so the render path
// below never branches: a list of `{ content, item }` entries, where `item`
// (data-driven path only) is rendered through the shared `content` render
// prop at render time. Attr tags win if both are present (compound is
// primary per the compound-tag-dx report).
<const/slideTags=[...(input.slide ?? [])]/>
// `item` is `T | undefined`, not `unknown`: the attr-tag branch carries no
// datum, but the items= branch carries a real `T` that must survive into the
// `content` render prop's parameter. Erasing it to `unknown` made the
// `<${input.content}(slide.item, index)>` call below a type error.
<const/slides=(slideTags.length > 0 ? slideTags.map((slide) => ({ content: slide.content, item: undefined as T | undefined })) : (input.items ?? []).map((item) => ({ content: undefined as Marko.Body | undefined, item: item as T | undefined })))/>

<zag/api=() => carouselMachine from=input
  slideCount=slides.length
/>

<const/nativeAttrs=() =>
  stripOwnProps(
    carouselMachine.splitProps(input)[1],
    "class",
    "items",
    "content",
    "slide",
    "pageChange",
  )
>

<div ...nativeAttrs() ...api().getRootProps() data-slot="carousel" class=cn(styles.root, input.class)>
  // Zag's item group is a real scroll container (scroll-snap), so keyboard
  // users need it focusable to scroll it (axe: scrollable-region-focusable).
  <div
    ...api().getItemGroupProps()
    data-slot="carousel-item-group"
    tabindex="0"
    class=styles.itemGroup
  >
    <for|slide, index| of=slides>
      <div
        ...api().getItemProps({ index })
        data-slot="carousel-item"
        class=styles.item
      >
        <if=slide.content>
          <${slide.content}/>
        </if>
        // A bodyless `<@slide/>` has no `content` and an undefined `item`, so
        // when the caller also passed a `content` render prop it is invoked
        // with `undefined` here — intentional and safe: the render prop owns
        // placeholder slides, and the `${slide.item}` fallback renders nothing.
        <else if=input.content>
          <${input.content}(slide.item, index)/>
        </else>
        <else>
          ${slide.item}
        </else>
      </div>
    </for>
  </div>
  <Button type="button"
    ...api().getPrevTriggerProps()
    data-slot="carousel-prev"
    variant="outline"
    size="icon-sm"
    class=styles.prev
  >
    <Icon name="ChevronLeft" class=styles.prevIcon/>
    <span class=styles.srOnly>Previous slide</span>
  </Button>
  <Button type="button"
    ...api().getNextTriggerProps()
    data-slot="carousel-next"
    variant="outline"
    size="icon-sm"
    class=styles.next
  >
    <Icon name="ChevronRight" class=styles.nextIcon/>
    <span class=styles.srOnly>Next slide</span>
  </Button>
  <div ...api().getIndicatorGroupProps() data-slot="carousel-indicator-group" class=styles.indicatorGroup>
    <for|_, index| of=api().pageSnapPoints>
      // 24px hit box around an 8px visual dot — WCAG 2.5.8 minimum target
      // size (Lighthouse: target-size) without changing the look.
      <button type="button"
        ...api().getIndicatorProps({ index })
        data-slot="carousel-indicator"
        class=styles.indicator
      >
        <span aria-hidden="true" class=styles.indicatorDot></span>
        <span class=styles.srOnly>Go to slide ${index + 1}</span>
      </button>
    </for>
  </div>
</div>

<return=api>
