import { jsx } from "react/jsx-runtime";
import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState
} from "react";
import { getBoundingBox } from "ink";
import { useMouse } from "./MouseContext.js";
const ScrollContext = createContext(null);
const findScrollableCandidates = (mouseEvent, scrollables) => {
  const candidates = [];
  for (const entry of scrollables.values()) {
    if (!entry.ref.current || !entry.hasFocus()) {
      continue;
    }
    const boundingBox = getBoundingBox(entry.ref.current);
    if (!boundingBox) continue;
    const { x, y, width, height } = boundingBox;
    const isInside = mouseEvent.col >= x && mouseEvent.col < x + width + 1 && mouseEvent.row >= y && mouseEvent.row < y + height;
    if (isInside) {
      candidates.push({ ...entry, area: width * height });
    }
  }
  candidates.sort((a, b) => a.area - b.area);
  return candidates;
};
function useDragDetection(onSelectionAttempt) {
  const isLeftButtonDownRef = useRef(false);
  const selectionHintShownRef = useRef(false);
  const handleDragDetection = useCallback(
    (event) => {
      if (event.name === "left-press") {
        isLeftButtonDownRef.current = true;
        selectionHintShownRef.current = false;
      } else if (event.name === "left-release") {
        isLeftButtonDownRef.current = false;
      } else if (event.name === "move" && isLeftButtonDownRef.current) {
        if (!selectionHintShownRef.current && onSelectionAttempt) {
          selectionHintShownRef.current = true;
          onSelectionAttempt();
        }
      }
    },
    [onSelectionAttempt]
  );
  return handleDragDetection;
}
const ScrollProvider = ({ children, onSelectionAttempt }) => {
  const [scrollables, setScrollables] = useState(/* @__PURE__ */ new Map());
  const handleDragDetection = useDragDetection(onSelectionAttempt);
  const register = useCallback((entry) => {
    setScrollables((prev) => new Map(prev).set(entry.id, entry));
  }, []);
  const unregister = useCallback((id) => {
    setScrollables((prev) => {
      const next = new Map(prev);
      next.delete(id);
      return next;
    });
  }, []);
  const scrollablesRef = useRef(scrollables);
  useEffect(() => {
    scrollablesRef.current = scrollables;
  }, [scrollables]);
  const pendingScrollsRef = useRef(/* @__PURE__ */ new Map());
  const flushScheduledRef = useRef(false);
  const scheduleFlush = useCallback(() => {
    if (!flushScheduledRef.current) {
      flushScheduledRef.current = true;
      setTimeout(() => {
        flushScheduledRef.current = false;
        for (const [id, delta] of pendingScrollsRef.current.entries()) {
          const entry = scrollablesRef.current.get(id);
          if (entry) {
            entry.scrollBy(delta);
          }
        }
        pendingScrollsRef.current.clear();
      }, 0);
    }
  }, []);
  const handleScroll = useCallback(
    (direction, mouseEvent) => {
      const delta = direction === "up" ? -1 : 1;
      const candidates = findScrollableCandidates(mouseEvent, scrollablesRef.current);
      for (const candidate of candidates) {
        const { scrollTop, scrollHeight, innerHeight } = candidate.getScrollState();
        const pendingDelta = pendingScrollsRef.current.get(candidate.id) || 0;
        const effectiveScrollTop = scrollTop + pendingDelta;
        const canScrollUp = effectiveScrollTop > 1e-3;
        const canScrollDown = effectiveScrollTop < scrollHeight - innerHeight - 1e-3;
        if (direction === "up" && canScrollUp) {
          pendingScrollsRef.current.set(candidate.id, pendingDelta + delta);
          scheduleFlush();
          return true;
        }
        if (direction === "down" && canScrollDown) {
          pendingScrollsRef.current.set(candidate.id, pendingDelta + delta);
          scheduleFlush();
          return true;
        }
      }
      return false;
    },
    [scheduleFlush]
  );
  useMouse(
    useCallback(
      (event) => {
        if (event.name === "scroll-up") {
          return handleScroll("up", event);
        } else if (event.name === "scroll-down") {
          return handleScroll("down", event);
        }
        handleDragDetection(event);
        return false;
      },
      [handleScroll, handleDragDetection]
    ),
    { isActive: true }
  );
  const contextValue = useMemo(() => ({ register, unregister }), [register, unregister]);
  return /* @__PURE__ */ jsx(ScrollContext.Provider, { value: contextValue, children });
};
let nextId = 0;
const useScrollable = (entry, isActive) => {
  const context = useContext(ScrollContext);
  if (!context) {
    throw new Error("useScrollable must be used within a ScrollProvider");
  }
  const [id] = useState(() => `scrollable-${nextId++}`);
  const entryRef = useRef(entry);
  entryRef.current = entry;
  useEffect(() => {
    if (isActive) {
      const registrationEntry = {
        id,
        ref: entryRef.current.ref,
        getScrollState: () => entryRef.current.getScrollState(),
        scrollBy: (delta) => entryRef.current.scrollBy(delta),
        hasFocus: () => entryRef.current.hasFocus()
      };
      if (entryRef.current.scrollTo) {
        registrationEntry.scrollTo = (scrollTop, duration) => {
          entryRef.current.scrollTo?.(scrollTop, duration);
        };
      }
      context.register(registrationEntry);
      return () => {
        context.unregister(id);
      };
    }
    return;
  }, [context, id, isActive]);
};
export {
  ScrollProvider,
  useScrollable
};
