// Particle orb — original implementation. Renders a rotating sphere of dots,
// with an audio-reactive pulse driven by the `intensity` prop (0..1).
function Orb({ size = 320, intensity = 0.4, color = '#22d3ee', state = 'idle' }) {
  const canvasRef = React.useRef(null);
  const rafRef = React.useRef(0);
  const stateRef = React.useRef({ intensity, color, state, t: 0 });
  stateRef.current.intensity = intensity;
  stateRef.current.color = color;
  stateRef.current.state = state;

  // The orb is drawn on a canvas larger than the requested visual size so the
  // halo glow has room to fade out before hitting the canvas edge. Without this,
  // the halo clips into a visible square box around the orb.
  const HALO_PADDING = 1.6; // canvas is HALO_PADDING * size on each axis
  const canvasSize = Math.round(size * HALO_PADDING);

  React.useEffect(() => {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    canvas.width = canvasSize * dpr;
    canvas.height = canvasSize * dpr;
    ctx.scale(dpr, dpr);

    // Generate evenly-distributed sphere points (Fibonacci sphere)
    const N = 1100;
    const pts = [];
    const phi = Math.PI * (3 - Math.sqrt(5));
    for (let i = 0; i < N; i++) {
      const y = 1 - (i / (N - 1)) * 2;
      const r = Math.sqrt(1 - y * y);
      const theta = phi * i;
      pts.push({
        x: Math.cos(theta) * r,
        y: y,
        z: Math.sin(theta) * r,
        // randomized "twinkle" offsets for shimmer
        tw: Math.random() * Math.PI * 2,
        sz: 0.6 + Math.random() * 1.4,
      });
    }

    // additional "noise" particles drifting outside the sphere
    const drift = [];
    for (let i = 0; i < 80; i++) {
      drift.push({
        a: Math.random() * Math.PI * 2,
        r: 1.05 + Math.random() * 0.5,
        speed: 0.0005 + Math.random() * 0.001,
        y: (Math.random() - 0.5) * 1.6,
        sz: Math.random() * 1.2,
      });
    }

    const draw = () => {
      const cx = canvasSize / 2;
      const cy = canvasSize / 2;
      const t = (stateRef.current.t += 0.004);
      const I = stateRef.current.intensity;
      const col = stateRef.current.color;
      const st = stateRef.current.state;

      // pulse modifier — "listening" pulses, "thinking" spins faster
      const pulse = st === 'listening'
        ? 1 + 0.06 * Math.sin(t * 12)
        : st === 'thinking'
          ? 1 + 0.02 * Math.sin(t * 6)
          : 1 + 0.015 * Math.sin(t * 2);

      const baseR = (size * 0.34) * pulse;

      // rotate around Y axis (and slight X tilt)
      const ry = t * (st === 'thinking' ? 1.2 : 0.4);
      const rx = Math.sin(t * 0.3) * 0.18;

      ctx.clearRect(0, 0, canvasSize, canvasSize);

      // Soft outer glow halo
      const halo = ctx.createRadialGradient(cx, cy, baseR * 0.3, cx, cy, baseR * 1.9);
      halo.addColorStop(0, hexA(col, 0.18 + I * 0.25));
      halo.addColorStop(0.5, hexA(col, 0.05 + I * 0.08));
      halo.addColorStop(1, hexA(col, 0));
      ctx.fillStyle = halo;
      ctx.fillRect(0, 0, canvasSize, canvasSize);

      // Inner core — soft tinted glow on light backgrounds
      const core = ctx.createRadialGradient(cx, cy, 0, cx, cy, baseR * 0.85);
      core.addColorStop(0, hexA(col, 0.10));
      core.addColorStop(0.7, hexA(col, 0.04));
      core.addColorStop(1, hexA(col, 0));
      ctx.fillStyle = core;
      ctx.beginPath();
      ctx.arc(cx, cy, baseR * 0.85, 0, Math.PI * 2);
      ctx.fill();

      // sphere points
      const cosY = Math.cos(ry), sinY = Math.sin(ry);
      const cosX = Math.cos(rx), sinX = Math.sin(rx);

      // sort by depth for proper layering
      const projected = pts.map((p) => {
        // rotate Y
        const x1 = p.x * cosY - p.z * sinY;
        const z1 = p.x * sinY + p.z * cosY;
        // rotate X
        const y2 = p.y * cosX - z1 * sinX;
        const z2 = p.y * sinX + z1 * cosX;
        return { x: x1, y: y2, z: z2, tw: p.tw, sz: p.sz };
      }).sort((a, b) => a.z - b.z);

      for (const p of projected) {
        const persp = 1 / (2 - p.z * 0.4);
        const px = cx + p.x * baseR * persp;
        const py = cy + p.y * baseR * persp;
        // depth-based opacity
        const depth = (p.z + 1) / 2; // 0 (back) .. 1 (front)
        const twinkle = 0.7 + 0.3 * Math.sin(t * 6 + p.tw);
        const alpha = (0.15 + depth * 0.85) * twinkle * (0.6 + I * 0.5);
        ctx.fillStyle = hexA(col, Math.min(1, alpha));
        const r = p.sz * (0.6 + depth * 0.8);
        ctx.beginPath();
        ctx.arc(px, py, r, 0, Math.PI * 2);
        ctx.fill();
      }

      // bright "equator" arc for sci-fi feel
      ctx.save();
      ctx.translate(cx, cy);
      ctx.rotate(rx * 0.5);
      ctx.strokeStyle = hexA(col, 0.18 + I * 0.2);
      ctx.lineWidth = 1;
      ctx.beginPath();
      ctx.ellipse(0, 0, baseR, baseR * 0.18, 0, 0, Math.PI * 2);
      ctx.stroke();
      ctx.strokeStyle = hexA(col, 0.1);
      ctx.beginPath();
      ctx.ellipse(0, 0, baseR * 1.06, baseR * 0.22, 0, 0, Math.PI * 2);
      ctx.stroke();
      ctx.restore();

      // drifting outer particles
      for (const d of drift) {
        d.a += d.speed * (1 + I * 4);
        const dx = cx + Math.cos(d.a) * baseR * d.r;
        const dy = cy + d.y * baseR * 0.4 + Math.sin(d.a * 2) * 4;
        ctx.fillStyle = hexA(col, 0.4 + Math.sin(t * 3 + d.a) * 0.2);
        ctx.beginPath();
        ctx.arc(dx, dy, d.sz, 0, Math.PI * 2);
        ctx.fill();
      }

      rafRef.current = requestAnimationFrame(draw);
    };
    draw();
    return () => cancelAnimationFrame(rafRef.current);
  }, [size]);

  // The wrapper occupies the requested visual `size` for layout purposes, but
  // the canvas inside is larger and absolutely-centered so the halo glow can
  // bleed past the wrapper bounds without being clipped into a visible square.
  return (
    <div
      style={{
        width: size,
        height: size,
        position: 'relative',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
      }}
    >
      <canvas
        ref={canvasRef}
        style={{
          width: canvasSize,
          height: canvasSize,
          position: 'absolute',
          top: '50%',
          left: '50%',
          transform: 'translate(-50%, -50%)',
          display: 'block',
          pointerEvents: 'none',
          filter: state === 'thinking' ? 'saturate(1.2)' : 'none',
        }}
      />
    </div>
  );
}

function hexA(hex, a) {
  // accept #rgb / #rrggbb
  let h = hex.replace('#', '');
  if (h.length === 3) h = h.split('').map((c) => c + c).join('');
  const r = parseInt(h.substring(0, 2), 16);
  const g = parseInt(h.substring(2, 4), 16);
  const b = parseInt(h.substring(4, 6), 16);
  return `rgba(${r},${g},${b},${a})`;
}

window.Orb = Orb;
window.hexA = hexA;
