import { useState, useEffect, useCallback, useRef } from "react";
import Konva from "konva";
import { useEditorStore } from "@/pages/editor/store/editorStore";
import type { EditorObject } from "@/pages/editor/store/editorStore";
import { usePencilDefaultsStore } from "@/pages/editor/store/pencilDefaultsStore";

const MIN_POINT_DISTANCE = 1.5;
const MIN_POINTS = 2;
const MIN_DRAW_DISTANCE = 4;
const DEFAULT_TENSION = 0.5;

export type DrawPoint = { x: number; y: number };

export type PencilDrawingState = {
  isDrawing: boolean;
  points: DrawPoint[];
  stroke: string;
  strokeWidth: number;
};

const INITIAL_STATE: PencilDrawingState = {
  isDrawing: false,
  points: [],
  stroke: "#000000",
  strokeWidth: 3,
};

const getCanvasPos = (stage: Konva.Stage): DrawPoint | null => {
  const pos = stage.getPointerPosition();
  if (!pos) return null;
  return {
    x: pos.x / stage.scaleX(),
    y: pos.y / stage.scaleY(),
  };
};

const getStagePosFromClient = (
  stage: Konva.Stage,
  clientX: number,
  clientY: number,
): DrawPoint | null => {
  const container = stage.container();
  const rect = container.getBoundingClientRect();
  const scaleX = stage.scaleX();
  const scaleY = stage.scaleY();
  return {
    x: (clientX - rect.left) / scaleX,
    y: (clientY - rect.top) / scaleY,
  };
};

const isFarEnough = (a: DrawPoint, b: DrawPoint, minDistance: number): boolean => {
  const dx = a.x - b.x;
  const dy = a.y - b.y;
  return dx * dx + dy * dy >= minDistance * minDistance;
};

const getPathLength = (points: DrawPoint[]): number => {
  let length = 0;
  for (let i = 1; i < points.length; i += 1) {
    length += Math.hypot(points[i].x - points[i - 1].x, points[i].y - points[i - 1].y);
  }
  return length;
};

const buildPencilObject = (
  points: DrawPoint[],
  stroke: string,
  strokeWidth: number,
): EditorObject => {
  const xs = points.map((p) => p.x);
  const ys = points.map((p) => p.y);
  const minX = Math.min(...xs);
  const minY = Math.min(...ys);
  const maxX = Math.max(...xs);
  const maxY = Math.max(...ys);

  const normalizedPoints = points.flatMap((p) => [p.x - minX, p.y - minY]);

  return {
    id: `pencil-${Date.now()}`,
    type: "pencil",
    x: minX,
    y: minY,
    width: Math.max(1, maxX - minX),
    height: Math.max(1, maxY - minY),
    points: normalizedPoints,
    closed: false,
    stroke,
    strokeWidth,
    tension: DEFAULT_TENSION,
    opacity: 100,
    rotation: 0,
  };
};

export const usePencilDrawing = (stageRef: React.RefObject<Konva.Stage | null>) => {
  const pointsRef = useRef<DrawPoint[]>([]);
  const isDrawingRef = useRef(false);
  const pointerIdRef = useRef<number | null>(null);
  const strokeRef = useRef("#000000");
  const strokeWidthRef = useRef(3);

  const [drawingState, setDrawingState] = useState<PencilDrawingState>(INITIAL_STATE);

  const { tool, addObject } = useEditorStore();
  const { defaults } = usePencilDefaultsStore();

  const reset = useCallback(() => {
    pointsRef.current = [];
    isDrawingRef.current = false;
    pointerIdRef.current = null;
    setDrawingState(INITIAL_STATE);
  }, []);

  useEffect(() => {
    if (tool !== "pencil") {
      reset();
    }
  }, [tool, reset]);

  const releasePointerCapture = useCallback(() => {
    const stage = stageRef.current;
    const pointerId = pointerIdRef.current;
    if (!stage || pointerId === null) return;

    try {
      stage.container().releasePointerCapture(pointerId);
    } catch {
      // pointer may already be released
    }
    pointerIdRef.current = null;
  }, [stageRef]);

  const commitStroke = useCallback(() => {
    const points = pointsRef.current;
    releasePointerCapture();

    if (points.length < MIN_POINTS || getPathLength(points) < MIN_DRAW_DISTANCE) {
      reset();
      return;
    }

    const obj = buildPencilObject(points, strokeRef.current, strokeWidthRef.current);
    useEditorStore.getState().commitObjectHistoryBeforeChange();
    addObject(obj);
    reset();
  }, [addObject, releasePointerCapture, reset]);

  const appendPoint = useCallback((pos: DrawPoint) => {
    const points = pointsRef.current;
    const last = points[points.length - 1];
    if (last && !isFarEnough(last, pos, MIN_POINT_DISTANCE)) {
      return;
    }

    pointsRef.current = [...points, pos];
    setDrawingState((prev) => ({
      ...prev,
      points: pointsRef.current,
    }));
  }, []);

  const startDrawing = useCallback(
    (pos: DrawPoint, pointerId: number) => {
      strokeRef.current = defaults.stroke;
      strokeWidthRef.current = defaults.strokeWidth;
      isDrawingRef.current = true;
      pointerIdRef.current = pointerId;
      pointsRef.current = [pos];

      setDrawingState({
        isDrawing: true,
        points: [pos],
        stroke: defaults.stroke,
        strokeWidth: defaults.strokeWidth,
      });
    },
    [defaults.stroke, defaults.strokeWidth],
  );

  const handleStagePointerDown = useCallback(
    (e: Konva.KonvaEventObject<PointerEvent>) => {
      if (tool !== "pencil") return;
      if (e.evt.button !== 0) return;

      e.evt.preventDefault();
      e.cancelBubble = true;

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

      const pos = getCanvasPos(stage);
      if (!pos) return;

      try {
        stage.container().setPointerCapture(e.evt.pointerId);
      } catch {
        // ignore if capture is unavailable
      }

      startDrawing(pos, e.evt.pointerId);
    },
    [tool, stageRef, startDrawing],
  );

  const handleStagePointerMove = useCallback(
    (e: Konva.KonvaEventObject<PointerEvent>) => {
      if (tool !== "pencil" || !isDrawingRef.current) return;
      if (pointerIdRef.current !== null && e.evt.pointerId !== pointerIdRef.current) return;

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

      const pos = getCanvasPos(stage);
      if (!pos) return;

      appendPoint(pos);
    },
    [tool, appendPoint, stageRef],
  );

  const handleStagePointerUp = useCallback(
    (e: Konva.KonvaEventObject<PointerEvent>) => {
      if (tool !== "pencil" || !isDrawingRef.current) return;
      if (pointerIdRef.current !== null && e.evt.pointerId !== pointerIdRef.current) return;

      e.evt.preventDefault();
      e.cancelBubble = true;
      commitStroke();
    },
    [tool, commitStroke],
  );

  // Keep drawing smooth when pointer leaves the canvas while still pressed
  useEffect(() => {
    if (tool !== "pencil") return;

    const handleWindowPointerMove = (e: PointerEvent) => {
      if (!isDrawingRef.current) return;
      if (pointerIdRef.current !== null && e.pointerId !== pointerIdRef.current) return;

      const stage = stageRef.current;
      if (!stage) return;

      const pos = getStagePosFromClient(stage, e.clientX, e.clientY);
      if (!pos) return;

      appendPoint(pos);
    };

    const handleWindowPointerUp = (e: PointerEvent) => {
      if (!isDrawingRef.current) return;
      if (pointerIdRef.current !== null && e.pointerId !== pointerIdRef.current) return;

      commitStroke();
    };

    window.addEventListener("pointermove", handleWindowPointerMove);
    window.addEventListener("pointerup", handleWindowPointerUp);
    window.addEventListener("pointercancel", handleWindowPointerUp);

    return () => {
      window.removeEventListener("pointermove", handleWindowPointerMove);
      window.removeEventListener("pointerup", handleWindowPointerUp);
      window.removeEventListener("pointercancel", handleWindowPointerUp);
    };
  }, [tool, appendPoint, commitStroke, stageRef]);

  return {
    drawingState,
    handleStagePointerDown,
    handleStagePointerMove,
    handleStagePointerUp,
  };
};
