import { useCallback, useEffect, useRef, useState, type RefCallback } from 'react';

interface ZoomPanState {
  scale: number;
  translateX: number;
  translateY: number;
}

const MIN_SCALE = 0.25;
const MAX_SCALE = 4.0;
const ZOOM_STEP = 0.15;
const FIT_PADDING = 20;
/** Extra top offset to clear the optional drill-down breadcrumb bar */
const TOP_OFFSET = 40;

export function useZoomPan() {
  const [state, setState] = useState<ZoomPanState>({
    scale: 1,
    translateX: 0,
    translateY: 0,
  });
  const isPanning = useRef(false);
  const didPan = useRef(false);
  const panStart = useRef({ x: 0, y: 0 });
  const containerRef = useRef<HTMLDivElement>(null);
  const svgElRef = useRef<SVGSVGElement | null>(null);
  const [svgReady, setSvgReady] = useState(false);

  const svgRef: RefCallback<SVGSVGElement> = useCallback((el: SVGSVGElement | null) => {
    svgElRef.current = el;
    setSvgReady(!!el);
  }, []);

  const clampScale = (s: number) => Math.min(MAX_SCALE, Math.max(MIN_SCALE, s));

  /** Returns the CSS transform string for the content <g> element. */
  const transform = `translate(${state.translateX}px, ${state.translateY}px) scale(${state.scale})`;

  // Attach wheel listener with { passive: false } so preventDefault() stops page scroll.
  // React's onWheel is passive by default and cannot prevent scrolling.
  // Re-runs when the SVG element becomes available (e.g. after loading state clears).
  useEffect(() => {
    const svg = svgElRef.current;
    if (!svg) return;
    const handler = (e: WheelEvent) => {
      e.preventDefault();
      const direction = e.deltaY < 0 ? 1 : -1;
      const factor = 1 + direction * ZOOM_STEP;

      const rect = svg.getBoundingClientRect();
      const cursorX = e.clientX - rect.left;
      const cursorY = e.clientY - rect.top;

      setState(prev => {
        const newScale = clampScale(prev.scale * factor);
        const scaleRatio = newScale / prev.scale;
        return {
          scale: newScale,
          translateX: cursorX - scaleRatio * (cursorX - prev.translateX),
          translateY: cursorY - scaleRatio * (cursorY - prev.translateY),
        };
      });
    };
    svg.addEventListener('wheel', handler, { passive: false });
    return () => svg.removeEventListener('wheel', handler);
  }, [svgReady]);

  const onPointerDown = useCallback(
    (e: React.PointerEvent<SVGSVGElement>) => {
      // Always allow drag-to-pan, even over nodes. Click vs drag is
      // distinguished by didPan: if the pointer moved, it's a pan and
      // node click handlers should be suppressed.
      isPanning.current = true;
      didPan.current = false;
      panStart.current = { x: e.clientX - state.translateX, y: e.clientY - state.translateY };
      // Only capture pointer on empty space — capturing on nodes would
      // redirect click/dblclick to the SVG, breaking node interactions
      if (!(e.target as Element).closest('[data-node-id]')) {
        (e.currentTarget as SVGSVGElement).setPointerCapture(e.pointerId);
      }
    },
    [state.translateX, state.translateY],
  );

  const onPointerMove = useCallback(
    (e: React.PointerEvent<SVGSVGElement>) => {
      if (!isPanning.current) return;
      didPan.current = true;
      setState(prev => ({
        ...prev,
        translateX: e.clientX - panStart.current.x,
        translateY: e.clientY - panStart.current.y,
      }));
    },
    [],
  );

  const onPointerUp = useCallback(
    (e: React.PointerEvent<SVGSVGElement>) => {
      isPanning.current = false;
      const svg = e.currentTarget as SVGSVGElement;
      if (svg.hasPointerCapture(e.pointerId)) {
        svg.releasePointerCapture(e.pointerId);
      }
    },
    [],
  );

  const zoomIn = useCallback(() => {
    setState(prev => {
      const newScale = clampScale(prev.scale * (1 + ZOOM_STEP));
      const container = containerRef.current;
      if (!container) return { ...prev, scale: newScale };
      const cx = container.clientWidth / 2;
      const cy = container.clientHeight / 2;
      const ratio = newScale / prev.scale;
      return {
        scale: newScale,
        translateX: cx - ratio * (cx - prev.translateX),
        translateY: cy - ratio * (cy - prev.translateY),
      };
    });
  }, []);

  const zoomOut = useCallback(() => {
    setState(prev => {
      const newScale = clampScale(prev.scale * (1 - ZOOM_STEP));
      const container = containerRef.current;
      if (!container) return { ...prev, scale: newScale };
      const cx = container.clientWidth / 2;
      const cy = container.clientHeight / 2;
      const ratio = newScale / prev.scale;
      return {
        scale: newScale,
        translateX: cx - ratio * (cx - prev.translateX),
        translateY: cy - ratio * (cy - prev.translateY),
      };
    });
  }, []);

  const panTo = useCallback((tx: number, ty: number) => {
    setState(prev => ({ ...prev, translateX: tx, translateY: ty }));
  }, []);

  const resetView = useCallback(() => {
    setState({ scale: 1, translateX: FIT_PADDING, translateY: TOP_OFFSET });
  }, []);

  const fitToView = useCallback(
    (contentWidth: number, contentHeight: number) => {
      const container = containerRef.current;
      if (!container) return;
      const cw = container.clientWidth - FIT_PADDING * 2;
      const ch = container.clientHeight - FIT_PADDING * 2;
      if (contentWidth <= 0 || contentHeight <= 0) return;
      const scaleX = cw / contentWidth;
      const scaleY = ch / contentHeight;
      const newScale = clampScale(Math.min(scaleX, scaleY));
      setState({ scale: newScale, translateX: FIT_PADDING, translateY: FIT_PADDING });
    },
    [],
  );

  const onKeyDown = useCallback(
    (e: React.KeyboardEvent, contentWidth: number, contentHeight: number) => {
      switch (e.key) {
        case '+':
        case '=':
          e.preventDefault();
          zoomIn();
          break;
        case '-':
          e.preventDefault();
          zoomOut();
          break;
        case '0':
          e.preventDefault();
          fitToView(contentWidth, contentHeight);
          break;
      }
    },
    [zoomIn, zoomOut, fitToView],
  );

  return {
    state,
    containerRef,
    svgRef,
    transform,
    /** True if the last pointer gesture was a drag (not a click). Check in click handlers. */
    didPan,
    panTo,
    resetView,
    onPointerDown,
    onPointerMove,
    onPointerUp,
    zoomIn,
    zoomOut,
    fitToView,
    onKeyDown,
  };
}
