'use client';

import React, { useRef, useEffect, useCallback } from 'react';

interface AIOrbProps {
  isThinking: boolean;
  hasError?: boolean;
}

function lerp(a: number, b: number, t: number) {
  return a + (b - a) * t;
}

// Simple noise function for organic movement
function noise(x: number, y: number, t: number): number {
  return (
    Math.sin(x * 1.3 + t * 0.7) * Math.cos(y * 0.9 + t * 0.5) * 0.5 +
    Math.sin(x * 2.7 + y * 1.1 + t * 1.3) * 0.25 +
    Math.cos(x * 0.8 - y * 2.3 + t * 0.9) * 0.25
  );
}

// ── Particle ───────────────────────────────────────────────────
interface Particle {
  // Sphere position (target shape)
  theta: number;
  phi: number;
  // Current position
  x: number;
  y: number;
  z: number;
  // Velocity
  vx: number;
  vy: number;
  vz: number;
  // Properties
  size: number;
  baseSize: number;
  hue: number; // 0=blue, 1=green
  brightness: number;
  phase: number;
  orbitSpeed: number;
  noiseScale: number;
}

function createParticles(count: number): Particle[] {
  const particles: Particle[] = [];

  for (let i = 0; i < count; i++) {
    // Fibonacci sphere distribution for even spacing
    const golden = Math.PI * (3 - Math.sqrt(5));
    const y = 1 - (i / (count - 1)) * 2;
    const radiusAtY = Math.sqrt(1 - y * y);
    const theta = golden * i;
    const phi = Math.acos(y);

    particles.push({
      theta,
      phi,
      x: Math.cos(theta) * radiusAtY,
      y,
      z: Math.sin(theta) * radiusAtY,
      vx: 0,
      vy: 0,
      vz: 0,
      size: 0.4 + Math.random() * 0.8,
      baseSize: 0.4 + Math.random() * 0.8,
      hue: Math.random() > 0.45 ? 0 : 1,
      brightness: 0.5 + Math.random() * 0.5,
      phase: Math.random() * Math.PI * 2,
      orbitSpeed: (0.1 + Math.random() * 0.4) * (Math.random() > 0.5 ? 1 : -1),
      noiseScale: 0.5 + Math.random() * 1.5,
    });
  }
  return particles;
}

export default function AIOrb({ isThinking = false, hasError = false }: AIOrbProps) {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const wrapRef = useRef<HTMLDivElement>(null);
  const animRef = useRef<number>(0);
  const particlesRef = useRef<Particle[]>([]);

  const mouse = useRef({ x: 0.5, y: 0.5, hover: false });
  const state = useRef({
    pulse: 0,
    hover: 0,
    error: 0,
    mouseX: 0.5,
    mouseY: 0.5,
    rotY: 0,
    rotX: 0,
    breathe: 0,
    scatter: 0,
    shake: 0,
  });

  const onMove = useCallback((e: React.PointerEvent) => {
    const r = wrapRef.current?.getBoundingClientRect();
    if (!r) return;
    mouse.current.x = (e.clientX - r.left) / r.width;
    mouse.current.y = (e.clientY - r.top) / r.height;
  }, []);
  const onEnter = useCallback(() => {
    mouse.current.hover = true;
  }, []);
  const onLeave = useCallback(() => {
    mouse.current.hover = false;
    mouse.current.x = 0.5;
    mouse.current.y = 0.5;
  }, []);

  const draw = useCallback(
    function drawStep() {
      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 * dpr;
      const h = rect.height * dpr;
      if (canvas.width !== w || canvas.height !== h) {
        canvas.width = w;
        canvas.height = h;
      }

      const cx = w / 2;
      const cy = h / 2;
      const minDim = Math.min(w, h);
      // Reduced further to give maximum breathing room for glow + movement
      const sphereR = minDim * 0.16;
      const time = performance.now() / 1000;
      const s = state.current;
      const m = mouse.current;

      // ── Interpolate state ──
      s.pulse = lerp(s.pulse, isThinking ? 1 : 0, 0.03);
      s.hover = lerp(s.hover, m.hover ? 1 : 0, 0.12);
      s.error = lerp(s.error, hasError ? 1 : 0, 0.05);
      s.mouseX = lerp(s.mouseX, m.x, 0.12);
      s.mouseY = lerp(s.mouseY, m.y, 0.12);
      s.scatter = lerp(s.scatter, m.hover ? 0.8 : hasError ? 0.5 : 0, 0.06);
      s.shake += 0.15 * s.error;

      // Global rotation — ALWAYS rotating + ALWAYS facing cursor
      s.rotY += lerp(0.008, 0.04, Math.max(s.pulse, s.hover));
      const mouseRotY = (s.mouseX - 0.5) * 1.2; // always face cursor
      const mouseRotX = (s.mouseY - 0.5) * 0.8;

      s.breathe += lerp(1.2, 3.0, s.pulse) / 60;
      const breathe = Math.sin(s.breathe) * 0.5 + 0.5;

      // ── Clear ──
      ctx.clearRect(0, 0, w, h);

      // ── Subtle core glow ──
      const shakeX = Math.sin(s.shake * 17) * s.error * minDim * 0.02;
      const glowCX = cx + shakeX;
      const glowCY = cy;
      // Clamp glow radius so it never exceeds ~48% of canvas (leaves padding for movement)
      const glowR = Math.min(
        sphereR * lerp(2.2, 4.0, Math.max(s.pulse, s.hover * 0.8)),
        minDim * 0.48,
      );
      const glowA = lerp(0.1, 0.4, Math.max(s.pulse, s.hover * 0.7, s.error * 0.8));
      const glow = ctx.createRadialGradient(glowCX, glowCY, 0, glowCX, glowCY, glowR);
      // Glow color: blue normally, red on error
      const glowR1 = Math.round(lerp(20, 255, s.error));
      const glowG1 = Math.round(lerp(60, 40, s.error));
      const glowB1 = Math.round(lerp(255, 40, s.error));
      glow.addColorStop(0, `rgba(${glowR1}, ${glowG1}, ${glowB1}, ${glowA * 2})`);
      glow.addColorStop(
        0.25,
        `rgba(${Math.round(lerp(80, 200, s.error))}, ${Math.round(lerp(140, 50, s.error))}, ${Math.round(lerp(255, 50, s.error))}, ${glowA * 1.2})`,
      );
      glow.addColorStop(0.6, `rgba(${glowR1}, ${glowG1}, ${glowB1}, ${glowA * 0.4})`);
      glow.addColorStop(1, `rgba(${glowR1}, ${glowG1}, ${glowB1}, 0)`);
      ctx.fillStyle = glow;
      ctx.beginPath();
      ctx.arc(glowCX, glowCY, glowR, 0, Math.PI * 2);
      ctx.fill();

      // ── Create particles if empty ──
      if (particlesRef.current.length === 0) {
        particlesRef.current = createParticles(350);
      }

      // ── Update & draw particles ──
      const cosRY = Math.cos(s.rotY + mouseRotY);
      const sinRY = Math.sin(s.rotY + mouseRotY);
      const cosRX = Math.cos(mouseRotX);
      const sinRX = Math.sin(mouseRotX);

      // Sort by z for correct layering
      type ParticleWithScreen = { p: Particle; sx: number; sy: number; sz: number; depth: number };
      const projected: ParticleWithScreen[] = [];

      for (const p of particlesRef.current) {
        // Target position: sphere surface + noise displacement
        const n = noise(p.theta * p.noiseScale, p.phi * p.noiseScale, time * 0.5 + p.phase);
        const displacement = 1 + n * lerp(0.12, 0.3, s.pulse);

        // Orbit: rotate theta — always moving, faster idle
        const activeTheta = p.theta + time * p.orbitSpeed * lerp(0.35, 0.8, s.pulse);

        // Sphere coordinates to cartesian
        const sinPhi = Math.sin(p.phi);
        const tgtX = Math.cos(activeTheta) * sinPhi * displacement;
        // Excitement from hover + pulse + error
        const targetExcite = Math.max(s.hover * 0.9, s.pulse, s.error * 0.8);
        const tgtY = Math.cos(p.phi) * displacement;
        const tgtZ = Math.sin(activeTheta) * sinPhi * displacement;

        // Scatter on hover: push particles outward
        const scatterMul = 1 + s.scatter * (0.5 + n * 0.5);

        // Spring physics toward target
        const tx = tgtX * scatterMul;
        const ty = tgtY * scatterMul;
        const tz = tgtZ * scatterMul;

        p.vx += (tx - p.x) * 0.08;
        p.vy += (ty - p.y) * 0.08;
        p.vz += (tz - p.z) * 0.08;
        p.vx *= 0.88;
        p.vy *= 0.88;
        p.vz *= 0.88;
        p.x += p.vx;
        p.y += p.vy;
        p.z += p.vz;

        // 3D rotation (Y then X)
        const rx = p.x * cosRY - p.z * sinRY;
        const rz = p.x * sinRY + p.z * cosRY;
        const ry = p.y * cosRX - rz * sinRX;
        const finalZ = p.y * sinRX + rz * cosRX;

        // Project to screen
        const perspective = 3;
        const scale = perspective / (perspective + finalZ);
        const sx = cx + rx * sphereR * scale;
        const sy = cy + ry * sphereR * scale;

        projected.push({ p, sx, sy, sz: finalZ, depth: scale });
      }

      // Sort back-to-front
      projected.sort((a, b) => a.sz - b.sz);

      for (const { p, sx, sy, sz, depth } of projected) {
        // Depth-based alpha and size
        const depthAlpha = 0.25 + (sz + 1) * 0.375; // 0.25 (back) → 1.0 (front)
        const twinkle = 0.75 + 0.25 * Math.sin(time * 3.5 + p.phase);

        const alpha =
          depthAlpha * twinkle * p.brightness * lerp(0.8, 1.3, Math.max(s.pulse, s.hover * 0.8));

        const drawSize =
          p.baseSize * depth * dpr * lerp(1.0, 2.0, Math.max(s.pulse, s.hover * 0.7));

        // Color — shift to red on error
        let r: number, g: number, b: number;
        if (s.error > 0.1) {
          // Error: red family
          if (p.hue === 0) {
            r = Math.round(lerp(40 + sz * 30, 255, s.error));
            g = Math.round(lerp(80 + sz * 40, 40 + sz * 20, s.error));
            b = Math.round(lerp(255, 40, s.error));
          } else {
            r = Math.round(lerp(100 + sz * 30, 230, s.error));
            g = Math.round(lerp(220 + sz * 17, 60, s.error));
            b = Math.round(lerp(20, 20, s.error));
          }
        } else if (p.hue === 0) {
          r = 60 + Math.round(sz * 40);
          g = 100 + Math.round(sz * 50);
          b = 255;
        } else {
          r = 120 + Math.round(sz * 30);
          g = 237 + Math.round(sz * 10);
          b = 30;
        }

        // Thinking: shift toward brighter, more saturated
        if (s.pulse > 0.1) {
          r = Math.round(lerp(r, p.hue === 0 ? 100 : 130, s.pulse * 0.3));
          g = Math.round(lerp(g, p.hue === 0 ? 140 : 237, s.pulse * 0.3));
          b = Math.round(lerp(b, p.hue === 0 ? 255 : 32, s.pulse * 0.3));
        }

        // Micro glow — always visible, stronger on front
        if (depthAlpha > 0.25) {
          const gSize = drawSize * lerp(4, 7, s.hover);
          const pg = ctx.createRadialGradient(sx, sy, 0, sx, sy, gSize);
          pg.addColorStop(0, `rgba(${r},${g},${b},${alpha * 0.5})`);
          pg.addColorStop(1, `rgba(${r},${g},${b},0)`);
          ctx.fillStyle = pg;
          ctx.beginPath();
          ctx.arc(sx, sy, gSize, 0, Math.PI * 2);
          ctx.fill();
        }

        // Core dot — bright
        ctx.fillStyle = `rgba(${Math.min(r + 40, 255)},${Math.min(g + 30, 255)},${b},${Math.min(alpha * 1.6, 1)})`;
        ctx.beginPath();
        ctx.arc(sx, sy, Math.max(drawSize * 0.5, 0.3 * dpr), 0, Math.PI * 2);
        ctx.fill();
      }

      // ── Loading rings (thinking) ──
      if (s.pulse > 0.02) {
        ctx.save();
        ctx.translate(cx, cy);

        // Spinning arc
        const spinAngle = time * 2;
        const arcLen = Math.PI * lerp(0.3, 1.0, (Math.sin(time * 1.5) + 1) / 2);
        ctx.rotate(spinAngle);
        ctx.strokeStyle = `rgba(130, 237, 32, ${s.pulse * 0.4})`;
        ctx.lineWidth = 1.2 * dpr;
        ctx.lineCap = 'round';
        ctx.beginPath();
        ctx.arc(0, 0, sphereR * 1.25, 0, arcLen);
        ctx.stroke();

        // Counter-spinning arc
        ctx.rotate(-spinAngle * 2);
        ctx.strokeStyle = `rgba(1, 29, 255, ${s.pulse * 0.3})`;
        ctx.lineWidth = 0.8 * dpr;
        ctx.beginPath();
        ctx.arc(0, 0, sphereR * 1.35, 0, arcLen * 0.6);
        ctx.stroke();

        ctx.restore();

        // Expanding pulse
        const pulsePhase = (time * 0.8) % 1;
        const pulseR = sphereR * (1 + pulsePhase * 1.5);
        const pulseA = s.pulse * (1 - pulsePhase) * 0.15;
        ctx.strokeStyle = `rgba(130, 237, 32, ${pulseA})`;
        ctx.lineWidth = 1 * dpr;
        ctx.beginPath();
        ctx.arc(cx, cy, pulseR, 0, Math.PI * 2);
        ctx.stroke();
      }

      animRef.current = requestAnimationFrame(drawStep);
    },
    [isThinking, hasError],
  );

  useEffect(() => {
    animRef.current = requestAnimationFrame(draw);
    return () => cancelAnimationFrame(animRef.current);
  }, [draw]);

  return (
    <div
      ref={wrapRef}
      className="w-full h-full relative overflow-visible"
      onPointerMove={onMove}
      onPointerEnter={onEnter}
      onPointerLeave={onLeave}
      style={{ cursor: 'pointer' }}
    >
      <canvas ref={canvasRef} className="w-full h-full block" style={{ imageRendering: 'auto' }} />
    </div>
  );
}
