/**
 * Stage 2 port of Base UI React's `useFloating` hook.
 *
 * React source: `base-ui/packages/react/src/floating-ui-react/hooks/useFloating.ts`.
 * That file wraps `@floating-ui/react-dom`'s `useFloating` (the positioning
 * engine) and grafts on a root-store context, a domReference ref, and a
 * `FloatingContext` value that later-stage interaction hooks consume.
 *
 * The Solid port:
 * - Replaces `@floating-ui/react-dom`'s `useFloating` with an inline
 *   Solid-native port of the same algorithm built on `@floating-ui/dom`'s
 *   `computePosition` + `autoUpdate`. We can't reuse the React helper
 *   because it is gated on `React.useState` / `ReactDOM.flushSync`.
 * - Swaps `React.useRef` for the `MutableRef<T>` pattern from Stage 1.
 * - Swaps `React.useState` / `useEffect` for Solid's `createSignal` /
 *   `createEffect` + `onCleanup`.
 * - Uses `useIsoLayoutEffect` (alias of `createRenderEffect`) where React
 *   uses `useIsoLayoutEffect`.
 *
 * `FloatingTree` / `useFloatingTree` is ported in Stage 3. The
 * tree-sync block at the end of React's `useFloating` that writes back
 * `node.context` is now wired: it's required for
 * `FloatingFocusManager.handleFocusOutside` to detect focus moves into
 * an ancestor floating element as "related" rather than closing the
 * current surface.
 */
import { autoUpdate, computePosition } from "@floating-ui/dom";
import { isElement } from "@floating-ui/utils/dom";
import { createEffect, createSignal, onCleanup, untrack } from "solid-js";
import { useIsoLayoutEffect } from "../../use-iso-layout-effect.jsx";
import { useFloatingTree } from "../components/floating-tree.jsx";
import { useFloatingRootContext } from "./use-floating-root-context.jsx";

// ---------------------------------------------------------------------------
// Low-level Solid port of `@floating-ui/react-dom`'s `useFloating`.
//
// This reimplements the positioning engine: drive `computePosition` from the
// latest reference/floating/middleware/strategy/placement, and use
// `autoUpdate` to re-run it when layout changes. Output signals feed the
// outer `UseFloatingReturn`. Kept in this file because no other hook needs
// it; Stage 5+ may extract it if components start reaching for the raw
// positioning engine.
// ---------------------------------------------------------------------------

function createPosition(getOpts) {
  const [x, setX] = createSignal(0, {
    ownedWrite: true
  });
  const [y, setY] = createSignal(0, {
    ownedWrite: true
  });
  // Initial signal values are taken as snapshots — `getOpts()` reads
  // reactive sources (the parent's options bag) inside this hook's
  // component-body scope, which strict-read flags. The seeded value is
  // only the initial; subsequent updates flow through the apply effect
  // further below.
  const [placement, setPlacement] = createSignal(untrack(() => getOpts().placement ?? "bottom"), {
    ownedWrite: true
  });
  const [strategy, setStrategy] = createSignal(untrack(() => getOpts().strategy ?? "absolute"), {
    ownedWrite: true
  });
  const [middlewareData, setMiddlewareData] = createSignal({}, {
    ownedWrite: true
  });
  const [isPositioned, setIsPositioned] = createSignal(false, {
    ownedWrite: true
  });
  const referenceRef = {
    current: null
  };
  const floatingRef = {
    current: null
  };
  const [referenceSignal, setReferenceSignal] = createSignal(null, {
    ownedWrite: true
  });
  const [floatingSignal, setFloatingSignal] = createSignal(null, {
    ownedWrite: true
  });

  // Track mount state so `computePosition` callbacks don't touch signals
  // after disposal. In Solid there is no `onMount` export; we flip the
  // flag in a split-effect's apply (which fires after setup completes)
  // and clear it via `onCleanup`.
  let isMounted = false;
  createEffect(() => undefined, () => {
    isMounted = true;
  });
  onCleanup(() => {
    isMounted = false;
  });
  const update = () => {
    // `update` fires from effect-apply callbacks and `autoUpdate`'s
    // resize/scroll listeners — neither is a tracking scope in Solid 2.
    // `getOpts` reads store signals (for `elements.reference`/`.floating`
    // in particular); wrap in `untrack` so those reads return the current
    // values without emitting the dev "read directly in an effect" warning.
    const opts = untrack(getOpts);
    const reference = referenceRef.current ?? opts.elements.reference;
    const floating = floatingRef.current ?? opts.elements.floating;
    if (!reference || !floating) {
      return;
    }
    const config = {
      placement: opts.placement ?? "bottom",
      strategy: opts.strategy ?? "absolute",
      middleware: opts.middleware ?? []
    };
    if (opts.platform) {
      config.platform = opts.platform;
    }
    computePosition(reference, floating, config).then(next => {
      if (!isMounted) {
        return;
      }
      setX(next.x);
      setY(next.y);
      setPlacement(next.placement);
      setStrategy(next.strategy);
      setMiddlewareData(next.middlewareData);
      // Matches React: don't flip isPositioned to `true` while open is
      // explicitly false (transition-out case).
      setIsPositioned(opts.open !== false);
    });
  };
  const setReference = node => {
    if (node !== referenceRef.current) {
      referenceRef.current = node;
      setReferenceSignal(node);
    }
  };
  const setFloating = node => {
    if (node !== floatingRef.current) {
      floatingRef.current = node;
      setFloatingSignal(node);
    }
  };

  // Reset isPositioned when open flips to false. Mirrors React's layout
  // effect. Split-effect: compute tracks `opts.open`, apply runs the
  // side effect. We check `isPositioned()` untracked inside apply since
  // we only want to flip it on the open-value change.
  createEffect(() => getOpts().open, openValue => {
    if (openValue === false) {
      setIsPositioned(false);
    }
  });

  // Drive `autoUpdate` whenever both reference + floating are attached.
  // `autoUpdate` itself handles scroll/resize listening — we just need
  // to re-subscribe if either element changes. Compute produces the
  // [reference, floating] pair (tracking `referenceSignal`,
  // `floatingSignal`, and the opts' elements), apply (re)binds
  // `autoUpdate` + hooks a cleanup.
  createEffect(() => {
    const opts = getOpts();
    return {
      reference: opts.elements.reference ?? referenceSignal(),
      // The locally-set floating element (`refs.setFloating`, i.e. the
      // positioner) must take precedence over the root store's
      // `floatingElement` for POSITIONING. Roots that sync the popup
      // element into the store (tooltip, preview-card — for interaction
      // hooks like safe-polygon) would otherwise re-target `autoUpdate`
      // + `computePosition` at the popup a frame after mount, and the
      // popup-relative coordinates would corrupt the positioner's
      // transform (the tooltip visibly flies toward the document
      // origin). React's `useFloating` has the same split: positioning
      // follows `position.refs.setFloating`, the store serves hooks.
      floating: floatingSignal() ?? opts.elements.floating
    };
  }, ({
    reference,
    floating
  }) => {
    if (reference) {
      referenceRef.current = reference;
    }
    if (floating) {
      floatingRef.current = floating;
    }
    if (!reference || !floating) {
      return;
    }
    const cleanup = autoUpdate(reference, floating, update);
    return cleanup;
  });

  // Re-run the position calc when placement/strategy/middleware change.
  createEffect(() => {
    const o = getOpts();
    return [o.placement, o.strategy, o.middleware, o.platform];
  }, () => {
    update();
  });
  return {
    x,
    y,
    placement,
    strategy,
    middlewareData,
    isPositioned,
    update,
    refs: {
      reference: referenceRef,
      floating: floatingRef,
      setReference,
      setFloating
    },
    elements: () => {
      const opts = getOpts();
      return {
        reference: opts.elements.reference ?? referenceSignal(),
        floating: opts.elements.floating ?? floatingSignal()
      };
    }
  };
}

// ---------------------------------------------------------------------------
// Public `useFloating` hook
// ---------------------------------------------------------------------------

/**
 * Provides data to position a floating element and context to add interactions.
 *
 * The `externalTree` option is consumed via `useFloatingTree(externalTree)`
 * to resolve the tree that owns `nodeId`; when omitted, the ambient
 * `FloatingTreeContext` supplies it. Both the `dataRef.current.floatingContext`
 * write and the tree-node back-ref (`node.context = context`) are wired.
 *
 * @see https://floating-ui.com/docs/useFloating
 */
export function useFloating(options = {}) {
  const {
    nodeId
  } = options;
  const internalRootStore = useFloatingRootContext({
    onOpenChange: options.onOpenChange,
    elements: options.elements
  });
  const rootContext = options.rootContext ?? internalRootStore;

  // Position-reference override (settable via `setPositionReference`).
  const [positionReference, setPositionReferenceRaw] = createSignal(null, {
    ownedWrite: true
  });
  const domReferenceRef = {
    current: null
  };

  // Sync rootContext's domReferenceElement into the local ref. React does
  // this inside a useIsoLayoutEffect; Solid uses the same primitive.
  useIsoLayoutEffect(() => rootContext.state.domReferenceElement, domReference => {
    if (domReference) {
      domReferenceRef.current = domReference;
    }
  });
  const position = createPosition(() => ({
    placement: options.placement,
    strategy: options.strategy,
    middleware: options.middleware,
    platform: options.platform,
    elements: {
      reference: positionReference() ?? rootContext.state.positionReference ?? rootContext.state.referenceElement,
      floating: rootContext.state.floatingElement
    },
    open: rootContext.state.open
  }));
  const setPositionReference = node => {
    const computedPositionReference = isElement(node) ? {
      getBoundingClientRect: () => node.getBoundingClientRect(),
      getClientRects: () => node.getClientRects(),
      contextElement: node
    } : node;
    setPositionReferenceRaw(computedPositionReference);
    position.refs.setReference(computedPositionReference);
  };

  // Local mirrors synced into the root store — mirrors React's
  // `localDomReference` / `localFloatingElement` state + `useSyncedValue`
  // calls. We implement them as plain signals + a Solid effect so the
  // writes happen reactively.
  const [localDomReference, setLocalDomReference] = createSignal(undefined, {
    ownedWrite: true
  });
  const [localFloatingElement, setLocalFloatingElement] = createSignal(null, {
    ownedWrite: true
  });
  createEffect(() => ({
    localRef: localDomReference(),
    localFloating: localFloatingElement()
  }), ({
    localRef,
    localFloating
  }) => {
    // Apply callback is untracked in Solid 2 (`createEffect(compute,
    // effectFn)` only tracks the compute fn). We want "current value,
    // no subscription" for the rootContext store reads — `untrack`
    // keeps the strict-read context from flagging the snapshot reads
    // while preserving identical semantics (rootContext writes still
    // reactively notify their own subscribers).
    untrack(() => {
      rootContext.sync("referenceElement", localRef === undefined ? rootContext.state.referenceElement : localRef ?? null);
      const localDomElement = isElement(localRef) ? localRef : null;
      rootContext.sync("domReferenceElement", localRef === undefined ? rootContext.state.domReferenceElement : localDomElement);
      rootContext.sync("floatingElement", localFloating);
    });
  });
  const setReference = node => {
    if (isElement(node) || node === null) {
      domReferenceRef.current = node;
      setLocalDomReference(node);
    }

    // Backwards-compat: passing a virtual element to `reference` AFTER
    // a DOM reference has been set preserves the virtual-ref path.
    // See matching comment in React source.
    if (isElement(position.refs.reference.current) || position.refs.reference.current === null || node !== null && !isElement(node)) {
      position.refs.setReference(node);
    }
  };
  const setFloating = node => {
    setLocalFloatingElement(node);
    position.refs.setFloating(node);
  };
  const refs = {
    reference: position.refs.reference,
    floating: position.refs.floating,
    domReference: domReferenceRef,
    setReference,
    setFloating,
    setPositionReference
  };

  // `elements` exposes current values. React used `useMemo`; Solid
  // inlines the read and relies on consumers to track if needed.
  // Defined as a getter-style object so the values stay live when read
  // from inside a tracking context (e.g. an effect in a consumer).
  const elements = {
    get reference() {
      return position.elements().reference;
    },
    get floating() {
      return position.elements().floating;
    },
    get domReference() {
      return rootContext.state.domReferenceElement;
    }
  };

  // FloatingContext — live getters over the underlying signals so that
  // interaction hooks (Stage 4+) observe current values inside effects.
  // Each getter subscribes to exactly one signal — a single `data()` function
  // that read all six signals per call caused
  // every reader to subscribe to all six, amplifying `middlewareData`
  // churn into ResizeObserver feedback via the `size` middleware's
  // `--anchor-width` writes.
  const context = {
    get x() {
      return position.x();
    },
    get y() {
      return position.y();
    },
    get placement() {
      return position.placement();
    },
    get strategy() {
      return position.strategy();
    },
    get middlewareData() {
      return position.middlewareData();
    },
    get isPositioned() {
      return position.isPositioned();
    },
    update: position.update,
    get open() {
      return rootContext.state.open;
    },
    onOpenChange: rootContext.setOpen,
    events: rootContext.context.events,
    dataRef: rootContext.context.dataRef,
    get floatingId() {
      return rootContext.state.floatingId;
    },
    refs,
    elements,
    nodeId,
    rootStore: rootContext
  };

  // Stash the context on the dataRef so legacy call sites can fish it
  // back out (matches React). Also sync the back-ref on the floating
  // tree node so ancestor / descendant walks (used by
  // `FloatingFocusManager.handleFocusOutside`, `useDismiss`, nested
  // hover interaction) can reach this context via `node.context`.
  // Mirrors React's `node.context = context` assignment at the tail of
  // `useFloating`.
  const tree = useFloatingTree(options.externalTree);
  useIsoLayoutEffect(() => undefined, () => {
    rootContext.context.dataRef.current.floatingContext = context;
    if (tree && nodeId != null) {
      const node = tree.nodesRef.current.find(n => n.id === nodeId);
      if (node) {
        node.context = context;
      }
    }
  });

  // Return shape: merge positioning data + context + refs + elements.
  // Each getter reads exactly one signal — see the note above the
  // `context` builder. Building the object with
  // getters (not via `{...result}` spread) preserves reactivity; spread
  // would evaluate getters once and freeze the initial `0`/`false` values.
  return {
    get x() {
      return position.x();
    },
    get y() {
      return position.y();
    },
    get placement() {
      return position.placement();
    },
    get strategy() {
      return position.strategy();
    },
    get middlewareData() {
      return position.middlewareData();
    },
    get isPositioned() {
      return position.isPositioned();
    },
    update: position.update,
    context,
    refs,
    elements
  };
}
//# sourceMappingURL=use-floating.jsx.map
