import { createEffect, createMemo, createSignal, onCleanup, runWithOwner, untrack } from "solid-js";
import { createChangeEventDetails, REASONS } from "../../internals/change-event-details.jsx";
import { createControlledState } from "../../internals/create-controlled-state.jsx";
import { useClientPoint } from "../../internals/floating-ui/hooks/use-client-point.jsx";
import { useDismiss } from "../../internals/floating-ui/hooks/use-dismiss.jsx";
import { useFloatingRootContext } from "../../internals/floating-ui/hooks/use-floating-root-context.jsx";
import { useFocus } from "../../internals/floating-ui/hooks/use-focus.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 { createTooltip } from "../create-tooltip.jsx";
import { TooltipStore } from "../store/tooltip-store.jsx";
import { TooltipRootContext } from "../tooltip-root-context.jsx";
/**
 * Groups all parts of the tooltip. Does not render its own HTML element.
 */
export function TooltipRoot(props) {
  // Adopt the handle's store if supplied.
  const store = props.handle?.store ?? new TooltipStore();

  // 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: "<Tooltip.Root>",
    state: "open"
  });

  // Compose the headless Tooltip primitive — owns open (pass-through),
  // disabled, trackCursorAxis, and disableHoverablePopup. The wrapper
  // layers floating-UI, transitions, hover/focus interactions, portal,
  // and trigger/handle plumbing on top.
  const tooltipPrimitive = createTooltip({
    open: resolvedOpen,
    disabled: () => Boolean(readOption(props.disabled)),
    trackCursorAxis: () => readOption(props.trackCursorAxis) ?? "none",
    disableHoverablePopup: () => readOption(props.disableHoverablePopup) ?? false
    // 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. The primitive's setOpen is unused
    // in the wrapper path.
  });
  const disabled = tooltipPrimitive.disabled;
  const trackCursorAxis = tooltipPrimitive.trackCursorAxis;
  const disableHoverablePopup = tooltipPrimitive.disableHoverablePopup;
  const rawOpen = tooltipPrimitive.open;
  const open = createMemo(() => !disabled() && rawOpen());
  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: the React source
  // keeps `mounted` true through the exit by
  // waiting on `Animation.finished` before dropping it. The ref is filled
  // by `<Tooltip.Popup>`'s `setPopupElement` callback below; the hook is a
  // no-op when `getAnimations()` returns an empty list, so the happy-dom
  // path still unmounts on the next microtask.
  const popupRef = {
    current: null
  };
  useOpenChangeComplete({
    open,
    ref: popupRef,
    onComplete: () => {
      if (!open()) {
        setMounted(false);
      }
    }
  });

  // Fire onOpenChangeComplete when transition settles.
  createEffect(() => ({
    open: open(),
    status: transitionStatus()
  }), ({
    open: o,
    status
  }) => {
    if (status === undefined) {
      props.onOpenChangeComplete?.(o);
    }
  });
  const [instantType, setInstantType] = createSignal(undefined, {
    ownedWrite: true
  });
  const [isInstantPhase, setIsInstantPhase] = createSignal(false, {
    ownedWrite: true
  });
  const [triggerElement, setTriggerElement] = createSignal(null, {
    ownedWrite: true
  });
  const [popupElement, setPopupElementInternal] = createSignal(null, {
    ownedWrite: true
  });
  // Keep the plain-ref mirror in sync with the popup-element signal. The
  // `useOpenChangeComplete` hook reads `.current` lazily inside a closure
  // (see `useAnimationsFinished`); keeping a live mutable mirror means the
  // hook picks up the popup element once `<Tooltip.Popup>` mounts without
  // an extra reactive effect.
  const setPopupElement = el => {
    popupRef.current = el;
    setPopupElementInternal(el);
  };

  // triggerId controlled override (consumer-supplied) layered over internal state.
  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);
  };
  createEffect(() => activeTriggerId(), id => {
    if (id === null) return;
    const el = store.context.triggerElements.getById(id);
    if (el) setTriggerElement(el);
  });
  const [payload, setPayload] = createSignal(undefined, {
    ownedWrite: true
  });
  const setOpen = (next, details) => {
    const reason = details.reason;
    if (next && reason === REASONS.triggerFocus) {
      setInstantType("focus");
    } else if (!next && (reason === REASONS.triggerPress || reason === REASONS.escapeKey || reason === REASONS.outsidePress)) {
      setInstantType("dismiss");
    } else if (reason === REASONS.triggerHover) {
      setInstantType(undefined);
    }
    // Use runWithOwner(null, ...) to escape the current reactive owner so
    // that consumer signal writes inside onOpenChange (and the internal
    // uncontrolled write) don't trigger SIGNAL_WRITE_IN_OWNED_SCOPE when
    // this setOpen is called from inside a reactive scope (e.g. the
    // delay-group useIsoLayoutEffect apply phase, or any
    // createRenderEffect body). untrack() alone doesn't clear owner.
    //
    // Base UI change flow (`TooltipStore.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).
    runWithOwner(null, () => {
      props.onOpenChange?.(next, details);
      if (details.isCanceled) return;
      setUncontrolledOpen(next);
    });
    // Base UI returns before trigger-state mirroring when the change was
    // canceled via `details.cancel()`.
    if (details.isCanceled) return;
    // Forward active trigger selection from the details.
    if (details.trigger?.id) {
      setActiveTriggerIdInternal(details.trigger.id);
      setTriggerElement(details.trigger);
    }
  };
  store.attachSetOpen((next, details) => setOpen(next, details));
  createEffect(() => open(), o => store.setOpenMirror(o));
  onCleanup(() => store.attachSetOpen(null));

  // Implicit active trigger. Capture `activeTriggerId()` in compute
  // alongside `open()` — apply runs unowned (`[STRICT_READ_UNTRACKED]`
  // would fire on a direct read).
  createEffect(() => ({
    openValue: open(),
    currentTriggerId: activeTriggerId()
  }), ({
    openValue,
    currentTriggerId
  }) => {
    if (!openValue) return;
    if (currentTriggerId !== 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);
      setTriggerElement(el);
    }
  });
  const floatingRootContext = useFloatingRootContext({
    open: untrack(() => open()),
    onOpenChange: (nextOpen, details) => {
      setOpen(nextOpen, details);
    }
  });
  createEffect(() => open(), next => {
    floatingRootContext.sync("open", next);
  });
  createEffect(() => triggerElement(), el => {
    floatingRootContext.sync("domReferenceElement", el);
    floatingRootContext.sync("referenceElement", el);
  });
  createEffect(() => popupElement(), el => {
    floatingRootContext.sync("floatingElement", el);
  });

  // Interaction hooks — with closeOnClick-aware dismiss.
  const closeOnClick = () => {
    // Track-cursor tooltips commonly want click-to-close, but the
    // default is still false (React's default). We respect the store's
    // `closeOnClick` flag below when the trigger writes to it — the
    // default on the trigger is `false`.
    return closeOnClickSignal();
  };
  const [closeOnClickSignal, setCloseOnClickSignal] = createSignal(false, {
    ownedWrite: true
  });
  const dismiss = useDismiss(floatingRootContext, {
    get enabled() {
      return !disabled();
    },
    referencePress: closeOnClick
  });
  const focus = useFocus(floatingRootContext, {
    get enabled() {
      return !disabled();
    }
  });
  const role = useRole(floatingRootContext, {
    role: "tooltip"
  });

  // useClientPoint — always install, gated via `enabled`.
  const clientPoint = useClientPoint(floatingRootContext, {
    get enabled() {
      return !disabled() && trackCursorAxis() !== "none";
    },
    get axis() {
      const a = trackCursorAxis();
      return a === "none" ? "both" : a;
    }
  });
  const interactions = useInteractions([dismiss, focus, role, clientPoint]);

  // Expose client-point prop bags separately so trigger / popup can
  // merge them on their own schedule (legacy callers already merge
  // `interactions.getReferenceProps()` etc., which now also carries
  // client-point; duplicating into these separate accessors is a
  // safety net for future callers).
  const clientPointReferenceProps = () => clientPoint.reference ?? {};
  const clientPointFloatingProps = () => clientPoint.floating ?? {};
  const state = createMemo(() => ({
    open: open()
  }));

  // Resolve children — render-function form.
  const resolveChildren = () => {
    const ch = props.children;
    if (typeof ch === "function") {
      return ch({
        payload: payload()
      });
    }
    return ch;
  };

  // actionsRef.
  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,
    disabled,
    mounted,
    transitionStatus,
    instantType,
    isInstantPhase,
    state,
    setInstantType,
    setIsInstantPhase,
    setOpen,
    floatingRootContext,
    interactions,
    setTriggerElement,
    triggerElement,
    setPopupElement,
    popupElement,
    store: store,
    activeTriggerId,
    setActiveTriggerId,
    payload,
    setPayload,
    disableHoverablePopup,
    trackCursorAxis,
    closeOnClick: closeOnClickSignal,
    setCloseOnClick: setCloseOnClickSignal,
    clientPointReferenceProps,
    clientPointFloatingProps
  };
  return <TooltipRootContext value={contextValue}>
			{resolveChildren()}
		</TooltipRootContext>;
}
//# sourceMappingURL=tooltip-root.jsx.map
