/**
 * Solid 2 port of Base UI React's `NavigationMenuTrigger`
 * (`packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx`).
 *
 * The interactive trigger for a single navigation-menu item. Opens the
 * popup on hover (with the restMs delay from the root) and toggles it on
 * click. When this trigger's item becomes active, it also:
 *
 *  - Publishes its own floating-root context into the root so list /
 *    positioner / popup can read it.
 *  - Tracks the popup's previous size so size transitions between items
 *    are smooth.
 *  - Renders focus-guard sentinels after itself so `Tab` / `Shift+Tab`
 *    from the trigger jumps into the popup (and vice-versa).
 *
 * ### Solid deviations vs. React source
 *
 *  - The elaborate popup auto-size reset state machine (MutationObserver +
 *    resize observer + animation-frame chains that shrink-then-measure
 *    between active items) is *ported in shape* but effectively inert in
 *    happy-dom because `getBoundingClientRect()` returns zero for every
 *    element. The React code survives and runs with no
 *    measurable effect — browser coverage is required to validate real
 *    transition behavior.
 *  - `ReactDOM.flushSync` calls are dropped — Solid's writes settle on
 *    microtasks (MIGRATION.md) and consumers rely on `flush()` in tests.
 *    For the "hover-open" path we still write open state synchronously,
 *    which is observable after `flush()` per popover.test's pattern.
 *  - `applySafePolygonPointerEventsMutation` / pointer-events mutation
 *    lives on the hook's shared-state; we call it in the matching
 *    `mouseenter` path but the Solid port's `use-hover-interaction-shared-state`
 *    already exports these as module functions, so this is ported 1:1.
 */
import { dynamic } from "@solidjs/web";
import { createEffect, createMemo, createSignal, createUniqueId, flush, omit, onCleanup, Show, untrack } from "solid-js";
import { createChangeEventDetails, REASONS } from "../../internals/change-event-details.jsx";
import { composeRefs, userRef } from "../../internals/compose-refs.jsx";
import { useFloatingParentNodeId, useFloatingTree } from "../../internals/floating-ui/components/floating-tree.jsx";
import { useClick } from "../../internals/floating-ui/hooks/use-click.jsx";
import { useDismiss } from "../../internals/floating-ui/hooks/use-dismiss.jsx";
import { useFloatingRootContext } from "../../internals/floating-ui/hooks/use-floating-root-context.jsx";
import { useHoverInteractionSharedState } from "../../internals/floating-ui/hooks/use-hover-interaction-shared-state.jsx";
import { useHoverReferenceInteraction } from "../../internals/floating-ui/hooks/use-hover-reference-interaction.jsx";
import { useInteractions } from "../../internals/floating-ui/hooks/use-interactions.jsx";
import { safePolygon } from "../../internals/floating-ui/safe-polygon.jsx";
import { contains } from "../../internals/floating-ui/utils/element.jsx";
import { stopEvent } from "../../internals/floating-ui/utils/event.jsx";
import { getNextTabbable, getPreviousTabbable, getTabbableAfterElement, isOutsideEvent } from "../../internals/floating-ui/utils/tabbable.jsx";
import { mergeRootProps } from "../../internals/merge-root-props.jsx";
import { FocusGuard } from "../../internals/popups/focus-guard.jsx";
import { resolveClassStyle } from "../../internals/resolve-class-style.jsx";
import { useButton } from "../../internals/use-button/use-button.jsx";
import { VISUALLY_HIDDEN_STYLE } from "../../internals/visually-hidden-style.jsx";
import { useNavigationMenuItemContext } from "../item/navigation-menu-item-context.jsx";
import { useNavigationMenuDismissContext } from "../list/navigation-menu-dismiss-context.jsx";
import { useNavigationMenuRootContext, useNavigationMenuTreeContext } from "../root/navigation-menu-root-context.jsx";
import { NAVIGATION_MENU_TRIGGER_IDENTIFIER } from "../utils/constants.jsx";
import { isOutsideMenuEvent } from "../utils/is-outside-menu-event.jsx";
const PATIENT_CLICK_THRESHOLD = 500;
export function NavigationMenuTrigger(props) {
  const root = useNavigationMenuRootContext();
  const item = useNavigationMenuItemContext();
  const nodeId = useNavigationMenuTreeContext();
  const tree = useFloatingTree();
  const dismissRef = useNavigationMenuDismissContext;
  const nested = useFloatingParentNodeId() != null || root.nested;
  const nativeButton = () => props.nativeButton ?? true;
  const disabled = () => Boolean(props.disabled);
  const [triggerElement, setTriggerElementSignal] = createSignal(null, {
    ownedWrite: true
  });
  const [stickIfOpen, setStickIfOpen] = createSignal(true, {
    ownedWrite: true
  });
  const [pointerType, setPointerType] = createSignal("", {
    ownedWrite: true
  });
  const triggerElementRef = {
    current: null
  };
  const allowFocusRef = {
    current: false
  };
  const isActiveItem = createMemo(() => root.open() && root.value() === item.value());
  const interactionsEnabled = () => {
    const positioner = root.positionerElement();
    if (positioner) return true;
    return root.value() == null;
  };
  const hoverFloatingElement = () => root.positionerElement() ?? root.viewportElement();
  const hoverInteractionsEnabled = () => {
    if (hoverFloatingElement()) return true;
    return root.value() == null;
  };
  const handleTriggerElement = element => {
    triggerElementRef.current = element;
    setTriggerElementSignal(element);
  };

  // ---------------------------------------------------------------------
  // Open-change handler
  // ---------------------------------------------------------------------

  // Patient-click timer: when the popup opens via hover, ignore click-to-close
  // for `PATIENT_CLICK_THRESHOLD` ms.
  let stickTimeoutId;
  onCleanup(() => {
    if (stickTimeoutId !== undefined) clearTimeout(stickTimeoutId);
  });

  // Invoked from `useFloatingRootContext`'s `onOpenChange` (effect-apply
  // scope). Reads here are snapshots — wrap in `untrack` to silence
  // `[STRICT_READ_UNTRACKED]` on `interactionsEnabled()` / `pointerType()`
  // / `root.value()` / `item.value()`.
  function handleOpenChange(nextOpen, details) {
    untrack(() => {
      const isHover = details.reason === REASONS.triggerHover;
      if (!interactionsEnabled()) return;
      if (pointerType() === "touch" && isHover) return;
      if (!nextOpen && root.value() !== item.value()) return;
      if (isHover) {
        setStickIfOpen(true);
        if (stickTimeoutId !== undefined) clearTimeout(stickTimeoutId);
        stickTimeoutId = setTimeout(() => {
          setStickIfOpen(false);
        }, PATIENT_CLICK_THRESHOLD);
      }
      const eventDetails = createChangeEventDetails(details.reason, details.event);
      if (nextOpen) {
        root.setValue(item.value(), eventDetails);
      } else {
        root.setValue(null, eventDetails);
        setPointerType("");
      }

      // React uses `flushSync` on hover so `value` updates before the
      // floating-root `open` sync effect runs (see `createEffect` below).
      if (isHover) {
        flush();
      }
    });
  }
  const floatingRootContext = useFloatingRootContext({
    open: untrack(() => root.open()),
    onOpenChange: (nextOpen, details) => handleOpenChange(nextOpen, details)
  });

  // Sync root-open flag + trigger/floating elements into the floating
  // context. `update()` no longer flushes per-writer (writes batch to the
  // next microtask), but the hover path above calls `flush()` synchronously,
  // which can drain these writes and their downstream effects within this
  // apply's stack while the strict-read scope is still armed. `untrack`
  // keeps the apply body's writes (and the `triggerElement()` snapshot read
  // in the second effect) from re-tripping `[STRICT_READ_UNTRACKED]`.
  createEffect(() => ({
    // Match React: sync menu-wide `open` (value != null), not per-item
    // `isActiveItem`, so hover-open isn't reset before `value` updates.
    open: root.open(),
    trigger: triggerElement(),
    float: hoverFloatingElement()
  }), ({
    open,
    trigger,
    float
  }) => untrack(() => {
    floatingRootContext.update({
      open,
      referenceElement: trigger,
      domReferenceElement: trigger,
      floatingElement: float
    });
  }));
  const hoverInteractionState = useHoverInteractionSharedState(floatingRootContext);

  // Publish this floating context to the root when this item becomes active.
  createEffect(() => ({
    active: isActiveItem(),
    ctx: floatingRootContext
  }), ({
    active,
    ctx
  }) => untrack(() => {
    if (active) {
      root.setFloatingRootContext(ctx);
      root.prevTriggerElementRef.current = triggerElement();
    }
  }));

  // Clear stale openEvent / rest timers when this item stops being active.
  createEffect(() => root.open(), open => {
    if (!open) {
      floatingRootContext.context.dataRef.current.openEvent = undefined;
      hoverInteractionState.pointerType = undefined;
      hoverInteractionState.interactedInside = false;
    }
  });

  // ---------------------------------------------------------------------
  // Interaction hooks
  // ---------------------------------------------------------------------

  const getScope = () => {
    if (!nested || !root.positionerElement()) {
      return triggerElementRef.current?.closest("ul") ?? null;
    }
    return null;
  };
  const hoverProps = useHoverReferenceInteraction(floatingRootContext, {
    enabled: () => hoverInteractionsEnabled(),
    move: false,
    handleClose: safePolygon({
      // `safePolygon` destructures `blockPointerEvents` once at body time
      // (see safe-polygon.ts line 112), so a getter would only fire
      // `[STRICT_READ_UNTRACKED]` and never re-read on `pointerType`
      // changes anyway. Snapshot through `untrack`.
      blockPointerEvents: untrack(() => pointerType() !== "touch"),
      getScope
    }),
    // `useHoverReferenceInteraction` accepts `restMs` / `delay` as
    // `number | (() => number)` / `Delay | (() => Delay)` and destructures
    // each once at body time (see use-hover-reference-interaction.ts lines
    // 116, 119) — passing accessors instead of getters preserves
    // reactivity through `getDelay()` / `getRestMs()` (which call the
    // function every read inside the hook's tracked effects) AND avoids
    // the body-time getter-read that fires `[STRICT_READ_UNTRACKED]`
    // from the hook's untrack(<HookLabel>) scope.
    restMs: () => root.mounted() && root.positionerElement() ? 0 : root.delay(),
    delay: () => ({
      open: root.delay(),
      close: root.closeDelay()
    }),
    triggerElementRef,
    triggerElement
  });

  // React parity: `toggle={isActiveItem}` so only the currently-active
  // trigger can close by re-clicking. Reactive accessor (P3).
  const clickProps = useClick(floatingRootContext, {
    // `useClick` accepts `enabled` / `toggle` as `MaybeAccessor<boolean>`
    // and reads them lazily through `readOption(...)` (use-click.ts lines
    // 113-115). Pass accessors instead of getters to keep reactivity
    // while sidestepping the body-time getter-read. `stickIfOpen` is
    // destructured once (line 105), so a getter would only fire the
    // body-time read — snapshot via `untrack`.
    enabled: () => interactionsEnabled(),
    stickIfOpen: untrack(() => stickIfOpen()),
    toggle: () => isActiveItem()
  });

  // Document-level Escape handler. Historically a workaround
  // (list's `useDismiss` installed against an undefined root context and
  // never re-bound). Post-P3 `useDismiss` accepts an accessor for its
  // context and re-installs listeners when the active root swaps, so the
  // list's dismiss now handles Escape too. Kept as belt-and-suspenders
  // against the `interactionsEnabled()` gate flip on the list (which
  // disables the list handler while the trigger is the active item).
  // `outsidePress` is disabled here — the list owns that path via its
  // `NAVIGATION_MENU_TRIGGER_IDENTIFIER` predicate.
  useDismiss(floatingRootContext, {
    // `useDismiss` accepts `enabled` as `MaybeAccessor<boolean>` and
    // re-installs listeners when it changes — pass an accessor.
    enabled: () => isActiveItem(),
    escapeKey: true,
    outsidePress: false
  });

  // The dismiss props from the enclosing list context — merge them onto the
  // trigger so the list's escape-key handler attaches to the reference.
  const {
    getReferenceProps
  } = useInteractions([hoverProps ? {
    reference: hoverProps
  } : undefined, clickProps, {
    reference: dismissRef()?.reference
  }]);
  const {
    props: buttonProps,
    ref: buttonRef
  } = useButton({
    disabled,
    focusableWhenDisabled: () => true,
    native: nativeButton
  });
  function handleActivation(event) {
    const prevRect = root.prevTriggerElementRef.current?.getBoundingClientRect?.();
    const triggerEl = triggerElement();
    if (root.mounted() && prevRect && triggerEl) {
      const nextRect = triggerEl.getBoundingClientRect();
      const isMovingRight = nextRect.left > prevRect.left;
      const isMovingDown = nextRect.top > prevRect.top;
      const orientation = root.orientation();
      let dir = null;
      if (orientation === "horizontal" && nextRect.left !== prevRect.left) {
        dir = isMovingRight ? "right" : "left";
      } else if (orientation === "vertical" && nextRect.top !== prevRect.top) {
        dir = isMovingDown ? "down" : "up";
      }
      if (dir !== null) root.setActivationDirection(dir);
    }

    // Reset `openEvent` when switching items so hover→hover back doesn't
    // leave stale state.
    if (event.type !== "click" && root.value() != null) {
      floatingRootContext.context.dataRef.current.openEvent = undefined;
    }
    if (pointerType() === "touch" && event.type !== "click") return;
    if (root.value() != null) {
      root.setValue(item.value(), createChangeEventDetails(event.type === "mouseenter" ? REASONS.triggerHover : REASONS.triggerPress, event));
    }
  }
  function handleOpenEvent(event) {
    handleActivation(event);
  }
  function handleSetPointerType(event) {
    setPointerType(event.pointerType);
  }
  const triggerId = createUniqueId();
  const defaultProps = {
    tabIndex: 0,
    onMouseEnter: handleOpenEvent,
    onClick: handleOpenEvent,
    onPointerEnter: handleSetPointerType,
    onPointerDown: handleSetPointerType,
    get "aria-expanded"() {
      // Return explicit string values so Solid's attribute binding doesn't
      // collapse `true` to an empty-string presence attr.
      return isActiveItem() ? "true" : "false";
    },
    get "aria-controls"() {
      // Seed with "" so the DOM spread key stays subscribed.
      return isActiveItem() ? root.popupElement()?.id ?? "" : "";
    },
    [NAVIGATION_MENU_TRIGGER_IDENTIFIER]: "",
    onFocus() {
      if (!isActiveItem()) return;
      root.setViewportInert(false);
    },
    onMouseMove() {
      allowFocusRef.current = false;
    },
    onKeyDown(event) {
      allowFocusRef.current = true;
      if (nested) return;
      const orientation = root.orientation();
      const openHorizontal = orientation === "horizontal" && event.key === "ArrowDown";
      const openVertical = orientation === "vertical" && event.key === "ArrowRight";
      if (openHorizontal || openVertical) {
        root.setValue(item.value(), createChangeEventDetails(REASONS.listNavigation, event));
        handleOpenEvent(event);
        stopEvent(event);
      }
    },
    onBlur(event) {
      const positioner = root.positionerElement();
      const popup = root.popupElement();
      if (positioner && popup && isOutsideMenuEvent({
        currentTarget: event.currentTarget,
        relatedTarget: event.relatedTarget
      }, {
        popupElement: popup,
        rootRef: root.rootRef,
        tree,
        nodeId
      })) {
        root.setValue(null, createChangeEventDetails(REASONS.focusOut, event));
      }
    }
  };
  const state = createMemo(() => ({
    open: isActiveItem()
  }));
  const dataAttrs = {
    get "data-popup-open"() {
      return state().open ? "" : undefined;
    },
    get "data-pressed"() {
      return state().open ? "" : undefined;
    }
  };
  const userClassStyle = resolveClassStyle(props, () => state());
  const userProps = omit(props, "as", "class", "style", "attrs", "nativeButton", "disabled");
  let merged = mergeRootProps(getReferenceProps(), defaultProps);
  merged = mergeRootProps(merged, buttonProps);
  merged = mergeRootProps(merged, {
    id: triggerId
  });
  merged = mergeRootProps(merged, dataAttrs);
  merged = mergeRootProps(merged, userClassStyle);
  merged = mergeRootProps(merged, userProps);

  // Primary trigger element.
  const Tag = dynamic(() => props.as ?? "button");
  const element = <Tag {...merged} ref={composeRefs(buttonRef, handleTriggerElement, userRef(props))} />;

  // Focus guards attached when this trigger is active.
  return <>
			{element}
			<Show when={isActiveItem()}>
				<FocusGuard ref={el => {
        root.beforeOutsideRef.current = el;
      }} onFocus={event => {
        const referenceElement = hoverFloatingElement();
        if (referenceElement && isOutsideEvent(event, referenceElement)) {
          root.beforeInsideRef.current?.focus();
        } else {
          const prev = getPreviousTabbable(triggerElement());
          prev?.focus();
        }
      }} />
				<span aria-owns={root.viewportElement()?.id ?? ""} style={{
        ...VISUALLY_HIDDEN_STYLE
      }} />
				<FocusGuard ref={el => {
        root.afterOutsideRef.current = el;
      }} onFocus={event => {
        const referenceElement = hoverFloatingElement();
        if (referenceElement && isOutsideEvent(event, referenceElement)) {
          root.setViewportInert(false);
          const elementToFocus = root.afterInsideRef.current ?? triggerElement();
          elementToFocus?.focus();
        } else {
          let nextTabbable = getNextTabbable(triggerElement());
          if (nested && !root.positionerElement() && referenceElement && nextTabbable && contains(referenceElement, nextTabbable)) {
            nextTabbable = getTabbableAfterElement(root.afterInsideRef.current);
          }
          nextTabbable?.focus();
          if ((!nested || root.positionerElement()) && !contains(root.rootRef.current, nextTabbable)) {
            root.setValue(null, createChangeEventDetails(REASONS.focusOut, event));
          }
        }
      }} />
			</Show>
		</>;
}
//# sourceMappingURL=navigation-menu-trigger.jsx.map
