/**
 * `useDrawerSnapPoints` — resolves the configured snap points against the
 * measured popup / viewport geometry, derives offsets, and picks the
 * currently-active snap point.
 *
 * 1:1 port of Base UI React's
 * `packages/react/src/drawer/root/useDrawerSnapPoints.ts`. Solid deviations:
 *
 * - React's `useStableCallback` inside `measureViewportHeight` becomes a
 *   plain function — Solid has no render cycle, callbacks close over the
 *   current signals directly.
 * - `useIsoLayoutEffect` uses the split compute/apply form.
 * - Under happy-dom (test env), `offsetHeight` / `ResizeObserver` return 0
 *   / are absent, so `resolvedSnapPoints` is always `[]` and
 *   `activeSnapPointOffset` is `null`. The test suite exercises the
 *   structural contract; a real-browser pass is required for geometry
 *   correctness.
 */

import { createMemo, createSignal, untrack } from "solid-js";
import { useDialogRootContext } from "../../dialog/dialog-root-context.jsx";
import { clamp } from "../../internals/slider-utils/clamp.jsx";
import { useIsoLayoutEffect } from "../../internals/use-iso-layout-effect.jsx";
import { useDrawerRootContext } from "./drawer-root-context.jsx";
function resolveSnapPointValue(snapPoint, viewportHeight, rootFontSize) {
  if (!Number.isFinite(viewportHeight) || viewportHeight <= 0) return null;
  if (typeof snapPoint === "number") {
    if (!Number.isFinite(snapPoint)) return null;
    if (snapPoint <= 1) return clamp(snapPoint, 0, 1) * viewportHeight;
    return snapPoint;
  }
  const trimmed = snapPoint.trim();
  if (trimmed.endsWith("px")) {
    const value = Number.parseFloat(trimmed);
    return Number.isFinite(value) ? value : null;
  }
  if (trimmed.endsWith("rem")) {
    const value = Number.parseFloat(trimmed);
    return Number.isFinite(value) ? value * rootFontSize : null;
  }
  return null;
}
function findClosestSnapPoint(height, points) {
  let closest = null;
  let closestDistance = Infinity;
  for (const point of points) {
    const distance = Math.abs(point.height - height);
    if (distance < closestDistance) {
      closestDistance = distance;
      closest = point;
    }
  }
  return closest;
}
export function useDrawerSnapPoints() {
  const rootCtx = useDialogRootContext();
  const drawer = useDrawerRootContext();
  const viewportElement = rootCtx.viewportElement;
  const [viewportHeight, setViewportHeight] = createSignal(0, {
    ownedWrite: true
  });
  const [rootFontSize, setRootFontSize] = createSignal(16, {
    ownedWrite: true
  });

  // `measureViewportHeight` is invoked from the iso-layout-effect apply
  // scope (and from a `ResizeObserver` callback). The `viewportElement()`
  // snapshot read is intentional — wrap in `untrack` so it doesn't trip
  // `[STRICT_READ_UNTRACKED]` when called synchronously from apply.
  const measureViewportHeight = () => untrack(() => {
    const el = viewportElement();
    const doc = el?.ownerDocument ?? document;
    const html = doc.documentElement;
    if (el) setViewportHeight(el.offsetHeight);else setViewportHeight(html.clientHeight);
    const fontSize = Number.parseFloat(getComputedStyle(html).fontSize);
    if (Number.isFinite(fontSize)) setRootFontSize(fontSize);
  });
  useIsoLayoutEffect(() => viewportElement(), el => {
    measureViewportHeight();
    if (!el || typeof ResizeObserver !== "function") return undefined;
    const resizeObserver = new ResizeObserver(measureViewportHeight);
    resizeObserver.observe(el);
    return () => {
      resizeObserver.disconnect();
    };
  });
  const resolvedSnapPoints = createMemo(() => {
    const snapPoints = drawer.snapPoints();
    const popupHeight = drawer.popupHeight();
    const viewportH = viewportHeight();
    const fontSize = rootFontSize();
    if (!snapPoints || snapPoints.length === 0 || viewportH <= 0 || popupHeight <= 0) return [];
    const maxHeight = Math.min(popupHeight, viewportH);
    if (!Number.isFinite(maxHeight) || maxHeight <= 0) return [];
    const resolved = snapPoints.map(value => {
      const resolvedHeight = resolveSnapPointValue(value, viewportH, fontSize);
      if (resolvedHeight === null || !Number.isFinite(resolvedHeight)) return null;
      const clampedHeight = clamp(resolvedHeight, 0, maxHeight);
      return {
        value,
        height: clampedHeight,
        offset: Math.max(0, popupHeight - clampedHeight)
      };
    }).filter(p => Boolean(p));
    if (resolved.length <= 1) return resolved;
    const deduped = [];
    const seenHeights = [];
    for (let i = resolved.length - 1; i >= 0; i -= 1) {
      const p = resolved[i];
      if (!p) continue;
      const dup = seenHeights.some(h => Math.abs(h - p.height) <= 1);
      if (dup) continue;
      seenHeights.push(p.height);
      deduped.push(p);
    }
    deduped.reverse();
    return deduped;
  });
  const resolvedActiveSnapPoint = createMemo(() => {
    const active = drawer.activeSnapPoint();
    const points = resolvedSnapPoints();
    if (active === undefined) return points[0];
    if (active === null) return undefined;
    const exact = points.find(p => Object.is(p.value, active));
    if (exact) return exact;
    const popupHeight = drawer.popupHeight();
    const viewportH = viewportHeight();
    const maxHeight = Math.min(popupHeight, viewportH);
    const resolvedHeight = resolveSnapPointValue(active, viewportH, rootFontSize());
    if (resolvedHeight === null || !Number.isFinite(resolvedHeight)) return undefined;
    const clampedHeight = clamp(resolvedHeight, 0, maxHeight);
    return findClosestSnapPoint(clampedHeight, points) ?? undefined;
  });
  return {
    snapPoints: drawer.snapPoints,
    activeSnapPoint: drawer.activeSnapPoint,
    setActiveSnapPoint: drawer.setActiveSnapPoint,
    popupHeight: drawer.popupHeight,
    viewportHeight,
    resolvedSnapPoints,
    activeSnapPointOffset: () => resolvedActiveSnapPoint()?.offset ?? null
  };
}
//# sourceMappingURL=use-drawer-snap-points.jsx.map
