import { useCallback, useRef } from 'react';
import type { Point } from '@house-plan-maker/shared';
import type Konva from 'konva';
import { clamp } from '../utils/geometry';
import { MIN_ZOOM, MAX_ZOOM, ZOOM_SENSITIVITY, DEFAULT_ZOOM } from '../types';

interface UseEditorZoomOptions {
  readonly zoom: number;
  readonly panOffset: Point;
  readonly onZoomChange: (zoom: number) => void;
  readonly onPanChange: (offset: Point) => void;
}

interface UseEditorZoomResult {
  /** Handle mouse wheel for zooming. Attach to Stage. */
  handleWheel(e: Konva.KonvaEventObject<WheelEvent>): void;
  /** Convert screen pixel coordinates to world (meter) coordinates. */
  screenToWorld(screenX: number, screenY: number): Point;
  /** Convert world (meter) coordinates to screen pixel coordinates. */
  worldToScreen(worldX: number, worldY: number): Point;
  /** Zoom in by a fixed step. */
  zoomIn(): void;
  /** Zoom out by a fixed step. */
  zoomOut(): void;
  /** Reset zoom and pan to fit the stage. */
  resetView(): void;
  /** Whether middle mouse is currently panning. */
  isPanningRef: React.RefObject<boolean>;
  /** Handle mouse down for panning (middle mouse button). */
  handlePanStart(e: Konva.KonvaEventObject<MouseEvent>): void;
  /** Start panning from left mouse button (empty space click). */
  startLeftMousePan(e: Konva.KonvaEventObject<MouseEvent>): void;
  /** Handle mouse move for panning. */
  handlePanMove(e: Konva.KonvaEventObject<MouseEvent>): void;
  /** Handle mouse up to end panning. */
  handlePanEnd(): void;
}

const ZOOM_STEP_FACTOR = 1.2;

export function useEditorZoom({
  zoom,
  panOffset,
  onZoomChange,
  onPanChange,
}: UseEditorZoomOptions): UseEditorZoomResult {
  const isPanningRef = useRef(false);
  const lastPanPointRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
  const panAccumRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });

  const screenToWorld = useCallback(
    (screenX: number, screenY: number): Point => ({
      x: (screenX - panOffset.x) / zoom,
      y: (screenY - panOffset.y) / zoom,
    }),
    [zoom, panOffset],
  );

  const worldToScreen = useCallback(
    (worldX: number, worldY: number): Point => ({
      x: worldX * zoom + panOffset.x,
      y: worldY * zoom + panOffset.y,
    }),
    [zoom, panOffset],
  );

  const handleWheel = useCallback(
    (e: Konva.KonvaEventObject<WheelEvent>) => {
      e.evt.preventDefault();

      const stage = e.target.getStage();
      if (!stage) return;

      const pointerPos = stage.getPointerPosition();
      if (!pointerPos) return;

      // Get world point under cursor before zoom
      const worldBefore = screenToWorld(pointerPos.x, pointerPos.y);

      // Compute new zoom
      const delta = -e.evt.deltaY * ZOOM_SENSITIVITY;
      const scale = Math.exp(delta);
      const newZoom = clamp(zoom * scale, MIN_ZOOM, MAX_ZOOM);

      // Adjust pan so the world point stays under the cursor
      const newPanOffset: Point = {
        x: pointerPos.x - worldBefore.x * newZoom,
        y: pointerPos.y - worldBefore.y * newZoom,
      };

      onZoomChange(newZoom);
      onPanChange(newPanOffset);
    },
    [zoom, screenToWorld, onZoomChange, onPanChange],
  );

  const zoomIn = useCallback(() => {
    const newZoom = clamp(zoom * ZOOM_STEP_FACTOR, MIN_ZOOM, MAX_ZOOM);
    onZoomChange(newZoom);
  }, [zoom, onZoomChange]);

  const zoomOut = useCallback(() => {
    const newZoom = clamp(zoom / ZOOM_STEP_FACTOR, MIN_ZOOM, MAX_ZOOM);
    onZoomChange(newZoom);
  }, [zoom, onZoomChange]);

  const resetView = useCallback(() => {
    onZoomChange(DEFAULT_ZOOM);
    onPanChange({ x: 50, y: 50 });
  }, [onZoomChange, onPanChange]);

  const handlePanStart = useCallback(
    (e: Konva.KonvaEventObject<MouseEvent>) => {
      // Middle mouse button (button === 1) — always pans
      if (e.evt.button === 1) {
        e.evt.preventDefault();
        isPanningRef.current = true;
        lastPanPointRef.current = { x: e.evt.clientX, y: e.evt.clientY };
        panAccumRef.current = { x: panOffset.x, y: panOffset.y };
      }
    },
    [panOffset],
  );

  /** Start panning from left mouse button (called by canvas on empty-space click). */
  const startLeftMousePan = useCallback(
    (e: Konva.KonvaEventObject<MouseEvent>) => {
      isPanningRef.current = true;
      lastPanPointRef.current = { x: e.evt.clientX, y: e.evt.clientY };
      panAccumRef.current = { x: panOffset.x, y: panOffset.y };
    },
    [panOffset],
  );

  const handlePanMove = useCallback(
    (e: Konva.KonvaEventObject<MouseEvent>) => {
      if (!isPanningRef.current) return;

      const dx = e.evt.clientX - lastPanPointRef.current.x;
      const dy = e.evt.clientY - lastPanPointRef.current.y;

      lastPanPointRef.current = { x: e.evt.clientX, y: e.evt.clientY };
      panAccumRef.current = {
        x: panAccumRef.current.x + dx,
        y: panAccumRef.current.y + dy,
      };

      onPanChange(panAccumRef.current);
    },
    [onPanChange],
  );

  const handlePanEnd = useCallback(() => {
    isPanningRef.current = false;
  }, []);

  return {
    handleWheel,
    screenToWorld,
    worldToScreen,
    zoomIn,
    zoomOut,
    resetView,
    isPanningRef,
    handlePanStart,
    startLeftMousePan,
    handlePanMove,
    handlePanEnd,
  };
}
