import { MutableRefObject, useEffect, useRef } from 'react';
import { COMMIT_FRACTION, DEAD_ZONE_PX, EDGE_GUARD_PX, MIN_COMMIT_PX } from './geometry';

type Args = {
  // The sliding chat CARD element the listeners bind to. Touches outside it
  // never reach the gesture. Because the card itself translates, the finger
  // stays over it for the whole drag.
  rootRef: MutableRefObject<HTMLDivElement | null>;
  // Master gate. When false the effect binds NOTHING — a true no-op (used
  // when the back target is itself a detail screen, e.g. a thread, where the
  // animated card-pop would flash the list; header/hardware back stays).
  enabled: boolean;
  // Extra suppression while bound (e.g. an in-flight commit, an open media
  // viewer with its own horizontal paging). touchstart bails immediately
  // when true; cheaper than re-binding on every toggle.
  disabled: boolean;
  // Live drag reporter — px the card is dragged right (>=0) and whether a
  // drag is in flight. The parent drives the transform from this via
  // ref-written CSS vars, so the heavy chat subtree never re-renders.
  setDrag: (px: number, dragging: boolean) => void;
  // Commit = go back. The parent animates the card out then navigates.
  onBack: () => void;
};

// Rightward "swipe-to-go-back" (interactive pop) driver. Mirrors
// `useMobileTabsPagerGesture`: single listener on the card root, refs for
// live state, axis-resolve in the dead-zone, distance threshold-commit on
// release with snap-back below it. Differences: rightward-only (back, never
// forward — leftward clamps at 0). The card is `touch-action: pan-y`, so the
// browser reserves horizontal panning for us and our moves stay cancelable
// over the whole screen.
export function useSwipeBackGesture({ rootRef, enabled, disabled, setDrag, onBack }: Args): void {
  const disabledRef = useRef(disabled);
  const setDragRef = useRef(setDrag);
  const onBackRef = useRef(onBack);
  disabledRef.current = disabled;
  setDragRef.current = setDrag;
  onBackRef.current = onBack;

  useEffect(() => {
    const root = rootRef.current;
    if (!root || !enabled) return undefined;

    let startX: number | null = null;
    let startY: number | null = null;
    let engaged = false;
    let bailed = false;
    let lastDragPx = 0;

    const reset = () => {
      startX = null;
      startY = null;
      engaged = false;
      bailed = false;
      lastDragPx = 0;
    };

    // Snap any in-flight visual offset back to rest, THEN clear state. Used on
    // every abort/terminal path (multi-touch, disabled, browser-stole-scroll,
    // cancel) so a partial drag can never be left applied — otherwise a second
    // finger landing mid-drag would bail with `reset()` only and freeze the
    // card half-dismissed (it's `data-swipe-dragging`, so no transition heals
    // it).
    const springHome = () => {
      if (engaged || lastDragPx !== 0) setDragRef.current(0, false);
      reset();
    };

    const onTouchStart = (e: TouchEvent) => {
      if (disabledRef.current || e.touches.length !== 1) {
        springHome();
        return;
      }
      const t = e.touches[0];
      const vw = window.innerWidth;
      // The L/R edge strip belongs to the Android system back-gesture in
      // edge-to-edge mode. Ours shares its direction, so we MUST cede it —
      // a drag must start at least EDGE_GUARD_PX inside the left edge.
      if (t.clientX < EDGE_GUARD_PX || t.clientX > vw - EDGE_GUARD_PX) {
        springHome();
        return;
      }
      startX = t.clientX;
      startY = t.clientY;
      engaged = false;
      bailed = false;
      lastDragPx = 0;
    };

    const onTouchMove = (e: TouchEvent) => {
      if (e.touches.length !== 1 || disabledRef.current) {
        springHome();
        bailed = true;
        return;
      }
      if (startX === null || startY === null || bailed) return;
      const t = e.touches[0];
      const dx = t.clientX - startX;
      const dy = t.clientY - startY;

      if (!engaged) {
        if (Math.abs(dx) < DEAD_ZONE_PX && Math.abs(dy) < DEAD_ZONE_PX) return;
        // Ties go vertical — lets the timeline scroll and the profile /
        // members / media horseshoes (all vertical) win ambiguous gestures.
        if (Math.abs(dy) >= Math.abs(dx)) {
          bailed = true;
          return;
        }
        // Rightward-only: leftward-dominant horizontal isn't "back".
        if (dx <= 0) {
          bailed = true;
          return;
        }
        engaged = true;
      }

      // The card is `touch-action: pan-y`, so the browser reserves HORIZONTAL
      // panning for us — these touchmoves stay cancelable across the whole
      // screen (including over the vertical-scrolling timeline), so the
      // gesture engages everywhere, not just over non-scrollable spots. The
      // guard is defensive only.
      if (e.cancelable) e.preventDefault();

      const vw = window.innerWidth;
      // Follow the finger 1:1 to the right; clamp to [0, vw]. Never negative:
      // there is no forward navigation, and a sub-zero translate would expose
      // a sliver of the static base list on the card's RIGHT edge.
      const drag = Math.max(0, Math.min(vw, dx));
      lastDragPx = drag;
      setDragRef.current(drag, true);
    };

    const onTouchEnd = () => {
      if (!engaged || disabledRef.current) {
        springHome();
        return;
      }
      // Distance-only commit, exactly like the tab pager: drag past the
      // threshold to go back, otherwise snap home and STAY on the chat. No
      // velocity/fling — a short flick must not navigate (the user asked for
      // the pager's "didn't drag far enough → nothing opens" feel).
      const vw = window.innerWidth;
      const threshold = Math.max(MIN_COMMIT_PX, vw * COMMIT_FRACTION);
      if (lastDragPx >= threshold) {
        onBackRef.current();
        reset();
      } else {
        springHome();
      }
    };

    const onTouchCancel = () => {
      // System cancel (incoming call, scroll take-over, …) never commits.
      springHome();
    };

    root.addEventListener('touchstart', onTouchStart, { passive: true });
    root.addEventListener('touchmove', onTouchMove, { passive: false });
    root.addEventListener('touchend', onTouchEnd, { passive: true });
    root.addEventListener('touchcancel', onTouchCancel, { passive: true });
    return () => {
      root.removeEventListener('touchstart', onTouchStart);
      root.removeEventListener('touchmove', onTouchMove);
      root.removeEventListener('touchend', onTouchEnd);
      root.removeEventListener('touchcancel', onTouchCancel);
    };
    // setDrag / onBack are mirrored via refs so the listener never re-binds
    // mid-gesture; `enabled` is a real dep so toggling it binds/unbinds.
  }, [rootRef, enabled]);
}
