/**
 * Port of Base UI React's `useAnchorPositioning` hook
 * (`packages/react/src/utils/useAnchorPositioning.ts`).
 *
 * This is the heavy-weight orchestrator that threads `anchor`,
 * `collisionBoundary`, `sticky`, `disableAnchorTracking`,
 * `collisionAvoidance`, `adaptiveOrigin`, `side`, `align`, offsets and
 * arrow padding through `computePosition` + middleware. It composes the
 * Solid port's `useFloating` — the low-level positioning engine lives
 * there. This file's job is purely middleware assembly + anchor wiring.
 *
 * ## React → Solid deviations
 *
 * - Accessor-based reactive inputs. Every prop that React allowed to
 *   change between renders is modeled here as `Accessor<T>`; callers
 *   pass getters. Primitive-style values (e.g. numeric `sideOffset`) may
 *   be passed as a plain number because components usually compute them
 *   inside a reactive memo and pass the memo. The orchestration below
 *   wraps all inputs with `() => ...` at read time so either shape works.
 * - `React.useState` → `createSignal`, `React.useRef` → `{ current }`,
 *   `useLayoutEffect` → `createRenderEffect` (via `useIsoLayoutEffect`),
 *   `useEffect` → `createEffect`.
 * - `offset` / `flip` / `shift` middleware don't accept a `deps` array in
 *   `@floating-ui/dom` the way `@floating-ui/react-dom` did — Solid's
 *   reactive reads inside the middleware functions handle the same
 *   contract (middleware rebuilds on each `computePosition` call).
 * - React gates the initial autoUpdate-less path on `keepMounted`; Solid
 *   `useFloating` already wires `autoUpdate` when reference + floating
 *   are attached, so we thread `disableAnchorTracking` through by
 *   conditionally returning `false` from `useFloating`'s `open` read.
 *   (Not quite — we can't short-circuit `autoUpdate` from here without
 *   patching `useFloating`. Instead we skip registering the anchor
 *   entirely while `disableAnchorTracking` is true after first
 *   position.) See the `registerAnchor` effect below.
 * - The React source computes `middleware` as a fresh array on every
 *   render. Solid runs setup once; we rebuild the array via a getter so
 *   `useFloating` sees the latest reactive values.
 */
import { autoUpdate, flip, limitShift, hide as nativeHide, offset, shift, size } from "@floating-ui/dom";
import { getAlignment, getSide, getSideAxis } from "@floating-ui/utils";
import { createEffect, createMemo, createSignal } from "solid-js";
import { useDirection } from "../../direction-provider/direction-provider-context.jsx";
import { ownerDocument } from "../dom/owner-document.jsx";
import { ownerWindow } from "../dom/owner-window.jsx";
import { useFloating } from "../floating-ui/hooks/use-floating.jsx";
import { arrow } from "../floating-ui/middleware/arrow.jsx";
import { useIsoLayoutEffect } from "../use-iso-layout-effect.jsx";
import { useLatestRef } from "../use-latest-ref.jsx";
import { DEFAULT_SIDES } from "./adaptive-origin-middleware.jsx";

// ---------------------------------------------------------------------------
// Public types
// ---------------------------------------------------------------------------

/** Ref-like shape matching the React source; accepted for the `anchor` prop. */

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function resolve(input) {
  return typeof input === "function" ? input() : input;
}
function getLogicalSide(sideParam, renderedSide, isRtl) {
  const isLogicalSideParam = sideParam === "inline-start" || sideParam === "inline-end";
  const logicalRight = isRtl ? "inline-start" : "inline-end";
  const logicalLeft = isRtl ? "inline-end" : "inline-start";
  const map = {
    top: "top",
    right: isLogicalSideParam ? logicalRight : "right",
    bottom: "bottom",
    left: isLogicalSideParam ? logicalLeft : "left"
  };
  return map[renderedSide];
}
function getOffsetData(state, sideParam, isRtl) {
  const {
    rects,
    placement
  } = state;
  return {
    side: getLogicalSide(sideParam, getSide(placement), isRtl),
    align: getAlignment(placement) || "center",
    anchor: {
      width: rects.reference.width,
      height: rects.reference.height
    },
    positioner: {
      width: rects.floating.width,
      height: rects.floating.height
    }
  };
}
function isRef(param) {
  return param != null && typeof param === "object" && "current" in param;
}

// ---------------------------------------------------------------------------
// Default `hide` middleware — Solid port inlines the React source's
// `hideMiddleware.ts`. `@floating-ui/dom` exports a `hide` middleware that
// reports reference-hidden status; we wrap it with a zero-rect short-circuit
// so a freshly-mounted anchor with no layout yet still flags as hidden.
// ---------------------------------------------------------------------------

const hideMiddleware = {
  name: "hide",
  async fn(state) {
    const {
      width,
      height,
      x,
      y
    } = state.rects.reference;
    const anchorHidden = width === 0 && height === 0 && x === 0 && y === 0;
    const nativeResult = await nativeHide().fn(state);
    return {
      data: {
        referenceHidden: nativeResult.data?.referenceHidden || anchorHidden
      }
    };
  }
};

// ---------------------------------------------------------------------------
// The hook
// ---------------------------------------------------------------------------

/**
 * Orchestrates anchor positioning for Base UI popup compounds. Composes
 * `useFloating` + the `offset` / `flip` / `shift` / `size` / `arrow` /
 * `hide` / `adaptiveOrigin` middleware chain.
 *
 * All reactive inputs can be passed as accessors. Primitive-typed props
 * accept both forms — the hook normalizes internally. The returned
 * object is a collection of getters backed by signals, suitable for
 * reactive spreads in JSX or reads inside memos.
 */
export function useAnchorPositioning(params) {
  const {
    anchor,
    positionMethod,
    side: sideParamInput,
    sideOffset: sideOffsetInput,
    align: alignInput,
    alignOffset: alignOffsetInput,
    collisionBoundary: collisionBoundaryInput,
    collisionPadding: collisionPaddingInput,
    sticky: stickyInput,
    arrowPadding: arrowPaddingInput,
    disableAnchorTracking: disableAnchorTrackingInput,
    keepMounted: keepMountedInput,
    floatingRootContext,
    mounted: mountedInput,
    collisionAvoidance: collisionAvoidanceInput,
    shiftCrossAxis: shiftCrossAxisInput,
    nodeId,
    adaptiveOrigin: adaptiveOriginInput,
    lazyFlip: lazyFlipInput,
    externalTree
  } = params;
  const getSideParam = () => resolve(sideParamInput) ?? "bottom";
  const getAlign = () => resolve(alignInput) ?? "center";
  const getSideOffset = () => resolve(sideOffsetInput) ?? 0;
  const getAlignOffset = () => resolve(alignOffsetInput) ?? 0;
  const getCollisionBoundary = () => resolve(collisionBoundaryInput);
  const getCollisionPadding = () => resolve(collisionPaddingInput) ?? 5;
  const getSticky = () => resolve(stickyInput) ?? false;
  const getArrowPadding = () => resolve(arrowPaddingInput) ?? 5;
  const getDisableAnchorTracking = () => resolve(disableAnchorTrackingInput) ?? false;
  const getMounted = () => resolve(mountedInput);
  const getKeepMounted = () => resolve(keepMountedInput) ?? false;
  const getShiftCrossAxis = () => resolve(shiftCrossAxisInput) ?? false;
  const getLazyFlip = () => resolve(lazyFlipInput) ?? false;
  const getPositionMethod = () => resolve(positionMethod) ?? "absolute";
  const getAdaptiveOrigin = () => resolve(adaptiveOriginInput);
  const getCollisionAvoidance = () => resolve(collisionAvoidanceInput) ?? {};

  // `mountSide` freezes the physical side after the first successful
  // position when `lazyFlip` is true, so filter lists etc. don't flip
  // eagerly while typing.
  const [mountSide, setMountSide] = createSignal(null, {
    ownedWrite: true
  });
  const direction = useDirection();
  const isRtl = () => direction() === "rtl";

  // Side / placement are reactive. React computes them via render math; we
  // wrap in a memo so the middleware getter below and the `placement` input
  // to `useFloating` see the same value.
  const resolvedSide = createMemo(() => {
    const mounted = mountSide();
    if (mounted) return mounted;
    const sideParam = getSideParam();
    const rtl = isRtl();
    switch (sideParam) {
      case "inline-end":
        return rtl ? "left" : "right";
      case "inline-start":
        return rtl ? "right" : "left";
      default:
        return sideParam;
    }
  });
  const placement = createMemo(() => {
    const s = resolvedSide();
    const a = getAlign();
    return a === "center" ? s : `${s}-${a}`;
  });

  // Arrow ref shared with the `arrow` middleware. Consumers write through
  // `arrowRef.current = el` from a ref callback.
  const arrowRef = {
    current: null
  };

  // Latest-value refs for offsets so function-shaped offsets re-evaluate
  // each `computePosition` call against current closures. React uses
  // `useValueAsRef`; Solid's `useLatestRef` is the direct analog.
  const sideOffsetRef = useLatestRef(() => getSideOffset());
  const alignOffsetRef = useLatestRef(() => getAlignOffset());
  const mountedRef = useLatestRef(() => getMounted());

  // --- Collision padding with "iOS bias" — copies React's logic exactly.
  const computeCollisionPadding = () => {
    const bias = 1;
    const sideParam = getSideParam();
    const biasTop = sideParam === "bottom" ? bias : 0;
    const biasBottom = sideParam === "top" ? bias : 0;
    const biasLeft = sideParam === "right" ? bias : 0;
    const biasRight = sideParam === "left" ? bias : 0;
    const raw = getCollisionPadding();
    let padding;
    if (typeof raw === "number") {
      padding = {
        top: raw + biasTop,
        right: raw + biasRight,
        bottom: raw + biasBottom,
        left: raw + biasLeft
      };
    } else if (raw) {
      const rawObj = raw;
      padding = {
        top: (rawObj.top || 0) + biasTop,
        right: (rawObj.right || 0) + biasRight,
        bottom: (rawObj.bottom || 0) + biasBottom,
        left: (rawObj.left || 0) + biasLeft
      };
    } else {
      padding = {
        top: biasTop,
        right: biasRight,
        bottom: biasBottom,
        left: biasLeft
      };
    }
    return {
      padding,
      bias
    };
  };
  const middlewareMemo = createMemo(() => {
    const sideParam = getSideParam();
    const rtl = isRtl();
    const avoidance = getCollisionAvoidance();
    const avoidanceSide = avoidance.side || "flip";
    const avoidanceAlign = avoidance.align || "flip";
    const avoidanceFallback = avoidance.fallbackAxisSide || "end";
    const alignValue = getAlign();
    const sticky = getSticky();
    const shiftCrossAxis = getShiftCrossAxis();
    const {
      padding: collisionPadding,
      bias
    } = computeCollisionPadding();
    const boundary = getCollisionBoundary();
    const commonCollisionProps = {
      boundary: boundary === "clipping-ancestors" ? "clippingAncestors" : boundary,
      padding: collisionPadding
    };
    const list = [offset(state => {
      const data = getOffsetData(state, sideParam, rtl);
      const sideAxis = typeof sideOffsetRef.current === "function" ? sideOffsetRef.current(data) : sideOffsetRef.current;
      const alignAxis = typeof alignOffsetRef.current === "function" ? alignOffsetRef.current(data) : alignOffsetRef.current;
      return {
        mainAxis: sideAxis,
        crossAxis: alignAxis,
        alignmentAxis: alignAxis
      };
    })];
    const shiftDisabled = avoidanceAlign === "none" && avoidanceSide !== "shift";
    const crossAxisShiftEnabled = !shiftDisabled && (sticky || shiftCrossAxis || avoidanceSide === "shift");
    const flipMiddleware = avoidanceSide === "none" ? null : flip({
      ...commonCollisionProps,
      padding: {
        top: collisionPadding.top + bias,
        right: collisionPadding.right + bias,
        bottom: collisionPadding.bottom + bias,
        left: collisionPadding.left + bias
      },
      mainAxis: !shiftCrossAxis && avoidanceSide === "flip",
      crossAxis: avoidanceAlign === "flip" ? "alignment" : false,
      fallbackAxisSideDirection: avoidanceFallback
    });
    const shiftMiddleware = shiftDisabled ? null : shift(data => {
      const html = ownerDocument(data.elements.floating).documentElement;
      return {
        ...commonCollisionProps,
        rootBoundary: shiftCrossAxis ? {
          x: 0,
          y: 0,
          width: html.clientWidth,
          height: html.clientHeight
        } : undefined,
        mainAxis: avoidanceAlign !== "none",
        crossAxis: crossAxisShiftEnabled,
        limiter: sticky || shiftCrossAxis ? undefined : limitShift(limitData => {
          if (!arrowRef.current) {
            return {};
          }
          const {
            width,
            height
          } = arrowRef.current.getBoundingClientRect();
          const sideAxis = getSideAxis(getSide(limitData.placement));
          const arrowSize = sideAxis === "y" ? width : height;
          const offsetAmount = sideAxis === "y" ? collisionPadding.left + collisionPadding.right : collisionPadding.top + collisionPadding.bottom;
          return {
            offset: arrowSize / 2 + offsetAmount / 2
          };
        })
      };
    });

    // Match React's middleware ordering for shift vs flip. See
    // https://floating-ui.com/docs/flip#combining-with-shift
    const pushMaybe = mw => {
      if (mw) list.push(mw);
    };
    if (avoidanceSide === "shift" || avoidanceAlign === "shift" || alignValue === "center") {
      pushMaybe(shiftMiddleware);
      pushMaybe(flipMiddleware);
    } else {
      pushMaybe(flipMiddleware);
      pushMaybe(shiftMiddleware);
    }
    list.push(size({
      ...commonCollisionProps,
      apply({
        elements: {
          floating
        },
        availableWidth,
        availableHeight,
        rects
      }) {
        if (!mountedRef.current) {
          return;
        }
        const floatingStyle = floating.style;
        floatingStyle.setProperty("--available-width", `${availableWidth}px`);
        floatingStyle.setProperty("--available-height", `${availableHeight}px`);
        const dpr = ownerWindow(floating).devicePixelRatio || 1;
        const {
          x,
          y,
          width,
          height
        } = rects.reference;
        const anchorWidth = (Math.round((x + width) * dpr) - Math.round(x * dpr)) / dpr;
        const anchorHeight = (Math.round((y + height) * dpr) - Math.round(y * dpr)) / dpr;
        floatingStyle.setProperty("--anchor-width", `${anchorWidth}px`);
        floatingStyle.setProperty("--anchor-height", `${anchorHeight}px`);
      }
    }), arrow(() => ({
      // Fall back to a detached element so transform-origin math still
      // has something to read when the arrow isn't mounted yet.
      element: arrowRef.current || ownerDocument(arrowRef.current).createElement("div"),
      padding: getArrowPadding(),
      offsetParent: "floating"
    })), {
      name: "transformOrigin",
      fn(state) {
        const {
          elements,
          middlewareData,
          placement: renderedPlacement,
          rects,
          y
        } = state;
        const currentRenderedSide = getSide(renderedPlacement);
        const currentRenderedAxis = getSideAxis(currentRenderedSide);
        const arrowEl = arrowRef.current;
        const arrowData = middlewareData.arrow;
        const shiftData = middlewareData.shift;
        const arrowX = arrowData?.x || 0;
        const arrowY = arrowData?.y || 0;
        const arrowWidth = arrowEl?.clientWidth || 0;
        const arrowHeight = arrowEl?.clientHeight || 0;
        const transformX = arrowX + arrowWidth / 2;
        const transformY = arrowY + arrowHeight / 2;
        const shiftY = Math.abs(shiftData?.y || 0);
        const halfAnchorHeight = rects.reference.height / 2;
        const currentSideOffset = sideOffsetRef.current;
        const sideOffsetValue = typeof currentSideOffset === "function" ? currentSideOffset(getOffsetData(state, sideParam, rtl)) : currentSideOffset;
        const isOverlappingAnchor = shiftY > sideOffsetValue;
        const adjacent = {
          top: `${transformX}px calc(100% + ${sideOffsetValue}px)`,
          bottom: `${transformX}px ${-sideOffsetValue}px`,
          left: `calc(100% + ${sideOffsetValue}px) ${transformY}px`,
          right: `${-sideOffsetValue}px ${transformY}px`
        };
        const adjacentTransformOrigin = adjacent[currentRenderedSide];
        const overlapTransformOrigin = `${transformX}px ${rects.reference.y + halfAnchorHeight - y}px`;
        elements.floating.style.setProperty("--transform-origin", crossAxisShiftEnabled && currentRenderedAxis === "y" && isOverlappingAnchor ? overlapTransformOrigin : adjacentTransformOrigin);
        return {};
      }
    }, hideMiddleware);
    const adaptive = getAdaptiveOrigin();
    if (adaptive) list.push(adaptive);
    return list;
  });

  // Reset local `mountSide` if `mounted` flipped to false, matching React's
  // render-phase guard that clears stale state. Compute returns a tuple so
  // `mountSide()` is read in tracked scope; apply receives both values
  // without further reactive reads.
  useIsoLayoutEffect(() => [getMounted(), mountSide()], ([mounted, currentMountSide]) => {
    if (!mounted && currentMountSide !== null) {
      setMountSide(null);
    }
  });

  // If the popup isn't mounted yet but we have a root context, clear
  // reference-related slots so `autoUpdate` doesn't attach to stale
  // elements (React does the same with a `useIsoLayoutEffect`).
  useIsoLayoutEffect(() => ({
    mounted: getMounted(),
    root: floatingRootContext
  }), ({
    mounted,
    root
  }) => {
    if (!mounted && root) {
      root.update({
        referenceElement: null,
        floatingElement: null,
        domReferenceElement: null,
        positionReference: null
      });
    }
  });

  // `autoUpdate` options — only track layout shift / resize when
  // `disableAnchorTracking` is false.
  const autoUpdateOptions = createMemo(() => {
    const disabled = getDisableAnchorTracking();
    return {
      elementResize: !disabled && typeof ResizeObserver !== "undefined",
      layoutShift: !disabled && typeof IntersectionObserver !== "undefined"
    };
  });
  const floating = useFloating({
    rootContext: floatingRootContext,
    get placement() {
      return placement();
    },
    get strategy() {
      return getPositionMethod();
    },
    get middleware() {
      return middlewareMemo();
    },
    nodeId,
    externalTree
  });

  // Track anchor element. Mirrors React's split-effect: a layout-phase
  // effect handles element / virtual-element anchors, a regular effect
  // handles ref-shaped anchors (since parent refs populate after layout).
  const registeredAnchorRef = {
    current: null
  };
  const resolveAnchor = () => {
    const raw = resolve(anchor);
    if (raw == null) return null;
    const val = typeof raw === "function" ? raw() : raw;
    if (val == null) return null;
    if (isRef(val)) return val.current;
    return val;
  };
  useIsoLayoutEffect(() => ({
    mounted: getMounted(),
    anchor: resolveAnchor()
  }), ({
    mounted,
    anchor: resolved
  }) => {
    if (!mounted) return;
    if (resolved !== registeredAnchorRef.current) {
      floating.refs.setPositionReference(resolved);
      registeredAnchorRef.current = resolved;
    }
  });

  // `keepMounted` path: keep autoUpdate running while mounted. Solid's
  // `useFloating` already wires autoUpdate based on reference +
  // floating; we tack on a manual subscription for the keepMounted case
  // where consumers want to drive updates themselves.
  createEffect(() => ({
    keep: getKeepMounted(),
    mounted: getMounted(),
    dom: floating.elements.domReference,
    float: floating.elements.floating,
    options: autoUpdateOptions()
  }), ({
    keep,
    mounted,
    dom,
    float,
    options
  }) => {
    if (!keep || !mounted || !dom || !float) return undefined;
    return autoUpdate(dom, float, floating.update, options);
  });

  // Lazy flip: lock the rendered side once positioned.
  useIsoLayoutEffect(() => ({
    lazy: getLazyFlip(),
    mounted: getMounted(),
    isPositioned: floating.isPositioned,
    placement: floating.placement
  }), ({
    lazy,
    mounted,
    isPositioned,
    placement: p
  }) => {
    if (lazy && mounted && isPositioned) {
      setMountSide(getSide(p));
    }
  });

  // --- Derived reactive outputs ---------------------------------------

  const renderedSide = () => getSide(floating.placement);
  const logicalRenderedSide = () => getLogicalSide(getSideParam(), renderedSide(), isRtl());
  const renderedAlign = () => getAlignment(floating.placement) || "center";
  const anchorHidden = () => {
    const data = floating.middlewareData.hide;
    return Boolean(data?.referenceHidden);
  };
  const arrowStyles = createMemo(() => {
    const data = floating.middlewareData.arrow;
    const out = {
      position: "absolute"
    };
    if (data?.y !== undefined) out.top = `${data.y}px`;
    if (data?.x !== undefined) out.left = `${data.x}px`;
    return out;
  });
  const arrowUncentered = () => {
    const data = floating.middlewareData.arrow;
    return (data?.centerOffset ?? 0) !== 0;
  };
  const floatingStyles = createMemo(() => {
    const resolvedPosition = floating.isPositioned ? getPositionMethod() : "fixed";
    const adaptive = getAdaptiveOrigin();
    const adaptiveData = floating.middlewareData.adaptiveOrigin;
    const sideX = adaptiveData?.sideX || DEFAULT_SIDES.sideX;
    const sideY = adaptiveData?.sideY || DEFAULT_SIDES.sideY;
    const base = adaptive ? {
      position: resolvedPosition,
      [sideX]: `${floating.x}px`,
      [sideY]: `${floating.y}px`
    } : {
      position: resolvedPosition,
      top: "0",
      left: "0",
      transform: `translate(${Math.round(floating.x)}px, ${Math.round(floating.y)}px)`
    };
    if (!floating.isPositioned) {
      base.opacity = 0;
    }
    return base;
  });
  return {
    get positionerStyles() {
      return floatingStyles();
    },
    get arrowStyles() {
      return arrowStyles();
    },
    arrowRef,
    get arrowUncentered() {
      return arrowUncentered();
    },
    get side() {
      return logicalRenderedSide();
    },
    get align() {
      return renderedAlign();
    },
    get physicalSide() {
      return renderedSide();
    },
    get anchorHidden() {
      return anchorHidden();
    },
    refs: floating.refs,
    context: floating.context,
    get isPositioned() {
      return floating.isPositioned;
    },
    update: floating.update
  };
}
//# sourceMappingURL=use-anchor-positioning.jsx.map
