import { useEffect, useRef } from 'react';
import { G, DOT_R, CANVAS, FACE, byId, getSpeakMouth } from '../data/expressions';

// ── Drawing primitives ────────────────────────────────────────────

/**
 * drawDots with optional twinkling.
 * When t is provided, dots are split into bright/dim groups each frame
 * using a per-dot phase hash → asynchronous LED shimmer effect.
 * When t is null (transition/fade), uses single-batch draw for performance.
 */
function drawDots(ctx, pts, color, alpha, t = null) {
  if (!pts || pts.length === 0) return;
  ctx.save();

  if (t == null) {
    // ── Static: one batched fill (fast)
    ctx.globalAlpha = Math.max(0, Math.min(1, alpha));
    ctx.shadowBlur = 14;
    ctx.shadowColor = color;
    ctx.fillStyle = color;
    ctx.beginPath();
    for (const [gx, gy] of pts) {
      const x = gx * G + G / 2, y = gy * G + G / 2;
      ctx.moveTo(x + DOT_R, y);
      ctx.arc(x, y, DOT_R, 0, Math.PI * 2);
    }
    ctx.fill();

    ctx.shadowBlur = 0;
    ctx.fillStyle = 'rgba(255,255,255,0.55)';
    ctx.beginPath();
    for (const [gx, gy] of pts) {
      const x = gx * G + G / 2, y = gy * G + G / 2;
      ctx.moveTo(x + DOT_R * 0.38, y);
      ctx.arc(x, y, DOT_R * 0.38, 0, Math.PI * 2);
    }
    ctx.fill();

  } else {
    // ── Live: split by per-dot sine phase → bright/dim groups
    const bright = [], dim = [];
    for (const [gx, gy] of pts) {
      const phase = (gx * 7.3 + gy * 13.1) * 0.31;
      (Math.sin(t * 1.1 + phase) > 0 ? bright : dim).push([gx, gy]);
    }

    const drawGroup = (group, a, blur) => {
      if (!group.length) return;
      ctx.globalAlpha = Math.max(0, Math.min(1, a));
      ctx.shadowBlur = blur;
      ctx.shadowColor = color;
      ctx.fillStyle = color;
      ctx.beginPath();
      for (const [gx, gy] of group) {
        const x = gx * G + G / 2, y = gy * G + G / 2;
        ctx.moveTo(x + DOT_R, y);
        ctx.arc(x, y, DOT_R, 0, Math.PI * 2);
      }
      ctx.fill();
    };

    drawGroup(dim,    alpha * 0.68, 7);   // dim group: subdued glow
    drawGroup(bright, alpha,        20);  // bright group: extra glow

    // White cores (always batched)
    ctx.globalAlpha = Math.max(0, Math.min(1, alpha * 0.55));
    ctx.shadowBlur = 0;
    ctx.fillStyle = 'rgba(255,255,255,0.55)';
    ctx.beginPath();
    for (const [gx, gy] of pts) {
      const x = gx * G + G / 2, y = gy * G + G / 2;
      ctx.moveTo(x + DOT_R * 0.38, y);
      ctx.arc(x, y, DOT_R * 0.38, 0, Math.PI * 2);
    }
    ctx.fill();
  }

  ctx.restore();
}

// Squeeze dot Y-coords toward the eye centre row (blink squash)
function applyBlink(pts, eyeCY, blinkScale) {
  if (blinkScale >= 1) return pts;
  return pts.map(([gx, gy]) => [gx, eyeCY + (gy - eyeCY) * blinkScale]);
}

function getBlinkScale(blinkStart, now) {
  if (blinkStart === null) return 1;
  const e = now - blinkStart;
  if (e < 120) return 1 - e / 120;      // 0–120 ms: closing
  if (e < 220) return 0;                 // 120–220 ms: closed
  if (e < 340) return (e - 220) / 120;  // 220–340 ms: opening
  return 1;
}

function drawEye(ctx, eyeData, cx, cy, color, pupilColor, alpha, blinkScale, t) {
  if (!eyeData) return;
  const pc = pupilColor || '#001020';

  if (eyeData.iris) {
    drawDots(ctx, applyBlink(eyeData.iris,  cy, blinkScale), color,     alpha,        t);
    drawDots(ctx, applyBlink(eyeData.pupil, cy, blinkScale), pc,        alpha,        null);
    if (eyeData.hl)
      drawDots(ctx, applyBlink(eyeData.hl,  cy, blinkScale), '#ffffff', alpha * 0.9,  null);
    if (eyeData.lash)
      drawDots(ctx, applyBlink(eyeData.lash,cy, blinkScale), color,     alpha,        null);
  } else if (eyeData.closed !== undefined) {
    drawDots(ctx, eyeData.closed, color, alpha, t);
    if (eyeData.lash) drawDots(ctx, eyeData.lash, color, alpha, null);
  } else if (eyeData.wink)   { drawDots(ctx, eyeData.wink,   color, alpha, t); }
  else if (eyeData.heart)    { drawDots(ctx, eyeData.heart,  color, alpha, t); }
  else if (eyeData.spiral)   { drawDots(ctx, eyeData.spiral, color, alpha, t); }
  else if (eyeData.star)     { drawDots(ctx, eyeData.star,   color, alpha, t); }
  else if (eyeData.pts)      {
    // pts-based micro-expression eyes: support blink + sparkle
    drawDots(ctx, applyBlink(eyeData.pts, cy, blinkScale), color, alpha, t);
  }
}

// ── RobotFace component ───────────────────────────────────────────

export default function RobotFace({ expressionId, scale = 2 }) {
  const canvasRef = useRef(null);
  const animRef   = useRef({
    currentId:       expressionId,
    previousId:      null,
    transitionStart: null,
    blinkStart:      null,
    nextBlink:       performance.now() + 2000 + Math.random() * 2000,
    t:               0,
    raf:             null,
  });

  // Trigger cross-fade whenever the prop changes
  useEffect(() => {
    const s = animRef.current;
    if (expressionId !== s.currentId) {
      s.previousId      = s.currentId;
      s.currentId       = expressionId;
      s.transitionStart = performance.now();
      s.blinkStart      = null;
    }
  }, [expressionId]);

  // One-time RAF loop
  useEffect(() => {
    const canvas = canvasRef.current;
    const ctx    = canvas.getContext('2d');
    const s      = animRef.current;

    function drawExpression(expr, alpha, blinkScale, t) {
      if (!expr) return;
      drawEye(ctx, expr.leftEye,  FACE.LE[0], FACE.LE[1],
              expr.color, expr.pupilColor, alpha, blinkScale, t);
      drawEye(ctx, expr.rightEye, FACE.RE[0], FACE.RE[1],
              expr.color, expr.pupilColor, alpha, blinkScale, t);

      const mouth = expr.speakingMouth
        ? getSpeakMouth(FACE.MO[0], FACE.MO[1], s.t)
        : expr.mouth;
      if (mouth) drawDots(ctx, mouth, expr.color, alpha, t);

      if (expr.extras) {
        for (const extra of expr.extras) {
          let ea = alpha * extra.alpha;
          if (extra.zzzIndex !== undefined) {
            const phase = s.t - extra.zzzIndex * 1.2;
            ea *= 0.35 + 0.65 * (Math.sin(phase) * 0.5 + 0.5);
          }
          drawDots(ctx, extra.dots, extra.color, ea, null); // extras: no sparkle
        }
      }
    }

    function render() {
      const now = performance.now();
      s.t += 0.04;

      ctx.clearRect(0, 0, CANVAS, CANVAS);
      ctx.fillStyle = '#000';
      ctx.fillRect(0, 0, CANVAS, CANVAS);

      // Transition alphas (ease-in-out, 380 ms)
      let curAlpha = 1, prevAlpha = 0;
      if (s.transitionStart !== null) {
        const elapsed = now - s.transitionStart;
        if (elapsed >= 380) {
          s.transitionStart = null;
          s.previousId      = null;
        } else {
          const p     = elapsed / 380;
          const eased = p < 0.5 ? 2 * p * p : 1 - (-2 * p + 2) ** 2 / 2;
          curAlpha  = eased;
          prevAlpha = 1 - eased;
        }
      }

      // Blink scheduling
      const curExpr = byId[s.currentId];
      if (!s.blinkStart && s.transitionStart === null &&
          curExpr?.canBlink && now >= s.nextBlink) {
        s.blinkStart = now;
        s.nextBlink  = now + 2500 + Math.random() * 2500; // 2.5–5 s between blinks
      }
      if (s.blinkStart !== null && now - s.blinkStart >= 340) s.blinkStart = null;
      const blinkScale = getBlinkScale(s.blinkStart, now);

      // Previous: fading out, no sparkle
      if (s.previousId) drawExpression(byId[s.previousId], prevAlpha, 1, null);

      // Current: sparkle only when fully settled (not mid-transition)
      const liveT = s.transitionStart === null ? s.t : null;
      drawExpression(curExpr, curAlpha, blinkScale, liveT);

      // CRT scanlines overlay
      ctx.save();
      ctx.globalAlpha = 0.04;
      ctx.fillStyle   = '#000';
      for (let y = 0; y < CANVAS; y += 2) ctx.fillRect(0, y + 1, CANVAS, 1);
      ctx.restore();

      s.raf = requestAnimationFrame(render);
    }

    s.raf = requestAnimationFrame(render);
    return () => cancelAnimationFrame(s.raf);
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  return (
    <canvas
      ref={canvasRef}
      width={CANVAS}
      height={CANVAS}
      style={{
        width: CANVAS * scale,
        height: CANVAS * scale,
        imageRendering: 'pixelated',
        borderRadius: 14,
        boxShadow: '0 0 40px rgba(0,255,200,0.2)',
        display: 'block',
      }}
    />
  );
}
