import { useCallback, useRef } from "react";

/**
 * Mobile bottom-sheet gesture. Function form `(onClose)` = drag down dismisses,
 * drag up expands (Settings/Details). Options form with `minimized`/`onMinimize`/
 * `onExpand` = drag down collapses to a peek, again from peek dismisses, drag up
 * expands the body (RF tool sheets). Imperative DOM ops so re-renders can't fight
 * the pointer.
 */
type SheetOpts = {
  onClose: () => void;
  minimized?: boolean;
  onMinimize?: () => void;
  onExpand?: () => void;
};

export function useBottomSheetGesture(arg: (() => void) | SheetOpts) {
  const optsRef = useRef<SheetOpts>(typeof arg === "function" ? { onClose: arg } : arg);
  optsRef.current = typeof arg === "function" ? { onClose: arg } : arg;

  const sheetRef = useRef<HTMLDivElement>(null);
  const startY = useRef(0);
  const dragging = useRef(false);
  const expandedRef = useRef(false);
  const preExpansionHeightRef = useRef<number | null>(null);

  const clearStyles = useCallback(() => {
    const sheet = sheetRef.current;
    if (!sheet) return;
    sheet.style.transform = "";
    sheet.style.transition = "";
    sheet.style.height = "";
    sheet.style.maxHeight = "";
    expandedRef.current = false;
    preExpansionHeightRef.current = null;
  }, []);

  // Double-reflow pattern: lock starting px height with no transition, flush;
  // re-enable transition, flush again so the property is committed before the
  // value change. Single-flush variants get batched and skip the animation.
  const animateHeightTo = useCallback(
    (targetPx: number, targetTransform: string) => {
      const sheet = sheetRef.current;
      if (!sheet) return;
      const currentHeightPx = sheet.getBoundingClientRect().height;

      sheet.style.transition = "none";
      sheet.style.height = `${currentHeightPx}px`;
      sheet.style.maxHeight = `${currentHeightPx}px`;
      void sheet.offsetHeight;

      sheet.style.transition =
        "transform 200ms ease-out, height 200ms ease-out, max-height 200ms ease-out";
      void sheet.offsetHeight;

      sheet.style.transform = targetTransform;
      sheet.style.height = `${targetPx}px`;
      sheet.style.maxHeight = `${targetPx}px`;
    },
    [],
  );

  const expand = useCallback(() => {
    const sheet = sheetRef.current;
    if (!sheet || expandedRef.current) return;
    expandedRef.current = true;
    preExpansionHeightRef.current = sheet.getBoundingClientRect().height;
    // window.innerHeight (px) instead of 100dvh — some mobile browsers skip
    // the interpolation when animating between px and viewport-unit lengths.
    animateHeightTo(window.innerHeight, "translateY(0)");
  }, [animateHeightTo]);

  const collapseFromExpanded = useCallback(() => {
    const sheet = sheetRef.current;
    if (!sheet) return;
    expandedRef.current = false;
    const targetPx = preExpansionHeightRef.current ?? sheet.getBoundingClientRect().height;
    animateHeightTo(targetPx, "translateY(0)");

    const onDone = () => {
      sheet.style.height = "";
      sheet.style.maxHeight = "";
      sheet.style.transition = "";
      preExpansionHeightRef.current = null;
    };
    const onTransitionEnd = (e: TransitionEvent) => {
      if (e.propertyName !== "height") return;
      sheet.removeEventListener("transitionend", onTransitionEnd);
      onDone();
    };
    sheet.addEventListener("transitionend", onTransitionEnd);
    window.setTimeout(() => {
      sheet.removeEventListener("transitionend", onTransitionEnd);
      onDone();
    }, 280);
  }, [animateHeightTo]);

  /** Settle the inline transform; a React state change (minimize/expand) re-renders the height. */
  const snapBack = useCallback(() => {
    const sheet = sheetRef.current;
    if (!sheet) return;
    sheet.style.transition = "transform 200ms ease-out";
    sheet.style.transform = "translateY(0)";
  }, []);

  const closeWithAnim = useCallback(() => {
    const sheet = sheetRef.current;
    if (!sheet) return;
    sheet.style.transition = "transform 200ms ease-in";
    sheet.style.transform = "translateY(100%)";
    let closed = false;
    const finishClose = () => { if (closed) return; closed = true; optsRef.current.onClose(); };
    sheet.addEventListener("transitionend", finishClose, { once: true });
    window.setTimeout(finishClose, 250); // fallback if transitionend never fires
  }, []);

  const onTouchStart = useCallback((e: React.TouchEvent) => {
    if (e.touches.length !== 1) return;
    dragging.current = true;
    startY.current = e.touches[0].clientY;
    if (sheetRef.current) {
      sheetRef.current.style.transition = "none";
    }
  }, []);

  const onTouchMove = useCallback((e: React.TouchEvent) => {
    if (!dragging.current) return;
    const touch = e.touches[0];
    if (!touch) return;
    const dy = touch.clientY - startY.current;
    // Up direction = rubber-band resistance (drag feels heavier).
    const visualDy = dy < 0 ? dy * 0.4 : dy;
    if (sheetRef.current) {
      sheetRef.current.style.transform = `translateY(${visualDy}px)`;
    }
  }, []);

  const onTouchEnd = useCallback((e: React.TouchEvent) => {
    if (!dragging.current) return;
    dragging.current = false;
    const sheet = sheetRef.current;
    if (!sheet) return;

    const touch = e.changedTouches[0];
    if (!touch) return;
    const dy = touch.clientY - startY.current;
    // Capped at 100px so a tall sheet still dismisses with a flick.
    const threshold = Math.min(sheet.offsetHeight * 0.2, 100);
    const { minimized, onMinimize, onExpand } = optsRef.current;

    if (dy > threshold) {
      // Dragged down: collapse expanded→peek first; from peek (or legacy) dismiss.
      if (onMinimize && !minimized) {
        onMinimize();
        snapBack();
      } else if (expandedRef.current) {
        collapseFromExpanded();
      } else {
        closeWithAnim();
      }
    } else if (dy < -40) {
      // Dragged up: expand the body (peek sheets) or grow to full height (legacy).
      if (onExpand) {
        onExpand();
        snapBack();
      } else if (!expandedRef.current) {
        expand();
      } else {
        snapBack();
      }
    } else {
      snapBack();
    }
  }, [snapBack, closeWithAnim, expand, collapseFromExpanded]);

  return { sheetRef, clearStyles, expand, onTouchStart, onTouchMove, onTouchEnd };
}
