import { MouseEventHandler, TouchEventHandler, useEffect, useRef, useState } from 'react';

export type Pan = {
  translateX: number;
  translateY: number;
};

const INITIAL_PAN = {
  translateX: 0,
  translateY: 0,
};

export const usePan = (active: boolean) => {
  const [pan, setPan] = useState<Pan>(INITIAL_PAN);
  const [cursor, setCursor] = useState<'grab' | 'grabbing' | 'initial'>(
    active ? 'grab' : 'initial',
  );

  // Track the exact handler references that were passed to addEventListener so
  // we can remove them even if the component re-renders or unmounts mid-drag.
  const attachedRef = useRef<{ move: (e: MouseEvent) => void; up: (e: MouseEvent) => void } | null>(
    null,
  );
  const touchAttachedRef = useRef<{
    move: (e: TouchEvent) => void;
    end: (e: TouchEvent) => void;
  } | null>(null);

  useEffect(() => {
    setCursor(active ? 'grab' : 'initial');
  }, [active]);

  const handleMouseDown: MouseEventHandler<HTMLElement> = (evt) => {
    if (!active) return;
    evt.preventDefault();
    setCursor('grabbing');

    const handleMouseMove = (e: MouseEvent) => {
      e.preventDefault();
      e.stopPropagation();
      setPan((p) => ({
        translateX: p.translateX + e.movementX,
        translateY: p.translateY + e.movementY,
      }));
    };

    const handleMouseUp = (e: MouseEvent) => {
      e.preventDefault();
      setCursor('grab');
      document.removeEventListener('mousemove', handleMouseMove);
      document.removeEventListener('mouseup', handleMouseUp);
      attachedRef.current = null;
    };

    attachedRef.current = { move: handleMouseMove, up: handleMouseUp };
    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);
  };

  // Touch equivalent so a zoomed image can be dragged on a phone. Single-finger
  // only (ignore multi-touch / pinch); touch events carry no movementX/Y, so we
  // derive the delta from the previous touch position.
  const handleTouchStart: TouchEventHandler<HTMLElement> = (evt) => {
    if (!active || evt.touches.length !== 1) return;
    setCursor('grabbing');
    let lastX = evt.touches[0].clientX;
    let lastY = evt.touches[0].clientY;

    const handleTouchMove = (e: TouchEvent) => {
      if (e.touches.length !== 1) return;
      e.preventDefault();
      const t = e.touches[0];
      const dx = t.clientX - lastX;
      const dy = t.clientY - lastY;
      lastX = t.clientX;
      lastY = t.clientY;
      setPan((p) => ({ translateX: p.translateX + dx, translateY: p.translateY + dy }));
    };

    const handleTouchEnd = () => {
      setCursor('grab');
      document.removeEventListener('touchmove', handleTouchMove);
      document.removeEventListener('touchend', handleTouchEnd);
      document.removeEventListener('touchcancel', handleTouchEnd);
      touchAttachedRef.current = null;
    };

    touchAttachedRef.current = { move: handleTouchMove, end: handleTouchEnd };
    document.addEventListener('touchmove', handleTouchMove, { passive: false });
    document.addEventListener('touchend', handleTouchEnd);
    document.addEventListener('touchcancel', handleTouchEnd);
  };

  useEffect(() => {
    if (!active) setPan(INITIAL_PAN);
  }, [active]);

  // Remove listeners if the component unmounts while a drag is in progress.
  useEffect(
    () => () => {
      if (attachedRef.current) {
        document.removeEventListener('mousemove', attachedRef.current.move);
        document.removeEventListener('mouseup', attachedRef.current.up);
        attachedRef.current = null;
      }
      if (touchAttachedRef.current) {
        document.removeEventListener('touchmove', touchAttachedRef.current.move);
        document.removeEventListener('touchend', touchAttachedRef.current.end);
        document.removeEventListener('touchcancel', touchAttachedRef.current.end);
        touchAttachedRef.current = null;
      }
    },
    [],
  );

  return {
    pan,
    cursor,
    onMouseDown: handleMouseDown,
    onTouchStart: handleTouchStart,
  };
};
