'use client';

import { useRef, useEffect, useCallback, useState } from 'react';
import type { GraphFunction } from '@/lib/calculate/store';
import { sampleFunction, evaluateAt } from '@/lib/calculate/math-engine';

interface ViewState {
  xMin: number;
  xMax: number;
  yMin: number;
  yMax: number;
}

interface Props {
  functions: GraphFunction[];
  variables: Record<string, string>;
}

const DEFAULT_VIEW: ViewState = { xMin: -10, xMax: 10, yMin: -6, yMax: 6 };

function niceStep(range: number): number {
  if (range <= 0) return 1;
  const rawStep = range / 8;
  const mag = Math.pow(10, Math.floor(Math.log10(rawStep)));
  const n = rawStep / mag;
  const nice = n <= 1 ? 1 : n <= 2 ? 2 : n <= 5 ? 5 : 10;
  return nice * mag;
}

function fmtLabel(v: number): string {
  if (Math.abs(v) < 1e-10) return '0';
  const abs = Math.abs(v);
  if (abs >= 1e5 || (abs < 0.01 && abs > 0)) return v.toExponential(1);
  return parseFloat(v.toPrecision(4)).toString();
}

export default function GraphCanvas({ functions, variables }: Props) {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const viewRef = useRef<ViewState>(DEFAULT_VIEW);
  const [, tick] = useState(0);
  const redraw = useCallback(() => tick((n) => n + 1), []);

  const [cursor, setCursor] = useState<{ x: number; y: number } | null>(null);
  const cursorRef = useRef<{ x: number; y: number } | null>(null);
  const functionsRef = useRef(functions);
  const variablesRef = useRef(variables);
  const dragRef = useRef<{ startX: number; startY: number; startView: ViewState } | null>(null);
  const rafRef = useRef(0);

  useEffect(() => { functionsRef.current = functions; }, [functions]);
  useEffect(() => { variablesRef.current = variables; }, [variables]);
  useEffect(() => { cursorRef.current = cursor; }, [cursor]);

  const draw = useCallback(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    if (!ctx) return;

    const dpr = window.devicePixelRatio || 1;
    const rect = canvas.getBoundingClientRect();
    const W = rect.width;
    const H = rect.height;
    if (!W || !H) return;

    if (canvas.width !== Math.round(W * dpr) || canvas.height !== Math.round(H * dpr)) {
      canvas.width = Math.round(W * dpr);
      canvas.height = Math.round(H * dpr);
    }
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);

    const v = viewRef.current;
    const xRange = v.xMax - v.xMin;
    const yRange = v.yMax - v.yMin;
    const fns = functionsRef.current;
    const vars = variablesRef.current;
    const cur = cursorRef.current;

    const toP = (mx: number, my: number): [number, number] => [
      (mx - v.xMin) / xRange * W,
      H - (my - v.yMin) / yRange * H,
    ];

    // ── Background ──────────────────────────────────────────────
    ctx.fillStyle = '#08080f';
    ctx.fillRect(0, 0, W, H);

    const radGrad = ctx.createRadialGradient(W * 0.5, H * 0.5, 0, W * 0.5, H * 0.5, Math.max(W, H) * 0.7);
    radGrad.addColorStop(0, 'rgba(139, 92, 246, 0.05)');
    radGrad.addColorStop(1, 'rgba(0, 0, 0, 0)');
    ctx.fillStyle = radGrad;
    ctx.fillRect(0, 0, W, H);

    // ── Grid ─────────────────────────────────────────────────────
    const xStep = niceStep(xRange);
    const yStep = niceStep(yRange);
    ctx.lineWidth = 1;

    for (let x = Math.ceil(v.xMin / xStep) * xStep; x <= v.xMax + xStep * 0.01; x += xStep) {
      const [px] = toP(x, 0);
      ctx.strokeStyle =
        Math.abs(x) < xStep * 0.01 ? 'rgba(255,255,255,0.18)' : 'rgba(255,255,255,0.055)';
      ctx.beginPath(); ctx.moveTo(px, 0); ctx.lineTo(px, H); ctx.stroke();
    }
    for (let y = Math.ceil(v.yMin / yStep) * yStep; y <= v.yMax + yStep * 0.01; y += yStep) {
      const [, py] = toP(0, y);
      ctx.strokeStyle =
        Math.abs(y) < yStep * 0.01 ? 'rgba(255,255,255,0.18)' : 'rgba(255,255,255,0.055)';
      ctx.beginPath(); ctx.moveTo(0, py); ctx.lineTo(W, py); ctx.stroke();
    }

    // ── Axes ──────────────────────────────────────────────────────
    const [ax, ay] = toP(0, 0);
    ctx.lineWidth = 1.5;
    ctx.strokeStyle = 'rgba(255,255,255,0.25)';
    if (ay >= 0 && ay <= H) {
      ctx.beginPath(); ctx.moveTo(0, ay); ctx.lineTo(W, ay); ctx.stroke();
    }
    if (ax >= 0 && ax <= W) {
      ctx.beginPath(); ctx.moveTo(ax, 0); ctx.lineTo(ax, H); ctx.stroke();
    }

    // ── Axis arrow tips ───────────────────────────────────────────
    const arrowSize = 5;
    ctx.fillStyle = 'rgba(255,255,255,0.25)';
    if (ax >= 0 && ax <= W) {
      // Y-axis arrow (pointing up)
      ctx.beginPath();
      ctx.moveTo(ax, 4);
      ctx.lineTo(ax - arrowSize, 4 + arrowSize * 1.8);
      ctx.lineTo(ax + arrowSize, 4 + arrowSize * 1.8);
      ctx.closePath(); ctx.fill();
    }
    if (ay >= 0 && ay <= H) {
      // X-axis arrow (pointing right)
      ctx.beginPath();
      ctx.moveTo(W - 4, ay);
      ctx.lineTo(W - 4 - arrowSize * 1.8, ay - arrowSize);
      ctx.lineTo(W - 4 - arrowSize * 1.8, ay + arrowSize);
      ctx.closePath(); ctx.fill();
    }

    // ── Axis labels ───────────────────────────────────────────────
    ctx.font = '10px monospace';
    ctx.fillStyle = 'rgba(161,161,170,0.6)';
    const labelAY = Math.min(Math.max(ay + 5, 2), H - 14);
    const labelAX = Math.min(Math.max(ax + 5, 2), W - 46);

    ctx.textAlign = 'center'; ctx.textBaseline = 'top';
    for (let x = Math.ceil(v.xMin / xStep) * xStep; x <= v.xMax; x += xStep) {
      if (Math.abs(x) < xStep * 0.01) continue;
      const [px] = toP(x, 0);
      if (px < 8 || px > W - 8) continue;
      ctx.fillText(fmtLabel(x), px, labelAY);
    }
    ctx.textAlign = 'left'; ctx.textBaseline = 'middle';
    for (let y = Math.ceil(v.yMin / yStep) * yStep; y <= v.yMax; y += yStep) {
      if (Math.abs(y) < yStep * 0.01) continue;
      const [, py] = toP(0, y);
      if (py < 8 || py > H - 8) continue;
      ctx.fillText(fmtLabel(y), labelAX, py);
    }

    if (ax >= 0 && ax <= W && ay >= 0 && ay <= H) {
      ctx.fillStyle = 'rgba(161,161,170,0.35)';
      ctx.textAlign = 'left'; ctx.textBaseline = 'top';
      ctx.fillText('0', labelAX, labelAY);
    }

    // ── Function curves ───────────────────────────────────────────
    const numPts = Math.round(W * 1.5);

    for (const fn of fns) {
      if (!fn.visible || !fn.expression.trim()) continue;
      const pts = sampleFunction(fn.expression, v.xMin, v.xMax, numPts, vars);

      // Three render passes: wide glow → medium glow → crisp line
      const passes = [
        { alpha: 0.08, width: 10 },
        { alpha: 0.28, width: 3.5 },
        { alpha: 1.0, width: 1.8 },
      ];
      for (const { alpha, width } of passes) {
        ctx.beginPath();
        ctx.strokeStyle = fn.color;
        ctx.globalAlpha = alpha;
        ctx.lineWidth = width;
        ctx.lineJoin = 'round';
        ctx.lineCap = 'round';

        let penDown = false;
        for (const pt of pts) {
          if (pt === null) {
            if (penDown) { ctx.stroke(); ctx.beginPath(); }
            penDown = false;
          } else {
            const [px, py] = toP(pt.x, pt.y);
            if (!penDown) { ctx.moveTo(px, py); penDown = true; }
            else ctx.lineTo(px, py);
          }
        }
        if (penDown) ctx.stroke();
        ctx.globalAlpha = 1;
      }
    }

    // ── Cursor crosshair + tooltip ────────────────────────────────
    if (cur) {
      const [cx, cy] = toP(cur.x, cur.y);

      ctx.setLineDash([3, 5]);
      ctx.strokeStyle = 'rgba(255,255,255,0.28)';
      ctx.lineWidth = 1;
      ctx.beginPath(); ctx.moveTo(cx, 0); ctx.lineTo(cx, H); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(0, cy); ctx.lineTo(W, cy); ctx.stroke();
      ctx.setLineDash([]);

      // Crosshair dot
      ctx.fillStyle = 'rgba(255,255,255,0.75)';
      ctx.beginPath(); ctx.arc(cx, cy, 3, 0, Math.PI * 2); ctx.fill();

      // Function values at cursor x
      type FnVal = { color: string; y: number; label: string };
      const fnVals: FnVal[] = fns
        .filter((f) => f.visible && f.expression.trim())
        .map((f, i) => {
          const y = evaluateAt(f.expression, cur.x, vars);
          return isNaN(y) ? null : { color: f.color, y, label: `f${i + 1}(x)` };
        })
        .filter((v): v is FnVal => v !== null);

      const coordLine = `x = ${cur.x.toFixed(3)}    y = ${cur.y.toFixed(3)}`;
      const lines: { text: string; color: string }[] = [
        { text: coordLine, color: 'rgba(200,200,215,0.85)' },
        ...fnVals.map((f) => ({
          text: `${f.label} = ${f.y.toFixed(4)}`,
          color: f.color,
        })),
      ];

      const lh = 15;
      const pad = 9;
      ctx.font = '10px monospace';
      const maxW = Math.max(...lines.map((l) => ctx.measureText(l.text).width));
      const bw = maxW + pad * 2;
      const bh = lines.length * lh + pad * 2;

      let bx = cx + 14;
      let by = cy - bh / 2;
      if (bx + bw > W - 4) bx = cx - bw - 14;
      if (by < 4) by = 4;
      if (by + bh > H - 4) by = H - bh - 4;

      ctx.fillStyle = 'rgba(6, 6, 16, 0.92)';
      ctx.strokeStyle = 'rgba(255,255,255,0.07)';
      ctx.lineWidth = 1;
      ctx.beginPath();
      ctx.roundRect(bx, by, bw, bh, 5);
      ctx.fill(); ctx.stroke();

      lines.forEach((line, i) => {
        ctx.fillStyle = line.color;
        ctx.textAlign = 'left'; ctx.textBaseline = 'top';
        ctx.fillText(line.text, bx + pad, by + pad + i * lh);
      });
    }
  }, []);

  const scheduleDraw = useCallback(() => {
    if (rafRef.current) cancelAnimationFrame(rafRef.current);
    rafRef.current = requestAnimationFrame(draw);
  }, [draw]);

  // Resize observer
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const obs = new ResizeObserver(scheduleDraw);
    obs.observe(canvas);
    scheduleDraw();
    return () => obs.disconnect();
  }, [scheduleDraw]);

  // Redraw whenever reactive state changes
  useEffect(() => { scheduleDraw(); }, [functions, variables, cursor, tick, scheduleDraw]);

  // Convert mouse event to math coords
  const toMath = useCallback((e: React.MouseEvent<HTMLCanvasElement>): [number, number] => {
    const rect = canvasRef.current!.getBoundingClientRect();
    const px = (e.clientX - rect.left) / rect.width;
    const py = (e.clientY - rect.top) / rect.height;
    const v = viewRef.current;
    return [v.xMin + px * (v.xMax - v.xMin), v.yMax - py * (v.yMax - v.yMin)];
  }, []);

  const handleMouseDown = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
    dragRef.current = { startX: e.clientX, startY: e.clientY, startView: { ...viewRef.current } };
  }, []);

  const handleMouseMove = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
    const [mx, my] = toMath(e);
    setCursor({ x: mx, y: my });
    if (dragRef.current) {
      const { startX, startY, startView: sv } = dragRef.current;
      const rect = canvasRef.current!.getBoundingClientRect();
      const dx = (e.clientX - startX) / rect.width * (sv.xMax - sv.xMin);
      const dy = (e.clientY - startY) / rect.height * (sv.yMax - sv.yMin);
      viewRef.current = {
        xMin: sv.xMin - dx, xMax: sv.xMax - dx,
        yMin: sv.yMin + dy, yMax: sv.yMax + dy,
      };
      redraw();
    }
  }, [toMath, redraw]);

  const handleMouseUp = useCallback(() => { dragRef.current = null; }, []);
  const handleMouseLeave = useCallback(() => { dragRef.current = null; setCursor(null); }, []);

  const handleWheel = useCallback((e: WheelEvent) => {
    e.preventDefault();
    const rect = canvasRef.current!.getBoundingClientRect();
    const px = (e.clientX - rect.left) / rect.width;
    const py = (e.clientY - rect.top) / rect.height;
    const v = viewRef.current;
    const mx = v.xMin + px * (v.xMax - v.xMin);
    const my = v.yMax - py * (v.yMax - v.yMin);
    const factor = e.deltaY > 0 ? 1.12 : 1 / 1.12;
    viewRef.current = {
      xMin: mx - (mx - v.xMin) * factor,
      xMax: mx + (v.xMax - mx) * factor,
      yMin: my - (my - v.yMin) * factor,
      yMax: my + (v.yMax - my) * factor,
    };
    redraw();
    scheduleDraw();
  }, [redraw, scheduleDraw]);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    canvas.addEventListener('wheel', handleWheel, { passive: false });
    return () => canvas.removeEventListener('wheel', handleWheel);
  }, [handleWheel]);

  const resetView = useCallback(() => {
    viewRef.current = DEFAULT_VIEW;
    redraw();
    scheduleDraw();
  }, [redraw, scheduleDraw]);

  return (
    <div className="relative w-full h-full group">
      <canvas
        ref={canvasRef}
        className="w-full h-full"
        style={{ display: 'block', cursor: dragRef.current ? 'grabbing' : 'crosshair' }}
        onMouseDown={handleMouseDown}
        onMouseMove={handleMouseMove}
        onMouseUp={handleMouseUp}
        onMouseLeave={handleMouseLeave}
      />
      <button
        onClick={resetView}
        className="absolute bottom-3 right-3 px-2.5 py-1 text-xs font-mono text-muted-foreground glass rounded-md opacity-0 group-hover:opacity-100 hover:text-foreground transition-all duration-200"
      >
        reset view
      </button>
    </div>
  );
}
