import { createMemo, createSignal } from "solid-js";
import { useBaseUiId } from "../../internals/base-ui-id.jsx";
import { contains } from "../../internals/dom/contains.jsx";
import { makeSlotRefDirective } from "../../internals/make-slot-ref-directive.jsx";
import { readOption } from "../../internals/maybe-accessor.jsx";
import { mergeRootProps } from "../../internals/merge-root-props.jsx";
import { SNAPSHOT_KEYS, snapshotKeys } from "../../internals/observability/snapshot.jsx";
import { useTimeout } from "../../internals/use-timeout.jsx";
import { SCROLL_TIMEOUT } from "../constants.jsx";
import { getOffset } from "../utils/get-offset.jsx";
import { scrollAreaDataAttrs } from "../utils/state-attrs.jsx";
import { ScrollAreaRootContext } from "./scroll-area-root-context.jsx";
import { ScrollAreaRootCssVars } from "./scroll-area-root-css-vars.jsx";
const DEFAULT_SIZE = {
  width: 0,
  height: 0
};
const DEFAULT_OVERFLOW_EDGES = {
  xStart: false,
  xEnd: false,
  yStart: false,
  yEnd: false
};
const DEFAULT_HIDDEN_STATE = {
  x: true,
  y: true,
  corner: true
};

/** Raw consumer shape for the overflow-edge threshold. */

function normalizeOverflowEdgeThreshold(threshold) {
  if (typeof threshold === "number") {
    const value = Math.max(0, threshold);
    return {
      xStart: value,
      xEnd: value,
      yStart: value,
      yEnd: value
    };
  }
  return {
    xStart: Math.max(0, threshold?.xStart ?? 0),
    xEnd: Math.max(0, threshold?.xEnd ?? 0),
    yStart: Math.max(0, threshold?.yStart ?? 0),
    yEnd: Math.max(0, threshold?.yEnd ?? 0)
  };
}

/**
 * Options for {@link createScrollArea}. Every reactive input is
 * `MaybeAccessor<T>`.
 */

/** Shape of attributes emitted by `c.root()`. */

/**
 * Public API returned by {@link createScrollArea}. A superset of
 * {@link ScrollAreaRootContextValue} — the api object IS the value published
 * to descendants via the root context, so `<ScrollArea.Viewport>` /
 * `<ScrollArea.Scrollbar>` / `<ScrollArea.Thumb>` keep reading it unchanged.
 *
 * On top of the context fields it adds the `root()` slot factory, a
 * `bindRoot` ref setter, a `Provider`, and the observability surface.
 *
 * **Inherently DOM-coupled.** Unlike `createSwitch`/`createCollapsible`, this
 * primitive owns no consumer logical state — its accessors reflect DOM
 * measurement (thumb/corner sizes, per-axis overflow) populated by the
 * viewport's `ResizeObserver` + scroll handler and the scrollbar/thumb drag
 * math. Bound to no elements it reports all-default (everything hidden, sizes
 * 0). It exists for parity / observability and to consolidate the root's
 * state machinery in one place, not as a standalone headless engine.
 */

/**
 * Solid 2.0-native scroll-area primitive. Owns the root's reactive state
 * (hover / scrolling / measured-scrollbar flags, thumb & corner sizes,
 * per-axis hidden state, overflow-edge flags), the element refs every part
 * reads, and the shared pointer/scroll handlers. The viewport's measurement
 * pipeline and the scrollbar/thumb drag math compose against the refs and
 * setters exposed here.
 *
 * Must be called in an owner scope (component body or `createRoot`) — it
 * allocates timeouts that install cleanups.
 */
export function createScrollArea(options = {}) {
  const rootId = useBaseUiId();
  const scrollYTimeout = useTimeout();
  const scrollXTimeout = useTimeout();
  const [hovering, setHovering] = createSignal(false, {
    name: "scroll-area.hovering",
    ownedWrite: true
  });
  const [scrollingX, setScrollingX] = createSignal(false, {
    name: "scroll-area.scrollingX",
    ownedWrite: true
  });
  const [scrollingY, setScrollingY] = createSignal(false, {
    name: "scroll-area.scrollingY",
    ownedWrite: true
  });
  const [hasMeasuredScrollbar, setHasMeasuredScrollbar] = createSignal(false, {
    name: "scroll-area.hasMeasuredScrollbar",
    ownedWrite: true
  });
  const [cornerSize, setCornerSize] = createSignal(DEFAULT_SIZE, {
    name: "scroll-area.cornerSize",
    ownedWrite: true
  });
  const [thumbSize, setThumbSize] = createSignal(DEFAULT_SIZE, {
    name: "scroll-area.thumbSize",
    ownedWrite: true
  });
  const [overflowEdges, setOverflowEdges] = createSignal(DEFAULT_OVERFLOW_EDGES, {
    name: "scroll-area.overflowEdges",
    ownedWrite: true
  });
  const [hiddenState, setHiddenState] = createSignal(DEFAULT_HIDDEN_STATE, {
    name: "scroll-area.hiddenState",
    ownedWrite: true
  });
  const rootRef = {
    current: null
  };
  const viewportRef = {
    current: null
  };
  const scrollbarYRef = {
    current: null
  };
  const scrollbarXRef = {
    current: null
  };
  const thumbYRef = {
    current: null
  };
  const thumbXRef = {
    current: null
  };
  const cornerRef = {
    current: null
  };

  // Imperative thumb-drag state. Not reactive — lives for the duration of a
  // single pointer gesture. Matches the React ref pattern 1:1.
  let thumbDragging = false;
  let startY = 0;
  let startX = 0;
  let startScrollTop = 0;
  let startScrollLeft = 0;
  let currentOrientation = "vertical";
  const scrollPositionRef = {
    x: 0,
    y: 0
  };
  function handleScroll(scrollPosition) {
    const offsetX = scrollPosition.x - scrollPositionRef.x;
    const offsetY = scrollPosition.y - scrollPositionRef.y;
    scrollPositionRef.x = scrollPosition.x;
    scrollPositionRef.y = scrollPosition.y;
    if (offsetY !== 0) {
      setScrollingY(true);
      scrollYTimeout.start(SCROLL_TIMEOUT, () => {
        setScrollingY(false);
      });
    }
    if (offsetX !== 0) {
      setScrollingX(true);
      scrollXTimeout.start(SCROLL_TIMEOUT, () => {
        setScrollingX(false);
      });
    }
  }
  function handlePointerDown(event) {
    if (event.button !== 0) return;
    thumbDragging = true;
    startY = event.clientY;
    startX = event.clientX;
    const target = event.currentTarget;
    const orientationAttr = target?.getAttribute("data-orientation");
    currentOrientation = orientationAttr === "horizontal" ? "horizontal" : "vertical";
    if (viewportRef.current) {
      startScrollTop = viewportRef.current.scrollTop;
      startScrollLeft = viewportRef.current.scrollLeft;
    }
    if (thumbYRef.current && currentOrientation === "vertical") {
      thumbYRef.current.setPointerCapture(event.pointerId);
    }
    if (thumbXRef.current && currentOrientation === "horizontal") {
      thumbXRef.current.setPointerCapture(event.pointerId);
    }
  }
  function handlePointerMove(event) {
    if (!thumbDragging) return;
    const deltaY = event.clientY - startY;
    const deltaX = event.clientX - startX;
    const viewportEl = viewportRef.current;
    if (!viewportEl) return;
    const scrollableContentHeight = viewportEl.scrollHeight;
    const viewportHeight = viewportEl.clientHeight;
    const scrollableContentWidth = viewportEl.scrollWidth;
    const viewportWidth = viewportEl.clientWidth;
    if (thumbYRef.current && scrollbarYRef.current && currentOrientation === "vertical") {
      const scrollbarYOffset = getOffset(scrollbarYRef.current, "padding", "y");
      const thumbYOffset = getOffset(thumbYRef.current, "margin", "y");
      const thumbHeight = thumbYRef.current.offsetHeight;
      const maxThumbOffsetY = scrollbarYRef.current.offsetHeight - thumbHeight - scrollbarYOffset - thumbYOffset;
      const scrollRatioY = deltaY / maxThumbOffsetY;
      viewportEl.scrollTop = startScrollTop + scrollRatioY * (scrollableContentHeight - viewportHeight);
      event.preventDefault();
      setScrollingY(true);
      scrollYTimeout.start(SCROLL_TIMEOUT, () => {
        setScrollingY(false);
      });
    }
    if (thumbXRef.current && scrollbarXRef.current && currentOrientation === "horizontal") {
      const scrollbarXOffset = getOffset(scrollbarXRef.current, "padding", "x");
      const thumbXOffset = getOffset(thumbXRef.current, "margin", "x");
      const thumbWidth = thumbXRef.current.offsetWidth;
      const maxThumbOffsetX = scrollbarXRef.current.offsetWidth - thumbWidth - scrollbarXOffset - thumbXOffset;
      const scrollRatioX = deltaX / maxThumbOffsetX;
      viewportEl.scrollLeft = startScrollLeft + scrollRatioX * (scrollableContentWidth - viewportWidth);
      event.preventDefault();
      setScrollingX(true);
      scrollXTimeout.start(SCROLL_TIMEOUT, () => {
        setScrollingX(false);
      });
    }
  }
  function handlePointerUp(event) {
    thumbDragging = false;
    if (thumbYRef.current && currentOrientation === "vertical") {
      thumbYRef.current.releasePointerCapture(event.pointerId);
    }
    if (thumbXRef.current && currentOrientation === "horizontal") {
      thumbXRef.current.releasePointerCapture(event.pointerId);
    }
  }
  function handlePointerEnterOrMove(event) {
    if (event.pointerType !== "touch") {
      const isTargetRootChild = contains(rootRef.current, event.target);
      setHovering(isTargetRootChild);
    }
  }
  const state = createMemo(() => {
    const edges = overflowEdges();
    const hidden = hiddenState();
    return {
      scrolling: scrollingX() || scrollingY(),
      hasOverflowX: !hidden.x,
      hasOverflowY: !hidden.y,
      overflowXStart: edges.xStart,
      overflowXEnd: edges.xEnd,
      overflowYStart: edges.yStart,
      overflowYEnd: edges.yEnd,
      cornerHidden: hidden.corner
    };
  }, {
    name: "scroll-area.state"
  });

  // ---------- root slot ----------
  const rootInternal = {
    role: "presentation",
    onPointerEnter: handlePointerEnterOrMove,
    onPointerMove: handlePointerEnterOrMove,
    onPointerLeave: () => {
      setHovering(false);
    },
    get style() {
      const size = cornerSize();
      return {
        position: "relative",
        [ScrollAreaRootCssVars.scrollAreaCornerHeight]: `${size.height}px`,
        [ScrollAreaRootCssVars.scrollAreaCornerWidth]: `${size.width}px`
      };
    }
  };
  const root = userProps => {
    const merged = mergeRootProps(rootInternal, scrollAreaDataAttrs(state));
    return mergeRootProps(merged, userProps);
  };
  const bindRoot = el => {
    rootRef.current = el;
  };
  const rootRefDirective = () => {
    const baseDirective = makeSlotRefDirective(() => root());
    return el => {
      bindRoot(el);
      baseDirective(el);
    };
  };

  // ---------- assemble api ----------
  const api = {
    handlePointerDown,
    handlePointerMove,
    handlePointerUp,
    handleScroll,
    cornerSize,
    setCornerSize,
    thumbSize,
    setThumbSize,
    hasMeasuredScrollbar,
    setHasMeasuredScrollbar,
    cornerRef,
    scrollingX,
    setScrollingX,
    scrollingY,
    setScrollingY,
    hovering,
    setHovering,
    viewportRef,
    rootRef,
    scrollbarYRef,
    scrollbarXRef,
    thumbYRef,
    thumbXRef,
    rootId,
    hiddenState,
    setHiddenState,
    overflowEdges,
    setOverflowEdges,
    viewportState: state,
    get overflowEdgeThreshold() {
      return normalizeOverflowEdgeThreshold(readOption(options.overflowEdgeThreshold));
    },
    root,
    bindRoot,
    rootRefDirective,
    Provider: providerProps => <ScrollAreaRootContext value={api}>
				{providerProps.children}
			</ScrollAreaRootContext>
  };
  api[SNAPSHOT_KEYS] = SCROLL_AREA_SNAPSHOT_KEYS;
  return api;
}
const SCROLL_AREA_SNAPSHOT_KEYS = snapshotKeys("hovering", "scrollingX", "scrollingY", "hasMeasuredScrollbar", "cornerSize", "thumbSize", "hiddenState", "overflowEdges");
//# sourceMappingURL=create-scroll-area.jsx.map
