import { createEffect, createMemo, createSignal, onCleanup, untrack } from "solid-js";
import { createChangeEventDetails, REASONS } from "../../internals/change-event-details.jsx";
import { createControlledState } from "../../internals/create-controlled-state.jsx";
import { FloatingTree, useFloatingParentNodeId } from "../../internals/floating-ui/components/floating-tree.jsx";
import { useDismiss } from "../../internals/floating-ui/hooks/use-dismiss.jsx";
import { useFloatingRootContext } from "../../internals/floating-ui/hooks/use-floating-root-context.jsx";
import { useInteractions } from "../../internals/floating-ui/hooks/use-interactions.jsx";
import { useRole } from "../../internals/floating-ui/hooks/use-role.jsx";
import { readOption } from "../../internals/maybe-accessor.jsx";
import { useOpenChangeComplete } from "../../internals/popups/use-open-change-complete.jsx";
import { useTransitionStatus } from "../../internals/use-transition-status/use-transition-status.jsx";
import { createPopover } from "../create-popover.jsx";
import { PopoverRootContext, usePopoverRootContext } from "../popover-root-context.jsx";
import { PopoverStore } from "../store/popover-store.jsx";

/**
 * Imperative actions surface written into `actionsRef` on `<Popover.Root>`.
 * Mirrors React's `PopoverRoot.Actions`.
 */

/**
 * Render-function variant for popover children — mirrors React's
 * `PayloadChildRenderFunction`. Called with the active trigger's
 * `payload`.
 */

/**
 * Groups all parts of the popover. Doesn't render its own HTML element —
 * the root is a pure context provider.
 */
function PopoverRootComponent(props) {
  // Adopt the handle's store if supplied; otherwise own one.
  const store = props.handle?.store ?? new PopoverStore();

  // Handle-owned internal signal. When `handle` is provided and the
  // consumer does not supply `open` / `onOpenChange`, the handle's
  // imperative calls write here so the popover actually opens/closes.
  // Controlled / uncontrolled open state (Base UI `open` / `defaultOpen`
  // contract): the `open` prop wins while its resolved value is defined;
  // otherwise the internal signal (seeded once from `defaultOpen`, also
  // written by handles) backs the state.
  const [resolvedOpen, setUncontrolledOpen] = createControlledState({
    controlled: () => {
      const controlled = readOption(props.open);
      return controlled === undefined ? undefined : Boolean(controlled);
    },
    default: () => readOption(props.defaultOpen),
    fallback: false,
    name: "<Popover.Root>",
    state: "open"
  });

  // Compose the headless Popover primitive — owns open (pass-through),
  // disabled, modal. The wrapper layers floating-UI, focus trap, portal,
  // transitions, dismiss, trigger registry, handle, and actionsRef on top.
  // onOpenChange is NOT wired here — the wrapper's own setOpen (below) is
  // the sole writer so it can thread reason/event details through the full
  // floating-UI pipeline.
  const popoverPrimitive = createPopover({
    open: resolvedOpen,
    modal: () => readOption(props.modal) ?? false
  });
  const open = popoverPrimitive.open;
  const modal = popoverPrimitive.modal;

  // Mount / transition machinery.
  const {
    mounted,
    transitionStatus,
    setMounted
  } = useTransitionStatus(open, () => true, () => false);

  // Defer unmount until any CSS exit animation on the popup has finished.
  // Mirrors the tabs-panel `useOpenChangeComplete` pattern.
  // Previously the popover never called `setMounted(false)` at
  // all — `mounted` flipped to `true` on first open and stayed there, so
  // the popup subtree effectively lived forever. Threading
  // `useOpenChangeComplete` here introduces the missing call: when `open`
  // flips false the hook waits for `Animation.finished` on the popup ref
  // (populated by `<Popover.Popup>` via `popupRef.current`), then drops
  // `mounted`. The hook is a no-op when `getAnimations()` returns an empty
  // list, so the static case closes on the next microtask.
  const popupRef = {
    current: null
  };
  useOpenChangeComplete({
    open,
    ref: popupRef,
    onComplete: () => {
      if (!open()) {
        setMounted(false);
      }
    }
  });

  // Fire onOpenChangeComplete after the transition settles (or immediately
  // when a transition isn't present in happy-dom). Mirrors React's
  // `useOpenChangeComplete`.
  createEffect(() => ({
    open: open(),
    status: transitionStatus()
  }), ({
    open: o,
    status
  }) => {
    if (status === undefined) {
      props.onOpenChangeComplete?.(o);
    }
  });

  // Title / description ids.
  const [titleId, setTitleId] = createSignal(undefined, {
    ownedWrite: true
  });
  const [descriptionId, setDescriptionId] = createSignal(undefined, {
    ownedWrite: true
  });

  // Active trigger id / element. Controlled via `triggerId`.
  const [activeTriggerIdInternal, setActiveTriggerIdInternal] = createSignal(null, {
    ownedWrite: true
  });
  const activeTriggerId = createMemo(() => {
    // `triggerId === undefined` → uncontrolled; fall back to internal.
    const controlled = props.triggerId;
    if (controlled !== undefined) {
      return controlled;
    }
    return activeTriggerIdInternal();
  });
  const setActiveTriggerId = id => {
    setActiveTriggerIdInternal(id);
  };

  // Active trigger element — matches React's `activeTriggerElement` slot.
  // When a `triggerId` prop is controlled, we look it up through the
  // store's trigger registry each time `activeTriggerId` changes.
  const [triggerElement, setTriggerElementInternal] = createSignal(null, {
    ownedWrite: true
  });
  const setTriggerElement = el => {
    setTriggerElementInternal(el);
  };

  // Sync element lookup when the controlled `triggerId` changes.
  createEffect(() => activeTriggerId(), id => {
    if (id === null) return;
    const el = store.context.triggerElements.getById(id);
    if (el) setTriggerElementInternal(el);
  });

  // Payload — written by the active trigger.
  const [payload, setPayload] = createSignal(undefined, {
    ownedWrite: true
  });

  // `setOpen` mirrors Base UI's `PopoverStore.setOpen`: fire
  // `props.onOpenChange`, honor `details.cancel()`, then write the
  // uncontrolled state (ignored for reads while the `open` prop is
  // controlled — the consumer's signal write is the source of truth).
  const setOpen = (next, details) => {
    props.onOpenChange?.(next, details);
    if (details.isCanceled) return;
    setUncontrolledOpen(next);
    // Mirror to store trigger tracking.
    if (details.trigger?.id) {
      setActiveTriggerIdInternal(details.trigger.id);
      setTriggerElementInternal(details.trigger);
    }
  };

  // Floating-UI integration.
  const floatingRootContext = useFloatingRootContext({
    open: untrack(open),
    onOpenChange: (nextOpen, details) => {
      setOpen(nextOpen, details);
    }
  });
  createEffect(() => ({
    openValue: open(),
    trigger: triggerElement(),
    // Capture transitionStatus in compute (snapshot pattern) so the
    // apply scope doesn't trip `[STRICT_READ_UNTRACKED]`.
    status: transitionStatus()
  }), ({
    openValue,
    trigger,
    status
  }) => untrack(() => {
    floatingRootContext.update({
      open: openValue,
      referenceElement: trigger,
      domReferenceElement: trigger,
      transitionStatus: status
    });
  }));

  // `floatingRootContext.update` does internal reactive reads (writes to
  // store fields read prev values for equality). Untrack the apply body
  // so those reads don't trip `[STRICT_READ_UNTRACKED]`.
  createEffect(() => transitionStatus(), status => untrack(() => {
    floatingRootContext.update({
      transitionStatus: status
    });
  }));

  // ------------------------------------------------------------------
  // Interaction hooks
  // ------------------------------------------------------------------
  const dismiss = useDismiss(floatingRootContext, {
    outsidePressEvent: {
      get mouse() {
        return modal() === "trap-focus" ? "sloppy" : "intentional";
      },
      touch: "sloppy"
    }
  });
  const role = useRole(floatingRootContext, {
    role: "dialog"
  });
  const interactions = useInteractions([dismiss, role]);

  // Wire the store's imperative setOpen.
  store.attachSetOpen((next, details) => {
    setOpen(next, details);
  });

  // Keep the store's open mirror in sync for synchronous `handle.isOpen`
  // reads.
  createEffect(() => open(), o => {
    store.setOpenMirror(o);
  });
  onCleanup(() => {
    store.attachSetOpen(null);
  });

  // Implicit active-trigger selection: when popover opens and no explicit
  // trigger id has been set, pick the sole registered trigger. The reads
  // of `activeTriggerId()` and the trigger registry are snapshots taken
  // during the open transition; `untrack` keeps them quiet under the
  // effect-apply scope.
  createEffect(() => open(), o => untrack(() => {
    if (!o) return;
    if (activeTriggerId() !== null) return;
    if (store.context.triggerElements.size !== 1) return;
    const entry = store.context.triggerElements.entries().next();
    if (!entry.done) {
      const [id, el] = entry.value;
      setActiveTriggerIdInternal(id);
      setTriggerElementInternal(el);
    }
  }));

  // actionsRef wiring.
  const makeImperativeDetails = () => createChangeEventDetails(REASONS.imperativeAction);
  const actions = {
    close: () => setOpen(false, makeImperativeDetails()),
    unmount: () => setOpen(false, makeImperativeDetails())
  };
  const ref = props.actionsRef;
  if (typeof ref === "function") {
    ref(actions);
    onCleanup(() => ref(null));
  } else if (ref && typeof ref === "object") {
    ref.current = actions;
    onCleanup(() => {
      ref.current = null;
    });
  }
  const contextValue = {
    open,
    mounted,
    transitionStatus,
    modal,
    titleId,
    setTitleId,
    descriptionId,
    setDescriptionId,
    floatingRootContext,
    interactions,
    setOpen,
    popupRef,
    triggerElement,
    setTriggerElement,
    store: store,
    activeTriggerId,
    setActiveTriggerId,
    payload,
    setPayload
  };

  // Resolve children — render function form receives `{ payload }`.
  const resolveChildren = () => {
    const ch = props.children;
    if (typeof ch === "function") {
      return ch({
        payload: payload()
      });
    }
    return ch;
  };
  return <PopoverRootContext value={contextValue}>
			{resolveChildren()}
		</PopoverRootContext>;
}

/**
 * Groups all parts of the popover.
 */
export function PopoverRoot(props) {
  if (usePopoverRootContext(true) != null || useFloatingParentNodeId() != null) {
    return <PopoverRootComponent {...props} />;
  }
  return <FloatingTree>
			<PopoverRootComponent {...props} />
		</FloatingTree>;
}
//# sourceMappingURL=popover-root.jsx.map
