import * as menu from "@zag-js/menu";
import { positionerStyle, connect } from "marko-zag";
import { cn } from "#lib/utils.ts";
import Icon from "../icon/icon.marko";
import Submenu from "./submenu.marko";
import { contextMenu as styles } from "./classes.ts";

export interface ContextMenuItem {
  type?: "item" | "separator" | "label" | "checkbox" | "radio" | "sub";
  value?: string;
  label?: string;
  disabled?: boolean;
  shortcut?: string;
  /** Upstream `variant: "default" | "destructive"`, emitted as `data-variant`. */
  variant?: "default" | "destructive";
  /** Emits `data-inset` — indents the entry to align with icon-bearing siblings. */
  inset?: boolean;
  /** Checked state for `type="checkbox"` and `type="radio"` entries. */
  checked?: boolean;
  /** Groups radio items — required for `type: "radio"` entries. */
  radioGroup?: string;
  /** `type: "radio"` entries only — this entry's value within its `radioGroup`. */
  radioValue?: string;
  /**
   * `type: "sub"` entries only — the nested submenu's own item list. One
   * level deep: a `type: "sub"` entry inside a submenu's own `subEntries` is
   * not wired (same limit as menubar's submenu).
   */
  subEntries?: ContextMenuItem[];
}

// Attr-tag form of ContextMenuItem. A SINGLE attr tag name (`item`) carries
// all five kinds (discriminated by `type`) rather than separate per-kind
// attr tags: Marko collects each attr-tag NAME into its own independent
// iterable on `input` with no shared ordering between differently-named
// siblings, so distinct tag names could not preserve call-site interleaving.
// One name with a `type` discriminant keeps document order intact by
// construction (same design as dropdown-menu.marko). `content` lets an item
// or label carry a markup body; the plain `label` string renders as the
// fallback when no body is supplied.
export interface ContextMenuItemAttrs extends ContextMenuItem {
  content?: Marko.Body;
}

export interface Input {
  content: Marko.Body;
  items?: ContextMenuItem[];
  item?: Marko.AttrTag<ContextMenuItemAttrs>;
  select?: (value: string) => void;
  class?: string;
}

// Walk-safe replacement for a `<const/{ class: className }=input/>`
// destructure + `<const/entries=...>` derivation — identical shape/rationale
// to dropdown-menu.marko's own fix (see that file's header comment for the
// full walk-order RCA and notes/bug-marko-dynamic-tag-hydration-crash.md).
// This component can be mounted via dynamic-tag-content the same way
// dropdown-menu.marko is (a `<for>`-loop-captured `<@item>` composition),
// which races any `<const>` read at walk time in the `<for>`/`<if>` below
// against its own deferred signal on a freshly-created scope. `entries`
// derives purely from `input`, so a module-level `static function` computes
// it fresh at each use site — no scope-property read to race the walk.
static function menuEntries(input: Input): ContextMenuItemAttrs[] {
  const attrItems = [...(input.item ?? [])];
  return attrItems.length > 0
    ? attrItems
    : (input.items ?? []).map((item) => ({ ...item, content: undefined as Marko.Body | undefined }));
}

<zag-machine/service=() => menu from=input
  onSelect(details: menu.SelectionDetails) {
    input.select?.(details.value);
  }
/>
<const/api=() => connect(menu, service())/>

// <zag-machine> returns the service GETTER itself, which is what each
// <Submenu> child receives so it can call `setParent` on this menu's
// service (see submenu.marko for the full wiring rationale).

<div ...api().getContextTriggerProps() data-slot="context-menu-trigger" class=styles.trigger>
  <${input.content}/>
</div>
<zag-portal>
  <if=api().open>
    <div ...api().getPositionerProps() style=positionerStyle data-slot="context-menu-positioner">
      <div
        ...api().getContentProps()
        data-slot="context-menu-content"
        class=cn(styles.content, input.class)
      >
        <for|entry, index| of=menuEntries(input)>
          <if=entry.type === "separator">
            <div ...api().getSeparatorProps() data-slot="context-menu-separator" class=styles.separator/>
          </if>
          <else if=entry.type === "label">
            <div data-slot="context-menu-label" data-inset=entry.inset || undefined class=styles.label>
              <if=entry.content>
                <${entry.content}/>
              </if>
              <else>
                ${entry.label}
              </else>
            </div>
          </else>
          <else if=entry.type === "checkbox">
            <div
              ...api().getOptionItemProps({ type: "checkbox", value: entry.value ?? `item-${index}`, checked: entry.checked ?? false, disabled: entry.disabled })
              data-slot="context-menu-checkbox-item"
              data-inset=entry.inset || undefined
              class=styles.checkboxItem
            >
              <span ...api().getItemIndicatorProps({ value: entry.value ?? `item-${index}`, checked: entry.checked ?? false }) data-slot="context-menu-checkbox-item-indicator" class=styles.itemIndicator>
                <if=entry.checked>
                  <Icon name="Check"/>
                </if>
              </span>
              <if=entry.content>
                <${entry.content}/>
              </if>
              <else>
                ${entry.label}
              </else>
              <if=entry.shortcut>
                <span data-slot="context-menu-shortcut" class=styles.shortcut>${entry.shortcut}</span>
              </if>
            </div>
          </else>
          <else if=entry.type === "radio">
            <div
              ...api().getOptionItemProps({ type: "radio", value: entry.value ?? `item-${index}`, checked: entry.checked ?? false, disabled: entry.disabled })
              data-slot="context-menu-radio-item"
              data-inset=entry.inset || undefined
              class=styles.radioItem
            >
              <span ...api().getItemIndicatorProps({ value: entry.value ?? `item-${index}`, checked: entry.checked ?? false }) data-slot="context-menu-radio-item-indicator" class=styles.itemIndicator>
                <if=entry.checked>
                  <Icon name="Circle" class=styles.radioItemIcon/>
                </if>
              </span>
              <if=entry.content>
                <${entry.content}/>
              </if>
              <else>
                ${entry.label}
              </else>
              <if=entry.shortcut>
                <span data-slot="context-menu-shortcut" class=styles.shortcut>${entry.shortcut}</span>
              </if>
            </div>
          </else>
          <else if=entry.type === "sub">
            <Submenu
              entries=entry.subEntries ?? []
              label=entry.label
              disabled=entry.disabled
              inset=entry.inset
              parent=service
              parentApi=() => api()
              onChildReady=(childService) => api().setChild(childService)
            >
              <@trigger|triggerProps|>
                <div
                  ...triggerProps
                  data-slot="context-menu-sub-trigger"
                  data-inset=entry.inset || undefined
                  class=styles.subTrigger
                >
                  ${entry.label}
                  <Icon name="ChevronRight" class=styles.subTriggerIcon/>
                </div>
              </@trigger>
            </Submenu>
          </else>
          <else>
            <div
              ...api().getItemProps({ value: entry.value ?? `item-${index}`, disabled: entry.disabled })
              data-slot="context-menu-item"
              data-inset=entry.inset || undefined
              data-variant=entry.variant ?? "default"
              class=styles.item
            >
              <if=entry.content>
                <${entry.content}/>
              </if>
              <else>
                ${entry.label}
              </else>
              <if=entry.shortcut>
                <span data-slot="context-menu-shortcut" class=styles.shortcut>${entry.shortcut}</span>
              </if>
            </div>
          </else>
        </for>
      </div>
    </div>
  </if>
</zag-portal>

<return=api>
