import { useEffect, useRef, useState } from "react";
import { Renderer } from "./renderer";

export default function App() {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const rendererRef = useRef<Renderer | null>(null);
  const [status, setStatus] = useState("Loading node positions…");
  const [nodeCount, setNodeCount] = useState(0);
  const [stats, setStats] = useState({
    fps: 0,
    drawn: 0,
    mode: "",
    zoom: 0,
    ptSize: 0,
  });
  const [error, setError] = useState("");
  const [hoveredNode, setHoveredNode] = useState<{ x: number; y: number; screenX: number; screenY: number } | null>(null);
  const [selectedNodes, setSelectedNodes] = useState<Set<number>>(new Set());
  const [backendStats, setBackendStats] = useState<{ nodes: number; edges: number; parsed_triples: number } | null>(null);

  // Store mouse position in a ref so it can be accessed in render loop without re-renders
  const mousePos = useRef({ x: 0, y: 0 });

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    let renderer: Renderer;
    try {
      renderer = new Renderer(canvas);
      rendererRef.current = renderer;
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e));
      return;
    }

    let cancelled = false;

    // Optional: fetch backend stats (proxied via Vite) so you can confirm backend is up.
    fetch("/api/stats")
      .then((r) => (r.ok ? r.json() : null))
      .then((j) => {
        if (!j || cancelled) return;
        if (typeof j.nodes === "number" && typeof j.edges === "number" && typeof j.parsed_triples === "number") {
          setBackendStats({ nodes: j.nodes, edges: j.edges, parsed_triples: j.parsed_triples });
        }
      })
      .catch(() => {
        // Backend is optional; ignore failures.
      });

    // Fetch CSVs, parse, and init renderer
    (async () => {
      try {
        setStatus("Fetching data files…");
        const [nodesResponse, edgesResponse] = await Promise.all([
          fetch("/node_positions.csv"),
          fetch("/edges.csv"),
        ]);
        if (!nodesResponse.ok) throw new Error(`Failed to fetch nodes: ${nodesResponse.status}`);
        if (!edgesResponse.ok) throw new Error(`Failed to fetch edges: ${edgesResponse.status}`);

        const [nodesText, edgesText] = await Promise.all([
          nodesResponse.text(),
          edgesResponse.text(),
        ]);
        if (cancelled) return;

        setStatus("Parsing positions…");
        const nodeLines = nodesText.split("\n").slice(1).filter(l => l.trim().length > 0);
        const count = nodeLines.length;

        const xs = new Float32Array(count);
        const ys = new Float32Array(count);
        const vertexIds = new Uint32Array(count);
        for (let i = 0; i < count; i++) {
          const parts = nodeLines[i].split(",");
          vertexIds[i] = parseInt(parts[0], 10);
          xs[i] = parseFloat(parts[1]);
          ys[i] = parseFloat(parts[2]);
        }

        setStatus("Parsing edges…");
        const edgeLines = edgesText.split("\n").slice(1).filter(l => l.trim().length > 0);
        const edgeData = new Uint32Array(edgeLines.length * 2);
        for (let i = 0; i < edgeLines.length; i++) {
          const parts = edgeLines[i].split(",");
          edgeData[i * 2] = parseInt(parts[0], 10);
          edgeData[i * 2 + 1] = parseInt(parts[1], 10);
        }

        if (cancelled) return;

        setStatus("Building spatial index…");
        await new Promise(r => setTimeout(r, 0));

        const buildMs = renderer.init(xs, ys, vertexIds, edgeData);
        setNodeCount(renderer.getNodeCount());
        setStatus("");
        console.log(`Init complete: ${count.toLocaleString()} nodes, ${edgeLines.length.toLocaleString()} edges in ${buildMs.toFixed(0)}ms`);
      } catch (e) {
        if (!cancelled) {
          setError(e instanceof Error ? e.message : String(e));
        }
      }
    })();

    // ── Input handling ──
    let dragging = false;
    let didDrag = false; // true if mouse moved significantly during drag
    let downX = 0;
    let downY = 0;
    let lastX = 0;
    let lastY = 0;
    const DRAG_THRESHOLD = 5; // pixels

    const onDown = (e: MouseEvent) => {
      dragging = true;
      didDrag = false;
      downX = e.clientX;
      downY = e.clientY;
      lastX = e.clientX;
      lastY = e.clientY;
    };
    const onMove = (e: MouseEvent) => {
      mousePos.current = { x: e.clientX, y: e.clientY };
      if (!dragging) return;

      // Check if we've moved enough to consider it a drag
      const dx = e.clientX - downX;
      const dy = e.clientY - downY;
      if (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD) {
        didDrag = true;
      }

      renderer.pan(e.clientX - lastX, e.clientY - lastY);
      lastX = e.clientX;
      lastY = e.clientY;
    };
    const onUp = (e: MouseEvent) => {
      if (dragging && !didDrag) {
        // This was a click, not a drag - handle selection
        const node = renderer.findNodeIndexAt(e.clientX, e.clientY);
        if (node) {
          setSelectedNodes((prev: Set<number>) => {
            const next = new Set(prev);
            if (next.has(node.index)) {
              next.delete(node.index); // Deselect if already selected
            } else {
              next.add(node.index); // Select
            }
            return next;
          });
        }
      }
      dragging = false;
      didDrag = false;
    };
    const onWheel = (e: WheelEvent) => {
      e.preventDefault();
      const factor = e.deltaY > 0 ? 0.9 : 1 / 0.9;
      renderer.zoomAt(factor, e.clientX, e.clientY);
    };
    const onMouseLeave = () => {
      setHoveredNode(null);
    };

    canvas.addEventListener("mousedown", onDown);
    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseup", onUp);
    canvas.addEventListener("wheel", onWheel, { passive: false });
    canvas.addEventListener("mouseleave", onMouseLeave);

    // ── Render loop ──
    let frameCount = 0;
    let lastTime = performance.now();
    let raf = 0;

    const frame = () => {
      const result = renderer.render();
      frameCount++;

      // Find hovered node using quadtree
      const node = renderer.findNodeAt(mousePos.current.x, mousePos.current.y);
      if (node) {
        setHoveredNode({ ...node, screenX: mousePos.current.x, screenY: mousePos.current.y });
      } else {
        setHoveredNode(null);
      }

      const now = performance.now();
      if (now - lastTime >= 500) {
        const fps = (frameCount / (now - lastTime)) * 1000;
        setStats({
          fps: Math.round(fps),
          drawn: result.drawnCount,
          mode: result.mode,
          zoom: result.zoom,
          ptSize: result.ptSize,
        });
        frameCount = 0;
        lastTime = now;
      }

      raf = requestAnimationFrame(frame);
    };
    raf = requestAnimationFrame(frame);

    return () => {
      cancelled = true;
      cancelAnimationFrame(raf);
      canvas.removeEventListener("mousedown", onDown);
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("mouseup", onUp);
      canvas.removeEventListener("wheel", onWheel);
      canvas.removeEventListener("mouseleave", onMouseLeave);
    };
  }, []);

  // Sync selection state to renderer
  useEffect(() => {
    if (rendererRef.current) {
      rendererRef.current.updateSelection(selectedNodes);
    }
  }, [selectedNodes]);

  return (
    <div style={{ width: "100vw", height: "100vh", overflow: "hidden", background: "#000" }}>
      <canvas
        ref={canvasRef}
        style={{ display: "block", width: "100%", height: "100%" }}
      />

      {/* Loading overlay */}
      {status && (
        <div
          style={{
            position: "absolute",
            inset: 0,
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            background: "rgba(0,0,0,0.9)",
            color: "#0f0",
            fontFamily: "monospace",
            fontSize: "16px",
          }}
        >
          {status}
        </div>
      )}

      {/* Error overlay */}
      {error && (
        <div
          style={{
            position: "absolute",
            inset: 0,
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            background: "rgba(0,0,0,0.9)",
            color: "#f44",
            fontFamily: "monospace",
            fontSize: "16px",
          }}
        >
          Error: {error}
        </div>
      )}

      {/* HUD */}
      {!status && !error && (
        <>
          <div
            style={{
              position: "absolute",
              top: 10,
              left: 10,
              background: "rgba(0,0,0,0.75)",
              color: "#0f0",
              fontFamily: "monospace",
              padding: "8px 12px",
              fontSize: "12px",
              lineHeight: "1.6",
              borderRadius: "4px",
              pointerEvents: "none",
            }}
          >
            <div>FPS: {stats.fps}</div>
            <div>Drawn: {stats.drawn.toLocaleString()} / {nodeCount.toLocaleString()}</div>
            <div>Mode: {stats.mode}</div>
            <div>Zoom: {stats.zoom < 0.01 ? stats.zoom.toExponential(2) : stats.zoom.toFixed(2)} px/unit</div>
            <div>Pt Size: {stats.ptSize.toFixed(1)}px</div>
            <div style={{ color: "#f80" }}>Selected: {selectedNodes.size}</div>
            {backendStats && (
              <div style={{ color: "#8f8" }}>
                Backend: {backendStats.nodes.toLocaleString()} nodes, {backendStats.edges.toLocaleString()} edges
              </div>
            )}
          </div>
          <div
            style={{
              position: "absolute",
              bottom: 10,
              left: 10,
              background: "rgba(0,0,0,0.75)",
              color: "#888",
              fontFamily: "monospace",
              padding: "6px 10px",
              fontSize: "11px",
              borderRadius: "4px",
              pointerEvents: "none",
            }}
          >
            Drag to pan · Scroll to zoom · Click to select
          </div>

          {/* Hover tooltip */}
          {hoveredNode && (
            <div
              style={{
                position: "absolute",
                left: hoveredNode.screenX + 15,
                top: hoveredNode.screenY + 15,
                background: "rgba(0,0,0,0.85)",
                color: "#0ff",
                fontFamily: "monospace",
                padding: "6px 10px",
                fontSize: "12px",
                borderRadius: "4px",
                pointerEvents: "none",
                whiteSpace: "nowrap",
                border: "1px solid rgba(0,255,255,0.3)",
                boxShadow: "0 2px 8px rgba(0,0,0,0.5)",
              }}
            >
              ({hoveredNode.x.toFixed(2)}, {hoveredNode.y.toFixed(2)})
            </div>
          )}
        </>
      )}
    </div>
  );
}
