"use client";

import { useEffect, useRef, useState } from "react";
import type { PointerEvent as ReactPointerEvent, ReactNode, RefObject } from "react";
import type {
  CanvasBackgroundMode,
  CanvasPosition,
  CanvasViewport,
} from "@/lib/canvas/types";
import {
  CANVAS_GRID_SIZE,
  clampScale,
  screenToWorld,
} from "@/lib/canvas/geometry";

type InfiniteCanvasProps = {
  containerRef: RefObject<HTMLDivElement | null>;
  viewport: CanvasViewport;
  backgroundMode: CanvasBackgroundMode;
  selectionMode?: boolean;
  onBackgroundPointerDown?: () => boolean | void;
  onViewportChange: (viewport: CanvasViewport) => void;
  onCanvasPointerMove?: (point: CanvasPosition) => void;
  onCanvasPointerUp?: () => void;
  onCanvasDeselect?: () => void;
  onSelectionStart?: (point: CanvasPosition) => void;
  onSelectionMove?: (point: CanvasPosition) => void;
  onSelectionEnd?: (point: CanvasPosition) => void;
  children: ReactNode;
};

export function InfiniteCanvas({
  containerRef,
  viewport,
  backgroundMode,
  selectionMode = false,
  onBackgroundPointerDown,
  onViewportChange,
  onCanvasPointerMove,
  onCanvasPointerUp,
  onCanvasDeselect,
  onSelectionStart,
  onSelectionMove,
  onSelectionEnd,
  children,
}: InfiniteCanvasProps) {
  const panState = useRef({
    mode: "idle" as "idle" | "pending" | "panning" | "selecting",
    startX: 0,
    startY: 0,
    initialX: 0,
    initialY: 0,
    moved: false,
    pointerWorld: { x: 0, y: 0 },
    longPressTimer: null as ReturnType<typeof setTimeout> | null,
  });
  const viewportRef = useRef(viewport);
  const frameRef = useRef<number | null>(null);
  const nextViewportRef = useRef<CanvasViewport | null>(null);
  const [spacePressed, setSpacePressed] = useState(false);

  useEffect(() => {
    viewportRef.current = viewport;
  }, [viewport]);

  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.code !== "Space") return;
      if (isTypingTarget(event.target)) return;
      event.preventDefault();
      setSpacePressed(true);
    };
    const handleKeyUp = (event: KeyboardEvent) => {
      if (event.code === "Space") setSpacePressed(false);
    };
    window.addEventListener("keydown", handleKeyDown);
    window.addEventListener("keyup", handleKeyUp);
    return () => {
      window.removeEventListener("keydown", handleKeyDown);
      window.removeEventListener("keyup", handleKeyUp);
    };
  }, []);

  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;

    const preventNativeScroll = (event: WheelEvent) => event.preventDefault();
    container.addEventListener("wheel", preventNativeScroll, { passive: false });
    return () => container.removeEventListener("wheel", preventNativeScroll);
  }, [containerRef]);

  useEffect(
    () => () => {
      if (frameRef.current) cancelAnimationFrame(frameRef.current);
      if (panState.current.longPressTimer) {
        clearTimeout(panState.current.longPressTimer);
      }
    },
    [],
  );

  useEffect(() => {
    const handlePointerMove = (event: PointerEvent) => {
      const rect = containerRef.current?.getBoundingClientRect();
      if (rect) {
        const worldPoint = screenToWorld(
          { x: event.clientX - rect.left, y: event.clientY - rect.top },
          viewportRef.current,
        );
        panState.current.pointerWorld = worldPoint;
        onCanvasPointerMove?.(worldPoint);

        if (panState.current.mode === "selecting") {
          onSelectionMove?.(worldPoint);
        }
      }

      if (panState.current.mode === "pending") {
        const dx = event.clientX - panState.current.startX;
        const dy = event.clientY - panState.current.startY;
        if (Math.abs(dx) > 4 || Math.abs(dy) > 4) {
          if (panState.current.longPressTimer) {
            clearTimeout(panState.current.longPressTimer);
            panState.current.longPressTimer = null;
          }
          panState.current.mode = "panning";
          document.body.style.cursor = "grabbing";
        }
      }

      if (panState.current.mode !== "panning") return;
      const dx = event.clientX - panState.current.startX;
      const dy = event.clientY - panState.current.startY;
      if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {
        panState.current.moved = true;
      }

      nextViewportRef.current = {
        x: panState.current.initialX + dx,
        y: panState.current.initialY + dy,
        k: viewportRef.current.k,
      };

      if (frameRef.current) return;
      frameRef.current = requestAnimationFrame(() => {
        frameRef.current = null;
        if (nextViewportRef.current) onViewportChange(nextViewportRef.current);
      });
    };

    const handlePointerUp = () => {
      if (panState.current.longPressTimer) {
        clearTimeout(panState.current.longPressTimer);
        panState.current.longPressTimer = null;
      }

      if (panState.current.mode === "selecting") {
        onSelectionEnd?.(panState.current.pointerWorld);
      } else if (
        (panState.current.mode === "pending" || panState.current.mode === "panning") &&
        !panState.current.moved
      ) {
        onCanvasDeselect?.();
      }
      panState.current.mode = "idle";
      document.body.style.cursor = "";
      onCanvasPointerUp?.();
    };

    window.addEventListener("pointermove", handlePointerMove);
    window.addEventListener("pointerup", handlePointerUp);
    return () => {
      window.removeEventListener("pointermove", handlePointerMove);
      window.removeEventListener("pointerup", handlePointerUp);
    };
  }, [
    containerRef,
    onCanvasDeselect,
    onCanvasPointerMove,
    onCanvasPointerUp,
    onViewportChange,
    onSelectionEnd,
    onSelectionMove,
  ]);

  function handleWheel(event: React.WheelEvent<HTMLDivElement>) {
    if (event.target instanceof Element && event.target.closest("[data-canvas-ui]")) {
      return;
    }

    const rect = containerRef.current?.getBoundingClientRect();
    if (!rect) return;

    const mouse = {
      x: event.clientX - rect.left,
      y: event.clientY - rect.top,
    };
    const delta = -event.deltaY;
    const factor = Math.pow(1.12, delta / 100);
    const nextScale = clampScale(viewport.k * factor);
    const world = screenToWorld(mouse, viewport);

    onViewportChange({
      x: mouse.x - world.x * nextScale,
      y: mouse.y - world.y * nextScale,
      k: nextScale,
    });
  }

  function handlePointerDown(event: ReactPointerEvent<HTMLDivElement>) {
    const target = event.target instanceof Element ? event.target : null;
    const isBackground = !target?.closest("[data-node-id],[data-connection-id],[data-canvas-ui]");
    if (!isBackground) return;
    if (onBackgroundPointerDown?.()) {
      event.preventDefault();
      return;
    }

    const rect = containerRef.current?.getBoundingClientRect();
    const pointerWorld = rect
      ? screenToWorld(
          { x: event.clientX - rect.left, y: event.clientY - rect.top },
          viewport,
        )
      : { x: 0, y: 0 };

    if (event.button === 0 || event.button === 1 || spacePressed) {
      event.preventDefault();
      event.currentTarget.setPointerCapture(event.pointerId);
      panState.current = {
        mode: selectionMode ? "selecting" : event.button === 1 || spacePressed ? "panning" : "pending",
        startX: event.clientX,
        startY: event.clientY,
        initialX: viewport.x,
        initialY: viewport.y,
        moved: false,
        pointerWorld,
        longPressTimer: null,
      };

      if (selectionMode) {
        document.body.style.cursor = "crosshair";
        onSelectionStart?.(pointerWorld);
        return;
      }

      if (event.button === 1 || spacePressed) {
        document.body.style.cursor = "grabbing";
        return;
      }

      panState.current.longPressTimer = setTimeout(() => {
        panState.current.mode = "selecting";
        panState.current.longPressTimer = null;
        document.body.style.cursor = "crosshair";
        onSelectionStart?.(panState.current.pointerWorld);
      }, 260);
    }
  }

  return (
    <div
      ref={containerRef}
      className="relative h-full w-full cursor-grab select-none overflow-hidden bg-zinc-100"
      onPointerDown={handlePointerDown}
      onWheel={handleWheel}
    >
      <CanvasGrid mode={backgroundMode} viewport={viewport} />
      <div
        className="absolute origin-top-left"
        style={{
          transform: `translate(${viewport.x}px, ${viewport.y}px) scale(${viewport.k})`,
        }}
      >
        {children}
      </div>
    </div>
  );
}

function CanvasGrid({
  mode,
  viewport,
}: {
  mode: CanvasBackgroundMode;
  viewport: CanvasViewport;
}) {
  if (mode === "blank") return null;

  const gridSize = CANVAS_GRID_SIZE * viewport.k;
  const x = viewport.x % gridSize;
  const y = viewport.y % gridSize;
  const backgroundImage =
    mode === "dots"
      ? "radial-gradient(circle, rgba(113, 113, 122, 0.22) 1.2px, transparent 1.5px)"
      : "linear-gradient(rgba(161, 161, 170, 0.22) 1px, transparent 1px), linear-gradient(90deg, rgba(161, 161, 170, 0.22) 1px, transparent 1px)";

  return (
    <div
      className="pointer-events-none absolute inset-0 opacity-80"
      style={{
        backgroundImage,
        backgroundPosition: `${x}px ${y}px`,
        backgroundSize: `${gridSize}px ${gridSize}px`,
      }}
    />
  );
}

function isTypingTarget(target: EventTarget | null) {
  return (
    target instanceof HTMLInputElement ||
    target instanceof HTMLTextAreaElement ||
    target instanceof HTMLSelectElement ||
    (target instanceof HTMLElement && target.isContentEditable)
  );
}
