import { createContext, createMemo, createSignal, createUniqueId, runWithOwner, untrack, useContext } from "solid-js";
import { makeSlotRefDirective } from "../internals/make-slot-ref-directive.jsx";
import { readOption } from "../internals/maybe-accessor.jsx";
import { mergeRootProps } from "../internals/merge-root-props.jsx";
import { SNAPSHOT_KEYS, snapshotKeys } from "../internals/observability/snapshot.jsx";

/**
 * Options for {@link createCollapsible}.
 *
 * All reactive inputs accept `MaybeAccessor<T>` so a consumer can flip
 * `open` / `disabled` / `keepMounted` post-construction without
 * re-creating the primitive.
 *
 * **Solid-native shape (2026-04-30).** No internal state. `open` is
 * required and the consumer always owns the signal. The primitive is
 * a pure function from `(open, onOpenChange) → api`. Migration from
 * pre-2026-04-30: `createCollapsible({ defaultOpen: false })` becomes
 * `const [open, setOpen] = createSignal(false); createCollapsible({ open, onOpenChange: setOpen })`.
 * See `solidify.md` §6 for rationale.
 */

/**
 * Why the open state changed. Emitted on the third arg of
 * `onOpenChange(next, event, reason)` and accepted by
 * `c.setOpen(next, event?, reason?)` / `c.toggle(event?, reason?)`.
 *
 * - `"trigger-press"` — click or keyboard-activated (Enter/Space) trigger.
 * - `"beforematch"` — browser find-in-page expanded the panel (when
 *   `hiddenUntilFound` is enabled, via the wrapper's `createMountTransition`).
 * - `"imperative"` — programmatic change with no originating DOM event,
 *   or caller-supplied reason not covered by the above.
 *
 * Outer compounds that embed Collapsible (Accordion, etc.) receive this
 * as-is.
 */

/**
 * Public API returned by {@link createCollapsible}. Flat accessors and
 * setters at the root; no `state` snapshot blob (axis D).
 *
 * Stage-01c cleanup-chain (post AccordionPanel migration): `mounted`,
 * `transitionStatus`, `shouldRenderPanel`, and `_setMounted` are no longer
 * on this api — animation lifecycle lives in `createMountTransition`,
 * composed by each structural panel wrapper.
 */

/**
 * Shape of attributes emitted by `c.trigger()`.
 */

const CollapsibleContext = createContext(null);

/**
 * Read the nearest {@link CollapsibleApi} provided by `c.Provider`.
 * Throws if used outside a Provider.
 */
export function useCollapsible() {
  const ctx = useContext(CollapsibleContext);
  if (ctx == null) {
    throw new Error("Base UI: useCollapsible() must be called inside a <c.Provider>.");
  }
  return ctx;
}

/**
 * Solid 2.0-native compound primitive for collapsible disclosure. One call
 * owns logical state (open/disabled, panel id, change callbacks). Animation
 * lifecycle (mount, exit, measurement, beforematch) is NOT in this primitive
 * — wrappers compose `createMountTransition` over `c.open`.
 */
export function createCollapsible(options) {
  // `open` reads through the consumer's accessor — no internal signal,
  // no controlled-or-internal branching. Memoized so dependents see the
  // same identity within a tracking scope and so dev-time observability
  // (`name`) lines up with the rest of the api's signals.
  const open = createMemo(() => Boolean(readOption(options.open)), {
    name: "collapsible.open"
  });
  const disabled = createMemo(() => Boolean(readOption(options.disabled)), {
    name: "collapsible.disabled"
  });

  // `setOpen` / `toggle` are pure event-emitters. They fire
  // `onOpenChange(next, event, reason)`; the consumer is responsible
  // for updating their open-signal in response. There is no internal
  // state to write — `event.preventDefault()` is now a no-op for the
  // primitive (the consumer's handler is the only writer).
  const setOpen = (next, event, reason = "imperative") => {
    const ev = event ?? new Event("openchange", {
      cancelable: true
    });
    // Use runWithOwner(null, ...) to escape the current reactive owner so
    // that consumer signal writes inside onOpenChange don't trigger
    // SIGNAL_WRITE_IN_OWNED_SCOPE. The callback is an event-emitter — it
    // exits the reactive graph intentionally (same pattern as DOM event
    // handlers). untrack() alone doesn't clear the owner context.
    runWithOwner(null, () => options.onOpenChange?.(next, ev, reason));
  };
  const toggle = (event, reason = "imperative") => {
    if (untrack(disabled)) return;
    setOpen(!untrack(open), event, reason);
  };

  // Panel-id signal. Seeded with a pre-generated fallback so the trigger's
  // `aria-controls` getter has a non-undefined initial value (Solid 2's DOM
  // spread filters undefined keys out of `ownKeys` at first spread and never
  // re-enters them — see `test/internals/merge-props-undefined-attr.test.tsx`).
  const fallbackPanelId = createUniqueId();
  const [panelId, setPanelId] = createSignal(fallbackPanelId, {
    ownedWrite: true,
    name: "collapsible.panelId"
  });

  // Internal getter-bag for the trigger slot.
  const triggerInternal = {
    type: "button",
    get "aria-expanded"() {
      return open() ? "true" : "false";
    },
    get "aria-controls"() {
      // Match React (`open ? panelId : undefined`): omit `aria-controls`
      // entirely while closed so a closed collapsible never points at a
      // panel that isn't rendered — notably under SSR, where a closed
      // panel has no element in the DOM. When open, `shouldRender`
      // guarantees the panel is mounted, so the id resolves to a real
      // element. `mergeRootProps` keeps this key reactive across spreads
      // even though it can start `undefined`, so the old `""`-seed
      // undefined-attr workaround is no longer needed.
      return open() ? panelId() : undefined;
    },
    get "data-panel-open"() {
      return open() ? "" : undefined;
    },
    get "data-disabled"() {
      return disabled() ? "" : undefined;
    },
    onClick(event) {
      if (disabled()) return;
      setOpen(!untrack(open), event, "trigger-press");
    }
  };

  // Internal getter-bag for the panel slot. Only logical state attrs —
  // animation lifecycle (`hidden`, `style`, `data-{starting,ending}-style`,
  // ref) is the wrapper's `createMountTransition` instance's job.
  const panelInternal = {
    get id() {
      return panelId();
    },
    get "data-state"() {
      return open() ? "open" : "closed";
    },
    get "data-open"() {
      return open() ? "" : undefined;
    },
    get "data-closed"() {
      return open() ? undefined : "";
    }
  };
  const trigger = userProps => mergeRootProps(triggerInternal, userProps);
  const panel = userProps => mergeRootProps(panelInternal, userProps);
  const triggerRef = () => makeSlotRefDirective(() => trigger());
  const panelRef = () => makeSlotRefDirective(() => panel());
  const api = {
    open,
    disabled,
    panelId,
    setPanelId,
    toggle,
    setOpen,
    trigger,
    panel,
    triggerRef,
    panelRef,
    Provider: props => <CollapsibleContext value={api}>{props.children}</CollapsibleContext>
  };
  api[SNAPSHOT_KEYS] = COLLAPSIBLE_SNAPSHOT_KEYS;
  return api;
}
const COLLAPSIBLE_SNAPSHOT_KEYS = snapshotKeys("open", "disabled", "panelId");

/**
 * Shared directive-factory pattern used by both `c.triggerRef()` /
 * `c.panelRef()` (above) and the top-level `collapsibleTrigger(c)` /
 * `collapsiblePanel(c)` exports.
 */
//# sourceMappingURL=create-collapsible.jsx.map
