import { useState, useCallback, useRef, useLayoutEffect } from 'react';

interface PaneResizeHandlers {
  onPointerDown: (e: React.PointerEvent) => void;
  onPointerMove: (e: React.PointerEvent) => void;
  onPointerUp: () => void;
}

export function usePaneResize(
  initialSize: number,
  minSize: number,
  maxSize: number,
  side: 'left' | 'right' | 'top' | 'bottom'
): [number, PaneResizeHandlers, React.RefCallback<HTMLElement>] {
  const [size, setSize] = useState(initialSize);
  const draggingRef = useRef(false);
  const liveSizeRef = useRef(initialSize);
  const targetRef = useRef<HTMLElement | null>(null);
  const containerOffsetRef = useRef(0);
  const containerSizeRef = useRef(0);
  const rafRef = useRef<number | null>(null);

  const isVertical = side === 'top' || side === 'bottom';
  const styleProp = isVertical ? 'height' : 'width';

  const targetCallbackRef = useCallback(
    (el: HTMLElement | null) => {
      targetRef.current = el;
      if (el) {
        el.style[styleProp] = `${liveSizeRef.current}px`;
      }
    },
    [styleProp]
  );

  // Keep DOM in sync when React state commits (e.g. on pointerUp).
  // This ensures the ref-managed element always reflects the latest size
  // without relying on React-controlled style props.
  useLayoutEffect(() => {
    if (targetRef.current) {
      targetRef.current.style[styleProp] = `${size}px`;
    }
  }, [size, styleProp]);

  const computeSize = useCallback(
    (e: React.PointerEvent): number => {
      if (isVertical) {
        const total = containerSizeRef.current || window.innerHeight;
        const resolvedMax = maxSize <= 1 ? total * maxSize : maxSize;
        const pos = e.clientY - containerOffsetRef.current;
        return side === 'top'
          ? Math.min(resolvedMax, Math.max(minSize, pos))
          : Math.min(resolvedMax, Math.max(minSize, total - pos));
      } else {
        const resolvedMax = maxSize <= 1 ? window.innerWidth * maxSize : maxSize;
        return side === 'left'
          ? Math.min(resolvedMax, Math.max(minSize, e.clientX))
          : Math.min(resolvedMax, Math.max(minSize, window.innerWidth - e.clientX));
      }
    },
    [side, isVertical, minSize, maxSize]
  );

  const handlePointerDown = useCallback(
    (e: React.PointerEvent) => {
      e.preventDefault();
      (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
      draggingRef.current = true;
      if (isVertical) {
        const container = (e.currentTarget as HTMLElement).parentElement;
        if (container) {
          const rect = container.getBoundingClientRect();
          containerOffsetRef.current = rect.top;
          containerSizeRef.current = rect.height;
        }
      }
    },
    [isVertical]
  );

  const handlePointerMove = useCallback(
    (e: React.PointerEvent) => {
      if (!draggingRef.current) return;
      liveSizeRef.current = computeSize(e);
      if (targetRef.current) {
        // Batch DOM updates to once per animation frame — on mobile, pointermove
        // can fire multiple times per frame, and each direct style.height write
        // forces a synchronous reflow that desynchronises MapLibre and deck.gl.
        if (rafRef.current == null) {
          rafRef.current = requestAnimationFrame(() => {
            rafRef.current = null;
            if (targetRef.current) {
              targetRef.current.style[styleProp] = `${liveSizeRef.current}px`;
            }
          });
        }
      } else {
        setSize(liveSizeRef.current);
      }
    },
    [computeSize, styleProp]
  );

  const handlePointerUp = useCallback(() => {
    draggingRef.current = false;
    if (rafRef.current != null) {
      cancelAnimationFrame(rafRef.current);
      rafRef.current = null;
    }
    // Apply final size synchronously so the commit is immediate
    if (targetRef.current) {
      targetRef.current.style[styleProp] = `${liveSizeRef.current}px`;
    }
    setSize(liveSizeRef.current);
  }, [styleProp]);

  return [
    size,
    {
      onPointerDown: handlePointerDown,
      onPointerMove: handlePointerMove,
      onPointerUp: handlePointerUp,
    },
    targetCallbackRef,
  ];
}
