import { createEffect, createMemo, createSignal, flush, 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 { readOption } from "../../internals/maybe-accessor.jsx";
import { useOpenChangeComplete } from "../../internals/popups/use-open-change-complete.jsx";
import { useIsoLayoutEffect } from "../../internals/use-iso-layout-effect.jsx";
import { useTransitionStatus } from "../../internals/use-transition-status/use-transition-status.jsx";
import { CLOSE_DELAY } from "../constants.jsx";
import { createPreviewCard } from "../create-preview-card.jsx";
import { PreviewCardRootContext, usePreviewCardRootContext } from "../preview-card-root-context.jsx";
import { PreviewCardStore } from "../store/preview-card-store.jsx";

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

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

/**
 * Inner implementation — split so we can wrap the tree in `<FloatingTree>`
 * only when we're the top-most root (matches React's
 * `PreviewCardRoot` → `PreviewCardRootComponent` shape).
 */
function PreviewCardRootComponent(props) {
  // Adopt the handle's store if supplied; otherwise own one.
  const store = props.handle?.store ?? new PreviewCardStore();

  // Controlled / uncontrolled open state (Base UI `open` / `defaultOpen`
  // contract): the `open` prop wins while its resolved value is defined;
  // otherwise an internal signal (seeded once from `defaultOpen`) backs
  // the state and the root's `setOpen` (below) writes it.
  const [resolvedOpen, setUncontrolledOpen] = createControlledState({
    controlled: () => {
      const controlled = readOption(props.open);
      return controlled === undefined ? undefined : Boolean(controlled);
    },
    default: () => readOption(props.defaultOpen),
    fallback: false,
    name: "<PreviewCard.Root>",
    state: "open"
  });

  // Compose the headless PreviewCard primitive — owns open (pass-through) and
  // disabled. The wrapper layers floating-UI, transitions, dismiss, portal,
  // and per-trigger hover delays on top.
  //
  // onOpenChange is NOT wired here — the wrapper's own setOpen (below) is
  // the sole writer so it can thread the full change-event details through
  // the floating-UI pipeline. The primitive's setOpen is unused in the
  // wrapper path.
  const previewCardPrimitive = createPreviewCard({
    open: resolvedOpen
  });
  const open = previewCardPrimitive.open;
  const {
    mounted,
    transitionStatus,
    setMounted
  } = useTransitionStatus(open, () => true, () => false);

  // Popup element tracking — the ref mirror is consumed by
  // `useOpenChangeComplete` so a pending CSS exit animation can hold the
  // popup in the DOM until it settles.
  const popupRef = {
    current: null
  };
  const [popupElement, setPopupElementInternal] = createSignal(null, {
    ownedWrite: true
  });
  const setPopupElement = el => {
    popupRef.current = el;
    setPopupElementInternal(el);
  };
  useOpenChangeComplete({
    open,
    ref: popupRef,
    onComplete: () => {
      if (!open()) {
        setMounted(false);
      }
    }
  });

  // Fire onOpenChangeComplete after the transition settles (or immediately
  // in happy-dom where `Animation.finished` resolves on the next microtask).
  createEffect(() => ({
    open: open(),
    status: transitionStatus()
  }), ({
    open: o,
    status
  }) => {
    if (status === undefined) {
      props.onOpenChangeComplete?.(o);
    }
  });

  // instantType — emitted as `data-instant` on popup/arrow when focus or
  // dismiss drives the open/close transition.
  const [instantType, setInstantType] = createSignal(undefined, {
    ownedWrite: true
  });

  // Active trigger id / element. `triggerId` is a controlled override.
  const [activeTriggerIdInternal, setActiveTriggerIdInternal] = createSignal(null, {
    ownedWrite: true
  });
  const activeTriggerId = createMemo(() => {
    const controlled = props.triggerId;
    if (controlled !== undefined) return controlled;
    return activeTriggerIdInternal();
  });
  const setActiveTriggerId = id => {
    setActiveTriggerIdInternal(id);
  };
  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);
  });
  const [payload, setPayload] = createSignal(undefined, {
    ownedWrite: true
  });

  // Clear payload when the card is open but no active trigger is set (React
  // source mirrors this: if `activeTriggerId == null && open`, payload is
  // reset so the render-function child doesn't leak stale data).
  createEffect(() => ({
    open: open(),
    id: activeTriggerId()
  }), ({
    open: o,
    id
  }) => {
    if (o && id == null) {
      setPayload(undefined);
    }
  });

  // Written by the active trigger; reserved for floating-hover close timing.
  const closeDelayRef = {
    current: CLOSE_DELAY
  };

  // Floating-UI shared store.
  const floatingRootContext = useFloatingRootContext({
    open: untrack(() => open()),
    onOpenChange: (nextOpen, details) => {
      setOpen(nextOpen, details);
    }
  });

  // `floatingRootContext.sync` / `.update` may read internal store state
  // for prev-value equality. Wrap each apply in `untrack` to keep
  // `[STRICT_READ_UNTRACKED]` quiet.
  createEffect(() => open(), next => untrack(() => {
    floatingRootContext.sync("open", next);
  }));
  // Sync reference / floating nodes into the floating store on the same
  // turn as the ref callback (layout effect) so `useHover` can arm
  // listeners before `mouseenter` is lost to microtask batching.
  useIsoLayoutEffect(() => triggerElement(), el => untrack(() => {
    floatingRootContext.sync("domReferenceElement", el);
    floatingRootContext.sync("referenceElement", el);
  }));
  useIsoLayoutEffect(() => popupElement(), el => untrack(() => {
    floatingRootContext.sync("floatingElement", el);
  }));
  createEffect(() => transitionStatus(), status => untrack(() => {
    floatingRootContext.update({
      transitionStatus: status
    });
  }));

  // setOpen — threads instantType + active-trigger bookkeeping off the
  // change-event reason, the same way the React store does.
  const setOpen = (next, details) => {
    const reason = details.reason;
    const isFocusOpen = next && reason === REASONS.triggerFocus;
    const isDismissClose = !next && (reason === REASONS.triggerPress || reason === REASONS.escapeKey || reason === REASONS.outsidePress);
    if (isFocusOpen) {
      setInstantType("focus");
    } else if (isDismissClose) {
      setInstantType("dismiss");
    } else if (reason === REASONS.triggerHover) {
      setInstantType(undefined);
    }

    // Base UI change flow (`PreviewCardStore.setOpen`): fire
    // `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).
    props.onOpenChange?.(next, details);
    if (details.isCanceled) return;
    setUncontrolledOpen(next);

    // Keep the floating store's `open` slot in sync immediately so hover
    // handlers reading `store.state.open` and the positioner see the new
    // value in the same turn (Solid 2 batches signal writes by default).
    untrack(() => {
      floatingRootContext.sync("open", next);
    });
    flush();
    if (details.trigger && details.trigger.id) {
      const id = details.trigger.id;
      setActiveTriggerIdInternal(id);
      setTriggerElementInternal(details.trigger);
    }
  };

  // Wire the store imperative bridge.
  store.attachSetOpen((next, details) => {
    setOpen(next, details);
  });
  createEffect(() => open(), o => store.setOpenMirror(o));
  onCleanup(() => store.attachSetOpen(null));

  // Implicit active-trigger selection: when the card opens and no explicit
  // trigger id has been set, pick the sole registered trigger.
  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);
    }
  }));

  // Interaction hooks — dismiss only at the root level. Hover (reference +
  // floating) lives on the trigger and popup, respectively, since
  // preview-card tracks per-trigger open/close delays.
  const dismiss = useDismiss(floatingRootContext);
  const interactions = useInteractions([dismiss]);
  const state = createMemo(() => ({
    open: open()
  }));

  // 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,
    instantType,
    setInstantType,
    state,
    setOpen,
    floatingRootContext,
    interactions,
    setTriggerElement,
    triggerElement,
    setPopupElement,
    popupElement,
    popupRef,
    store: store,
    activeTriggerId,
    setActiveTriggerId,
    payload,
    setPayload,
    closeDelayRef
  };
  const resolveChildren = () => {
    const ch = props.children;
    if (typeof ch === "function") {
      return ch({
        payload: payload()
      });
    }
    return ch;
  };
  return <PreviewCardRootContext value={contextValue}>
			{resolveChildren()}
		</PreviewCardRootContext>;
}

/**
 * Groups all parts of the preview card. Doesn't render its own HTML element —
 * the root is a pure context provider.
 *
 * Wraps the component tree in `<FloatingTree>` unless we're already nested
 * inside one (matches Base UI React's behavior so cross-tree events — like
 * dismiss-on-outside-press — propagate correctly for preview cards rendered
 * inside other popups).
 */
export function PreviewCardRoot(props) {
  if (usePreviewCardRootContext(true) != null || useFloatingParentNodeId() != null) {
    return <PreviewCardRootComponent {...props} />;
  }
  return <FloatingTree>
			<PreviewCardRootComponent {...props} />
		</FloatingTree>;
}
//# sourceMappingURL=preview-card-root.jsx.map
