/**
 * Stage 4.7 port of Base UI React's `useClientPoint` hook.
 *
 * React source:
 *   `base-ui/packages/react/src/floating-ui-react/hooks/useClientPoint.ts`
 *
 * Positions the floating element at a viewport client point (e.g. the
 * cursor). Used for context menus and tooltips that follow the cursor.
 * The hook installs a document `mousemove` listener while the floating
 * element is open and writes a fresh `VirtualElement` whose
 * `getBoundingClientRect()` returns the current `(x, y)` into the
 * store's `positionReference` slot — which `useFloating` prefers over
 * the raw `referenceElement`, so positioning tracks the cursor.
 *
 * ### React → Solid deviations
 *
 * 1. `useRef` → `MutableRef<T>` closures (matches sibling hooks). The
 *    `initialRef` / `cleanupListenerRef` state is preserved 1:1.
 *
 * 2. `useState<string | undefined>` (`pointerType`) →
 *    `createSignal<string | undefined>` so the listener-install effect
 *    re-runs when the pointer type changes (React's dep array doesn't
 *    actually include `pointerType`, but downstream reads inside the
 *    virtual-element factory go through the signal which is read
 *    lazily — correct either way).
 *
 * 3. `useState([])` (`reactive`) — React uses a tick-signal state to
 *    force the listener-install effect to re-run without changing any
 *    other dep. The Solid port uses a `createSignal<number>(0)` + bump
 *    counter with the same semantics.
 *
 * 4. `useStableCallback` → `createStableCallback`. Only used where the
 *    React source stabilized handler identity — Solid's
 *    non-re-rendering model doesn't strictly need it, but we preserve
 *    the shape for parity.
 *
 * 5. `useEffect(() => addListener(), [addListener, reactive])` → a
 *    single `createEffect(compute, apply)` that tracks the reactive
 *    inputs (`open`, `floatingElement`, `domReferenceElement`,
 *    `enabled`, `pointerType`, `reactiveTick`) and returns an
 *    `onCleanup` callback that removes the mousemove listener. The
 *    two smaller `useEffect`s that reset `initialRef` are collapsed
 *    into an `useIsoLayoutEffect` that runs synchronously on reactive
 *    changes.
 *
 * 6. React's `React.MouseEvent<Element>` → plain `MouseEvent`. The
 *    port's handler receives native events (Solid has no synthetic
 *    event wrapper).
 *
 * 7. Shadow-DOM-safe `contains()` from `src/internals/dom/contains.ts`
 *    replaces the raw `node.contains(target)` calls.
 *
 * 8. `React.useMemo` for the `reference` bag collapses to a plain
 *    object literal — Solid component bodies don't re-run, so the bag
 *    identity is already stable.
 *
 * ### Reason strings
 *
 * None. `useClientPoint` doesn't call `store.setOpen` — it only writes
 * the `positionReference` slot, so no `REASONS.*` mapping applies.
 */
import { getWindow } from "@floating-ui/utils/dom";
import { createEffect, createMemo, createSignal } from "solid-js";
import { contains } from "../../dom/contains.jsx";
import { createStableCallback } from "../../stable-callback.jsx";
import { useIsoLayoutEffect } from "../../use-iso-layout-effect.jsx";
import { getTarget } from "../utils/element.jsx";
import { isMouseLikePointerType } from "../utils/event.jsx";
function createVirtualElement(domElement, data) {
  let offsetX = null;
  let offsetY = null;
  let isAutoUpdateEvent = false;
  return {
    contextElement: domElement || undefined,
    getBoundingClientRect() {
      const domRect = domElement?.getBoundingClientRect() || {
        width: 0,
        height: 0,
        x: 0,
        y: 0
      };
      const isXAxis = data.axis === "x" || data.axis === "both";
      const isYAxis = data.axis === "y" || data.axis === "both";
      const canTrackCursorOnAutoUpdate = ["mouseenter", "mousemove"].includes(data.dataRef.current.openEvent?.type || "") && data.pointerType !== "touch";
      let width = domRect.width;
      let height = domRect.height;
      let x = domRect.x;
      let y = domRect.y;
      if (offsetX == null && data.x && isXAxis) {
        offsetX = domRect.x - data.x;
      }
      if (offsetY == null && data.y && isYAxis) {
        offsetY = domRect.y - data.y;
      }
      x -= offsetX || 0;
      y -= offsetY || 0;
      width = 0;
      height = 0;
      if (!isAutoUpdateEvent || canTrackCursorOnAutoUpdate) {
        width = data.axis === "y" ? domRect.width : 0;
        height = data.axis === "x" ? domRect.height : 0;
        x = isXAxis && data.x != null ? data.x : x;
        y = isYAxis && data.y != null ? data.y : y;
      } else if (isAutoUpdateEvent && !canTrackCursorOnAutoUpdate) {
        height = data.axis === "x" ? domRect.height : height;
        width = data.axis === "y" ? domRect.width : width;
      }
      isAutoUpdateEvent = true;
      return {
        width,
        height,
        x,
        y,
        top: y,
        right: x + width,
        bottom: y + height,
        left: x
      };
    }
  };
}
function isMouseBasedEvent(event) {
  return event != null && event.clientX != null;
}
/**
 * Positions the floating element relative to a client point (in the viewport),
 * such as the mouse position. By default, it follows the mouse cursor.
 * @see https://floating-ui.com/docs/useClientPoint
 */
export function useClientPoint(context, props = {}) {
  const store = "rootStore" in context ? context.rootStore : context;
  const dataRef = store.context.dataRef;

  // Lazy accessors so consumer-passed `get enabled()` / `get axis()`
  // stay reactive AND don't trip `[STRICT_READ_UNTRACKED]` from this
  // hook's body (which runs inside the parent component's untracked
  // scope). Use sites read via the accessor; effects fold the value
  // into compute so apply stays clean.
  const isEnabled = () => props.enabled === undefined ? true : props.enabled;
  const axisOf = () => props.axis === undefined ? "both" : props.axis;
  const {
    x: xProp = null,
    y: yProp = null
  } = props;

  // React `useRef` equivalents.
  const initialRef = {
    current: false
  };
  const cleanupListenerRef = {
    current: null
  };
  const [pointerType, setPointerType] = createSignal(undefined, {
    name: "useClientPoint.pointerType",
    ownedWrite: true
  });
  // React uses `useState([])` + `setReactive([])` as a tick-signal to
  // re-run the listener-install effect. Solid equivalent: a bumped counter.
  const [reactiveTick, setReactiveTick] = createSignal(0, {
    name: "useClientPoint.reactiveTick",
    ownedWrite: true
  });
  const setReference = createStableCallback(() => (newX, newY, referenceElement) => {
    if (initialRef.current) {
      return;
    }

    // Prevent setting if the open event was not a mouse-like one
    // (e.g. focus to open, then hover over the reference element).
    // Only apply if the event exists.
    if (dataRef.current.openEvent && !isMouseBasedEvent(dataRef.current.openEvent)) {
      return;
    }
    const domReference = store.state.domReferenceElement;
    store.set("positionReference", createVirtualElement(referenceElement ?? domReference, {
      x: newX,
      y: newY,
      axis: axisOf(),
      dataRef,
      pointerType: pointerType()
    }));
  });
  const handleReferenceEnterOrMove = createStableCallback(() => event => {
    if (!store.state.open) {
      setReference(event.clientX, event.clientY, event.currentTarget);
    } else if (!cleanupListenerRef.current) {
      // If there's no cleanup, there's no listener, but we want to ensure
      // we add the listener if the cursor landed on the floating element
      // and then back on the reference (i.e. it's interactive).
      setReactiveTick(t => t + 1);
    }
  });

  // Per-key memos de-dupe the store's coarse `equals: false` signal into
  // per-field identity-equal accessors. Without this, writes from inside
  // the main effect (below) would re-fire the compute with a fresh tuple
  // each time and loop. With the memos, compute only re-runs when an
  // individual key's identity changes.
  const openMemo = createMemo(() => store.state.open);
  const floatingMemo = createMemo(() => store.state.floatingElement);
  const domReferenceMemo = createMemo(() => store.state.domReferenceElement);

  // Main listener-install effect. React keyed this on
  // `[openCheck, enabled, floating, dataRef, domReference, store,
  // setReference]` plus a re-tick via `reactive`.
  createEffect(() => {
    // Tracked reads — mirrors React's dep array, through the
    // per-key memos so writes we make inside apply don't re-fire
    // the compute. Also captures `pointerType` here so the apply
    // callback never reads it directly (apply is unowned + strict).
    const open = openMemo();
    const floatingElement = floatingMemo();
    const domReferenceElement = domReferenceMemo();
    reactiveTick();
    const pointer = pointerType();
    const enabled = isEnabled();
    const axis = axisOf();
    return {
      open,
      floatingElement,
      domReferenceElement,
      pointer,
      enabled,
      axis
    };
  }, ({
    open,
    floatingElement,
    domReferenceElement,
    pointer,
    enabled,
    axis
  }) => {
    // Mirror React's `openCheck`: for mouse-like pointers keep
    // tracking even while the floating element is transitioning
    // out; for touch gate strictly on `open`.
    const openCheck = isMouseLikePointerType(pointer) ? floatingElement : open;
    if (!openCheck || !enabled) {
      return;
    }

    // Controlled override: `x` / `y` props bypass cursor tracking.
    if (xProp != null || yProp != null) {
      initialRef.current = false;
      store.set("positionReference", createVirtualElement(domReferenceElement, {
        x: xProp,
        y: yProp,
        axis,
        dataRef,
        pointerType: pointer
      }));
      return;
    }
    const win = getWindow(floatingElement);
    function handleMouseMove(event) {
      const target = getTarget(event);
      if (!contains(floatingElement, target)) {
        setReference(event.clientX, event.clientY);
      } else {
        cleanupListenerRef.current?.();
        cleanupListenerRef.current = null;
      }
    }
    if (!dataRef.current.openEvent || isMouseBasedEvent(dataRef.current.openEvent)) {
      win.addEventListener("mousemove", handleMouseMove);
      const removeListener = () => {
        win.removeEventListener("mousemove", handleMouseMove);
      };
      cleanupListenerRef.current = () => {
        removeListener();
        cleanupListenerRef.current = null;
      };
      return () => {
        removeListener();
        cleanupListenerRef.current = null;
      };
    }
    store.set("positionReference", domReferenceElement);
    return undefined;
  });

  // Reset `initialRef` when the floating element detaches (React runs
  // this inside a dedicated `useEffect`).
  useIsoLayoutEffect(() => ({
    floatingElement: store.state.floatingElement,
    enabled: isEnabled()
  }), ({
    floatingElement,
    enabled
  }) => {
    if (enabled && !floatingElement) {
      initialRef.current = false;
    }
  });

  // Snapshot `initialRef` when disabled while open (React runs this
  // inside a dedicated `useEffect`). Prevents a stale cursor tracking
  // from overwriting the positionReference after `enabled` flips off.
  useIsoLayoutEffect(() => ({
    open: store.state.open,
    enabled: isEnabled()
  }), ({
    open,
    enabled
  }) => {
    if (!enabled && open) {
      initialRef.current = true;
    }
  });
  const reference = {
    onPointerDown(event) {
      if (!isEnabled()) return;
      setPointerType(event.pointerType);
    },
    onPointerEnter(event) {
      if (!isEnabled()) return;
      setPointerType(event.pointerType);
    },
    onMouseMove(event) {
      if (!isEnabled()) return;
      handleReferenceEnterOrMove(event);
    },
    onMouseEnter(event) {
      if (!isEnabled()) return;
      handleReferenceEnterOrMove(event);
    }
  };
  return {
    reference,
    trigger: reference
  };
}
//# sourceMappingURL=use-client-point.jsx.map
