/**
 * Stage 4.2 port of Base UI React's `useFocus` hook.
 *
 * React source:
 *   `base-ui/packages/react/src/floating-ui-react/hooks/useFocus.ts`
 *
 * Opens the floating element while the reference element has focus, and
 * closes it on blur. Respects `visibleOnly` via `matchesFocusVisible`,
 * handles window-blur re-entry edge cases, and exposes a `delay` option
 * for keyboard-triggered opens.
 *
 * ### React → Solid deviations
 *
 * 1. React `useRef` → plain closure-captured `let` bindings. Solid has
 *    no re-render cycle, so component-scoped mutables are just variables
 *    in the function body.
 *
 * 2. Two `useEffect`s in the React source become two `createEffect`s
 *    here. Listener install/remove is wrapped in `onCleanup`.
 *
 * 3. React's `addEventListener` + `mergeCleanups` helpers from
 *    `@base-ui/utils` are replaced with direct `addEventListener` +
 *    manual cleanup arrays. Shape-equivalent.
 *
 * 4. The `delay` option accepts `number | (() => number | undefined)` —
 *    the function form is invoked at focus-time so the latest value
 *    wins. Same as React.
 *
 * 5. `:focus-visible` matching in happy-dom is unreliable; the
 *    shared `matchesFocusVisible` helper short-circuits to `true`
 *    inside the test env.
 */
import { isElement, isHTMLElement } from "@floating-ui/utils/dom";
import { createEffect, untrack } from "solid-js";
import { createChangeEventDetails, REASONS } from "../../change-event-details.jsx";
import { contains } from "../../dom/contains.jsx";
import { ownerDocument } from "../../dom/owner-document.jsx";
import { useTimeout } from "../../use-timeout.jsx";
import { isMac, isSafari } from "../detect-browser.jsx";
import { createAttribute } from "../utils/create-attribute.jsx";
import { activeElement, getTarget, isTargetInsideEnabledTrigger, isTypeableElement, matchesFocusVisible } from "../utils/element.jsx";

// `isMac && isSafari` gate, mirroring the React source. Safari fails to match
// `:focus-visible` when focus arrives from outside the document, so the
// Mac-Safari branches fall back to keyboard-modality tracking. On every other
// browser this is `false` and the happy-path runs.
const isMacSafari = isMac && isSafari;
/**
 * Opens the floating element while the reference element has focus, like CSS
 * `:focus`.
 * @see https://floating-ui.com/docs/useFocus
 */
export function useFocus(context, props = {}) {
  const store = "rootStore" in context ? context.rootStore : context;
  const {
    events,
    dataRef
  } = store.context;
  // `enabled` is read lazily so consumers can pass a reactive getter
  // (e.g. `get enabled() { return !disabled(); }`) and have the hook
  // honor flips. Body-time destructure would freeze the value AND
  // invoke the getter eagerly under strict-read.
  const isEnabled = () => props.enabled === undefined ? true : props.enabled;
  const delay = untrack(() => props.delay);

  // Closure state — React `useRef` equivalents.
  let blockFocus = false;
  let blockedReference = null;
  let keyboardModality = true;
  const timeout = useTimeout();

  // Window-level listeners: blur / keydown / pointerdown. React installs
  // these inside a `useEffect`; we do the same via `createEffect`'s
  // split-effect shape (Solid 2 requires `(compute, apply)`).
  createEffect(
  // Compute reads `domReferenceElement` so the effect re-installs
  // listeners against the right window when the reference moves
  // between owner-documents (e.g. iframe portals). Apply receives
  // the element through its argument — no reactive reads in apply.
  () => ({
    domReference: store.state.domReferenceElement,
    enabled: isEnabled()
  }), ({
    domReference,
    enabled
  }) => {
    if (!enabled) return undefined;
    const win = domReference?.ownerDocument?.defaultView ?? window;
    function onBlur() {
      const currentDomReference = store.state.domReferenceElement;
      if (!store.state.open && isHTMLElement(currentDomReference) && currentDomReference === activeElement(ownerDocument(currentDomReference))) {
        blockFocus = true;
      }
    }
    function onKeyDown() {
      keyboardModality = true;
    }
    function onPointerDown() {
      keyboardModality = false;
    }
    win.addEventListener("blur", onBlur);
    if (isMacSafari) {
      win.addEventListener("keydown", onKeyDown, true);
      win.addEventListener("pointerdown", onPointerDown, true);
    }
    return () => {
      win.removeEventListener("blur", onBlur);
      if (isMacSafari) {
        win.removeEventListener("keydown", onKeyDown, true);
        win.removeEventListener("pointerdown", onPointerDown, true);
      }
    };
  });

  // `openchange` listener — when the popup closes via escape / press, block
  // the reference from re-opening on the ensuing focus event.
  createEffect(() => isEnabled(), enabled => {
    if (!enabled) return undefined;
    function onOpenChangeLocal(details) {
      if (details.reason === REASONS.triggerPress || details.reason === REASONS.escapeKey) {
        const referenceElement = store.state.domReferenceElement;
        if (isElement(referenceElement)) {
          blockedReference = referenceElement;
          blockFocus = true;
        }
      }
    }
    events.on("openchange", onOpenChangeLocal);
    return () => {
      events.off("openchange", onOpenChangeLocal);
    };
  });
  const reference = {
    onMouseLeave() {
      if (!isEnabled()) return;
      blockFocus = false;
      blockedReference = null;
    },
    onFocus(event) {
      if (!isEnabled()) return;
      const focusTarget = event.currentTarget;
      if (blockFocus) {
        if (blockedReference === focusTarget) {
          return;
        }
        blockFocus = false;
        blockedReference = null;
      }
      const target = getTarget(event);
      if (isElement(target)) {
        // Safari fails to match `:focus-visible` if focus was
        // initially outside the document.
        if (isMacSafari && !event.relatedTarget) {
          if (!keyboardModality && !isTypeableElement(target)) {
            return;
          }
        } else if (!matchesFocusVisible(target)) {
          return;
        }
      }
      const movedFromOtherEnabledTrigger = isTargetInsideEnabledTrigger(event.relatedTarget, store.context.triggerElements);
      const nativeEvent = event;
      const currentTarget = event.currentTarget;
      const delayValue = typeof delay === "function" ? delay() : delay;
      if (store.state.open && movedFromOtherEnabledTrigger || delayValue === 0 || delayValue === undefined) {
        store.setOpen(true, createChangeEventDetails(REASONS.triggerFocus, nativeEvent, currentTarget));
        return;
      }
      timeout.start(delayValue, () => {
        if (blockFocus) return;
        store.setOpen(true, createChangeEventDetails(REASONS.triggerFocus, nativeEvent, currentTarget));
      });
    },
    onBlur(event) {
      if (!isEnabled()) return;
      blockFocus = false;
      blockedReference = null;
      const relatedTarget = event.relatedTarget;
      const nativeEvent = event;

      // Hit the non-modal focus management portal guard. Focus will
      // be moved into the floating element immediately after.
      const movedToFocusGuard = isElement(relatedTarget) && relatedTarget.hasAttribute(createAttribute("focus-guard")) && relatedTarget.getAttribute("data-type") === "outside";

      // Wait for the window blur listener to fire.
      timeout.start(0, () => {
        const domReference = store.state.domReferenceElement;
        const activeEl = activeElement(ownerDocument(domReference));

        // Focus left the page, keep it open.
        if (!relatedTarget && activeEl === domReference) {
          return;
        }

        // When focusing the reference element (e.g. regular click),
        // then clicking into the floating element, prevent it from
        // hiding. Note: it must be focusable, e.g. `tabindex="-1"`.
        if (contains(dataRef.current.floatingContext?.refs.floating.current ?? null, activeEl) || contains(domReference, activeEl) || movedToFocusGuard) {
          return;
        }

        // If the next focused element is one of the triggers, do
        // not close the floating element. The focus handler of that
        // trigger will handle the open state.
        const nextFocusedElement = relatedTarget ?? activeEl;
        if (isTargetInsideEnabledTrigger(nextFocusedElement, store.context.triggerElements)) {
          return;
        }
        store.setOpen(false, createChangeEventDetails(REASONS.triggerFocus, nativeEvent));
      });
    }
  };
  return {
    reference,
    trigger: reference
  };
}
//# sourceMappingURL=use-focus.jsx.map
