import { dynamic } from "@solidjs/web";
import { createRenderEffect, omit, untrack } from "solid-js";
import { useCollapsible } from "../../collapsible/create-collapsible.jsx";
import { ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, END, HOME } from "../../internals/composite/composite-constants.jsx";
import { isElementDisabled } from "../../internals/dom/is-element-disabled.jsx";
import { mergeRootProps } from "../../internals/merge-root-props.jsx";
import { resolveClassStyle } from "../../internals/resolve-class-style.jsx";
import { useButton } from "../../internals/use-button/use-button.jsx";
import { useAccordionItemContext } from "../accordion-item-context.jsx";
import { useAccordionRootContext } from "../accordion-root-context.jsx";
const SUPPORTED_KEYS = new Set([ARROW_DOWN, ARROW_UP, ARROW_RIGHT, ARROW_LEFT, HOME, END]);

/**
 * Walk the accordion's registered items and collect the trigger element from
 * each non-disabled item. Mirrors Base UI React's `getActiveTriggers`.
 *
 * Shadow-DOM-safe DOM traversal via `isElementDisabled` from
 * `src/internals/dom/` (the shadow-DOM-safe helper). `querySelector` is used
 * to find the trigger within each item — we target `[type="button"]` and
 * `[role="button"]` so both native and non-native triggers work.
 */
function getActiveTriggers(accordionItemRefs) {
  const output = [];
  for (const section of accordionItemRefs) {
    if (!section || isElementDisabled(section)) continue;
    const trigger = section.querySelector('[type="button"], [role="button"]');
    if (trigger && !isElementDisabled(trigger)) {
      output.push(trigger);
    }
  }
  return output;
}

/**
 * Button that expands or collapses its item's panel. Renders a `<button>` by
 * default; use `nativeButton={false}` with a `render` prop override to render
 * a non-button element.
 *
 * Solid 2 notes:
 *
 * - The keyboard handler walks `root.accordionItemRefs` (a plain array kept
 *   in DOM order by `<CompositeList>`) and builds a "live" list of sibling
 *   triggers on every key press. This matches React's approach — no
 *   pre-computed ref tracking, so re-ordering is handled naturally.
 * - `aria-controls` is gated on `open` (matching React) — the getter returns
 *   `undefined` while closed so the attribute is omitted (not `aria-controls=""`
 *   pointing at a non-existent panel). `id` uses a `""` seed so the key stays
 *   in `ownKeys` from the first spread. `mergeRootProps` declares every getter
 *   key as `own` regardless of value, so a getter starting `undefined` still
 *   tracks (see `test/internals/merge-props-undefined-attr.test.tsx`).
 * - `aria-expanded` emits the literal `"true"`/`"false"` string — RFC 07's
 *   enumerated-pseudo-boolean rules collapse raw `true` to a presence
 *   attribute, which breaks AT that keys off the literal string.
 * - The consumer-supplied `id` registers as the item's `triggerId` so the
 *   panel's `aria-labelledby` can point to it. We also use it as the
 *   trigger's own `id` (so the trigger and panel form a two-way ARIA link).
 *   Registration happens in a `createRenderEffect` and is cleaned up on
 *   unmount — matches React's `useIsoLayoutEffect`.
 */
export function AccordionTrigger(props) {
  const collapsible = useCollapsible();
  const root = useAccordionRootContext();
  const item = useAccordionItemContext();
  const disabled = () => {
    const own = props.disabled;
    if (own !== undefined) return Boolean(own);
    return collapsible.disabled();
  };
  const nativeButton = () => props.nativeButton ?? true;
  const {
    props: buttonProps,
    ref: buttonRef
  } = useButton({
    disabled,
    focusableWhenDisabled: () => true,
    native: nativeButton,
    composite: () => true
  });

  // React reads `idProp` at setup and registers on every change; our effect
  // mirrors that. We only register a consumer-supplied id — the
  // auto-generated fallback stays registered by the item (via the initial
  // `createSignal(useBaseUiId())`). The 2-arg form (compute, apply) is what
  // `@solidjs/signals` v2 expects — see every `createRenderEffect` call in
  // `src/collapsible/` and `src/internals/use-transition-status/`.
  createRenderEffect(() => typeof props.id === "string" ? props.id : undefined, idProp => {
    if (!idProp) return undefined;
    item.setTriggerId(idProp);
    return () => {
      // Only clear if we're still the registered id — matches the
      // collapsible panel's cleanup guard. A later re-registration
      // (e.g. the root's `get children()` re-evaluating) must not
      // clobber the fresh write.
      if (untrack(item.triggerId) === idProp) {
        item.setTriggerId(undefined);
      }
    };
  });
  const handleClick = event => {
    if (disabled()) return;
    collapsible.setOpen(!collapsible.open(), event, "trigger-press");
  };
  const handleKeyDown = event => {
    if (!SUPPORTED_KEYS.has(event.key)) return;
    event.preventDefault();
    event.stopPropagation();
    const triggers = getActiveTriggers(root.accordionItemRefs);
    const numEnabled = triggers.length;
    const lastIndex = numEnabled - 1;
    const thisIndex = triggers.indexOf(event.currentTarget);
    const loopFocus = root.loopFocus();
    const isHorizontal = root.orientation() === "horizontal";
    const isRtl = root.direction() === "rtl";
    let nextIndex = -1;
    const toNext = () => {
      if (loopFocus) {
        nextIndex = thisIndex + 1 > lastIndex ? 0 : thisIndex + 1;
      } else {
        nextIndex = Math.min(thisIndex + 1, lastIndex);
      }
    };
    const toPrev = () => {
      if (loopFocus) {
        nextIndex = thisIndex === 0 ? lastIndex : thisIndex - 1;
      } else {
        nextIndex = thisIndex - 1;
      }
    };
    switch (event.key) {
      case ARROW_DOWN:
        if (!isHorizontal) toNext();
        break;
      case ARROW_UP:
        if (!isHorizontal) toPrev();
        break;
      case ARROW_RIGHT:
        if (isHorizontal) (isRtl ? toPrev : toNext)();
        break;
      case ARROW_LEFT:
        if (isHorizontal) (isRtl ? toNext : toPrev)();
        break;
      case HOME:
        nextIndex = 0;
        break;
      case END:
        nextIndex = lastIndex;
        break;
      default:
        break;
    }
    if (nextIndex > -1 && nextIndex <= lastIndex) {
      triggers[nextIndex]?.focus();
    }
  };
  const userStyling = resolveClassStyle(props, () => item.state());
  const userHtmlProps = omit(props, "class", "style", "attrs", "as", "disabled", "nativeButton");
  const internalProps = {
    ref: buttonRef,
    tabIndex: 0,
    onClick: handleClick,
    onKeyDown: handleKeyDown,
    get "aria-controls"() {
      // Match React: omit `aria-controls` entirely (not `""`) while closed,
      // so it never points at a panel that isn't rendered. `mergeRootProps`
      // keeps the key reactive across spreads even though it can start
      // `undefined`.
      return collapsible.open() ? collapsible.panelId() : undefined;
    },
    get "aria-expanded"() {
      return collapsible.open() ? "true" : "false";
    },
    get id() {
      return item.triggerId() ?? "";
    },
    get "data-panel-open"() {
      return collapsible.open() ? "" : undefined;
    },
    get "data-disabled"() {
      return disabled() ? "" : undefined;
    },
    get "data-index"() {
      const i = item.state().index;
      return Number.isInteger(i) ? String(i) : undefined;
    }
  };
  const styledUserProps = mergeRootProps(userStyling, userHtmlProps);
  const merged = mergeRootProps(mergeRootProps(internalProps, buttonProps), styledUserProps);
  const Tag = dynamic(() => props.as ?? "button");
  return <Tag {...merged} />;
}
//# sourceMappingURL=accordion-trigger.jsx.map
