// Canvas-rendered glowing eye halo.
// Uses a single useEffect with robust setup; no ref-capture games.

const { useRef, useEffect } = React;

function Halo(props) {
  const canvasRef = useRef(null);
  // Stuff props into refs so the RAF loop always reads latest
  const propsRef = useRef(props);
  propsRef.current = props;

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    if (!ctx) return;
    let w = 1, h = 1, dpr = 1;
    let rafId = 0;
    let intervalId = 0;
    let alive = true;
    const start = performance.now();

    const resize = () => {
      const rect = canvas.getBoundingClientRect();
      dpr = Math.min(window.devicePixelRatio || 1, 2);
      w = Math.max(1, rect.width);
      h = Math.max(1, rect.height);
      canvas.width = Math.round(w * dpr);
      canvas.height = Math.round(h * dpr);
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };

    const draw = (now) => {
      if (!alive) return;
      try {
        const t = (now - start) / 1000;
        ctx.clearRect(0, 0, w, h);
        const cx = w / 2;
        const cy = h / 2;
        const R = Math.min(w, h) * 0.4;
        const p = propsRef.current;
        const s = p.state || "idle";
        const style = p.style || "eye";
        const hue = p.accentHue ?? 22;

        const amp = s === "thinking" ? 1 : s === "speaking" ? 1.4 : s === "listening" ? 0.6 : 0.35;
        const speed = s === "thinking" ? 1.6 : s === "speaking" ? 2.2 : s === "listening" ? 1.1 : 0.7;

        if (style === "rings") drawRings(ctx, cx, cy, R, t, s, hue, speed, amp);
        else if (style === "orb") drawOrb(ctx, cx, cy, R, t, s, hue, speed, amp);
        else drawEye(ctx, cx, cy, R, t, s, hue, speed, amp);
      } catch (e) {
        console.error('[halo] draw error', e);
      }
    };

    // Schedule next frame via RAF when visible, setInterval fallback when hidden.
    // This guarantees the halo renders at least a poster frame even in offscreen/hidden iframes.
    const tick = () => {
      if (!alive) return;
      draw(performance.now());
      rafId = requestAnimationFrame(tick);
    };

    resize();
    // Draw an initial frame synchronously so something is always visible
    draw(performance.now());
    rafId = requestAnimationFrame(tick);
    // Low-frequency fallback in case RAF is throttled/paused (hidden tab/iframe)
    intervalId = setInterval(() => { if (alive) draw(performance.now()); }, 100);
    const ro = new ResizeObserver(resize);
    ro.observe(canvas);
    window.addEventListener('resize', resize);

    return () => {
      alive = false;
      cancelAnimationFrame(rafId);
      clearInterval(intervalId);
      ro.disconnect();
      window.removeEventListener('resize', resize);
    };
  }, []);

  return React.createElement('canvas', {
    ref: canvasRef,
    style: { width: "100%", height: "100%", display: "block" }
  });
}

function drawEye(ctx, cx, cy, R, t, state, hue, speed, amp) {
  const surround = ctx.createRadialGradient(cx, cy, R * 0.9, cx, cy, R * 1.25);
  surround.addColorStop(0, "rgba(0,0,0,0)");
  surround.addColorStop(1, "rgba(0,0,0,0.85)");
  ctx.fillStyle = surround;
  ctx.beginPath();
  ctx.arc(cx, cy, R * 1.25, 0, Math.PI * 2);
  ctx.fill();

  const bloomR = R * (1.15 + 0.05 * Math.sin(t * speed));
  const bloom = ctx.createRadialGradient(cx, cy, R * 0.5, cx, cy, bloomR);
  bloom.addColorStop(0, `oklch(0.74 0.09 ${hue} / 0.32)`);
  bloom.addColorStop(0.5, `oklch(0.5 0.08 ${hue} / 0.16)`);
  bloom.addColorStop(1, `oklch(0.4 0.06 ${hue} / 0)`);
  ctx.fillStyle = bloom;
  ctx.beginPath();
  ctx.arc(cx, cy, bloomR, 0, Math.PI * 2);
  ctx.fill();

  const irisR = R * 0.88;
  const iris = ctx.createRadialGradient(cx, cy, irisR * 0.05, cx, cy, irisR);
  iris.addColorStop(0, "#0a0607");
  iris.addColorStop(0.25, "#1a0a0b");
  iris.addColorStop(0.55, `oklch(0.42 0.08 ${hue})`);
  iris.addColorStop(0.85, `oklch(0.7 0.09 ${hue})`);
  iris.addColorStop(1, `oklch(0.82 0.08 ${hue})`);
  ctx.fillStyle = iris;
  ctx.beginPath();
  ctx.arc(cx, cy, irisR, 0, Math.PI * 2);
  ctx.fill();

  const jitter = state === "thinking" ? (Math.sin(t * 9) * 2) : 0;
  const pupilR = irisR * (0.28 + 0.05 * Math.sin(t * speed * 0.9));
  ctx.fillStyle = "#000";
  ctx.beginPath();
  ctx.arc(cx + jitter, cy, pupilR, 0, Math.PI * 2);
  ctx.fill();

  const coreR = pupilR * (0.6 + 0.25 * Math.sin(t * speed * 1.3));
  const core = ctx.createRadialGradient(cx, cy, 0, cx, cy, coreR);
  core.addColorStop(0, `oklch(0.96 0.06 ${hue})`);
  core.addColorStop(0.4, `oklch(0.86 0.09 ${hue} / 0.55)`);
  core.addColorStop(1, `oklch(0.7 0.08 ${hue} / 0)`);
  ctx.fillStyle = core;
  ctx.beginPath();
  ctx.arc(cx, cy, coreR, 0, Math.PI * 2);
  ctx.fill();

  // Scanline flicker
  ctx.save();
  ctx.globalCompositeOperation = "overlay";
  ctx.beginPath();
  ctx.arc(cx, cy, irisR, 0, Math.PI * 2);
  ctx.clip();
  for (let y = 0; y < irisR * 2; y += 3) {
    ctx.fillStyle = "rgba(0,0,0,0.08)";
    ctx.fillRect(cx - irisR, cy - irisR + y, irisR * 2, 1);
  }
  const sweepY = cy - irisR + ((t * 30) % (irisR * 2));
  const grad = ctx.createLinearGradient(0, sweepY - 40, 0, sweepY + 40);
  grad.addColorStop(0, "rgba(255,255,255,0)");
  grad.addColorStop(0.5, `oklch(0.93 0.06 ${hue} / 0.15)`);
  grad.addColorStop(1, "rgba(255,255,255,0)");
  ctx.fillStyle = grad;
  ctx.fillRect(cx - irisR, sweepY - 40, irisR * 2, 80);
  ctx.restore();

  ctx.strokeStyle = `oklch(0.9 0.06 ${hue} / 0.5)`;
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.arc(cx, cy, irisR + 1, 0, Math.PI * 2);
  ctx.stroke();

  if (state === "listening" || state === "speaking" || state === "thinking") {
    const count = 72;
    const ringR = irisR * 1.08;
    for (let i = 0; i < count; i++) {
      const a = (i / count) * Math.PI * 2 + t * (state === "speaking" ? 0.6 : 0.15);
      const noise = Math.sin(i * 0.7 + t * 3) * 0.5 + 0.5;
      const tickAmp = state === "speaking" ? 18 : state === "thinking" ? 10 : 6;
      const len = 4 + noise * tickAmp * amp;
      const x1 = cx + Math.cos(a) * ringR;
      const y1 = cy + Math.sin(a) * ringR;
      const x2 = cx + Math.cos(a) * (ringR + len);
      const y2 = cy + Math.sin(a) * (ringR + len);
      ctx.strokeStyle = `oklch(0.88 0.08 ${hue} / ${0.22 + noise * 0.45})`;
      ctx.lineWidth = 1.2;
      ctx.beginPath();
      ctx.moveTo(x1, y1);
      ctx.lineTo(x2, y2);
      ctx.stroke();
    }
  }
}

function drawRings(ctx, cx, cy, R, t, state, hue, speed, amp) {
  const surround = ctx.createRadialGradient(cx, cy, R * 0.1, cx, cy, R * 1.2);
  surround.addColorStop(0, `oklch(0.3 0.05 ${hue} / 0.1)`);
  surround.addColorStop(1, "rgba(0,0,0,0)");
  ctx.fillStyle = surround;
  ctx.beginPath();
  ctx.arc(cx, cy, R * 1.2, 0, Math.PI * 2);
  ctx.fill();

  for (let i = 0; i < 5; i++) {
    const phase = i / 5;
    const r = R * (0.3 + phase * 0.7) + Math.sin(t * speed + i) * 4 * amp;
    ctx.strokeStyle = `oklch(${0.6 + phase * 0.3} 0.08 ${hue} / ${0.25 + phase * 0.3})`;
    ctx.lineWidth = 1 + (1 - phase) * 2;
    ctx.beginPath();
    ctx.arc(cx, cy, r, 0, Math.PI * 2);
    ctx.stroke();
  }
  const coreR = 8 + Math.sin(t * speed * 1.5) * 3 * amp;
  const core = ctx.createRadialGradient(cx, cy, 0, cx, cy, coreR * 3);
  core.addColorStop(0, `oklch(0.92 0.07 ${hue})`);
  core.addColorStop(1, `oklch(0.7 0.08 ${hue} / 0)`);
  ctx.fillStyle = core;
  ctx.beginPath();
  ctx.arc(cx, cy, coreR * 3, 0, Math.PI * 2);
  ctx.fill();
}

function drawOrb(ctx, cx, cy, R, t, state, hue, speed, amp) {
  const count = 220;
  const orbR = R * 0.85;
  const bloom = ctx.createRadialGradient(cx, cy, 0, cx, cy, orbR * 1.1);
  bloom.addColorStop(0, `oklch(0.6 0.08 ${hue} / 0.22)`);
  bloom.addColorStop(1, `oklch(0.4 0.08 ${hue} / 0)`);
  ctx.fillStyle = bloom;
  ctx.beginPath();
  ctx.arc(cx, cy, orbR * 1.1, 0, Math.PI * 2);
  ctx.fill();
  for (let i = 0; i < count; i++) {
    const seed = i * 137.5;
    const a1 = seed * 0.017 + t * speed * 0.3;
    const a2 = seed * 0.023 + t * speed * 0.5;
    const rr = orbR * (0.75 + 0.25 * Math.sin(seed + t * speed));
    const x = cx + Math.cos(a1) * rr + Math.cos(a2) * 8 * amp;
    const y = cy + Math.sin(a1) * rr + Math.sin(a2) * 8 * amp;
    const size = 1 + (Math.sin(seed + t * 2) + 1) * 1.2;
    ctx.fillStyle = `oklch(0.88 0.09 ${hue} / ${0.3 + 0.5 * Math.sin(seed + t)})`;
    ctx.beginPath();
    ctx.arc(x, y, size, 0, Math.PI * 2);
    ctx.fill();
  }
}

window.Halo = Halo;
