// pixel-rig.jsx — "Tanu", pixel-art edition. A tiny software rasterizer paints
// the foxy-tanuki head onto a low-res canvas (nearest-neighbour upscaled), so it
// reads as genuine 16-bit pixel art — and it still reacts: cursor-look, blink,
// poke, type-flick, and the full mood set. Head only. Exports window.TanuPixel.
(function () {
  const { useState, useEffect, useRef, useCallback } = React;

  const GRID = 40;
  const C = {
    furL: '#7fc0ac', furM: '#4f8f80', furD: '#2b5b4d',
    cream: '#fdf6e7', creamD: '#e7d4b4',
    mask: '#16241f', earin: '#f2dcba',
    iris: '#e7c089', irisD: '#a9712f', pupil: '#160d06', white: '#ffffff',
    nose: '#1a2420', blush: '#ef8e74', spark: '#f3c34a',
  };

  // ── geometry helpers (40×40 space, head centred at x=20) ──
  const inEll = (x, y, cx, cy, rx, ry) => {
    const dx = (x - cx) / rx, dy = (y - cy) / ry; return dx * dx + dy * dy <= 1;
  };
  const inTri = (px, py, a, b, c) => {
    const s = (a[0]-c[0])*(py-c[1])-(a[1]-c[1])*(px-c[0]);
    const t = (b[0]-a[0])*(py-a[1])-(b[1]-a[1])*(px-a[0]);
    if ((s < 0) !== (t < 0) && s !== 0 && t !== 0) return false;
    const d = (c[0]-b[0])*(py-b[1])-(c[1]-b[1])*(px-b[0]);
    return d === 0 || (d < 0) === (s + t <= 0);
  };

  function expr(mood) {
    switch (mood) {
      case 'curious':   return { eye: 'wide', mouth: 'smile', look: [0, -1], blush: 1, spark: 0 };
      case 'thinking':  return { eye: 'half', mouth: 'tiny', look: [2, -1], blush: 0, spark: 0 };
      case 'listening': return { eye: 'wide', mouth: 'open', look: [0, 0], blush: 1, spark: 0 };
      case 'success':   return { eye: 'happy', mouth: 'grin', look: [0, 0], blush: 1, spark: 1 };
      case 'error':     return { eye: 'wide', mouth: 'wavy', look: [0, 1], blush: 0, spark: 0 };
      case 'excited':   return { eye: 'huge', mouth: 'o', look: [0, 0], blush: 1, spark: 1 };
      case 'surprised': return { eye: 'huge', mouth: 'o', look: [0, 0], blush: 0, spark: 0 };
      case 'yawn':      return { eye: 'happy', mouth: 'open', look: [0, 0], blush: 0, spark: 0 };
      default:          return { eye: 'open', mouth: 'smirk', look: null, blush: 0, spark: 0 };
    }
  }

  function paint(P, mood, lookX, lookY, blinkOn) {
    const E = expr(mood);
    const g = []; for (let i = 0; i < GRID * GRID; i++) g.push(null);
    const set = (x, y, c) => { x = Math.round(x); y = Math.round(y); if (x >= 0 && x < GRID && y >= 0 && y < GRID && c) g[y * GRID + x] = c; };
    const cx = 20;

    // ── ears (behind head) ──
    const ears = [
      { tip: [7, 2], bl: [8, 13], br: [17, 9], inn: [[9, 5], [11, 12], [15, 10]] },
      { tip: [33, 2], bl: [23, 9], br: [32, 13], inn: [[31, 5], [29, 12], [25, 10]] },
    ];
    for (const ear of ears) {
      for (let y = 0; y < 20; y++) for (let x = 0; x < GRID; x++) {
        if (inTri(x, y, ear.tip, ear.bl, ear.br)) {
          let col = C.furM;
          if (y <= 5) col = C.mask;                 // dark tip
          else if (inTri(x, y, ear.inn[0], ear.inn[1], ear.inn[2])) col = C.earin;
          set(x, y, col);
        }
      }
    }

    // ── head fur (with light/shadow banding) ──
    for (let y = 0; y < GRID; y++) for (let x = 0; x < GRID; x++) {
      if (inEll(x, y, cx, 21, 14, 13)) {
        const nx = (x - cx) / 14, ny = (y - 21) / 13;
        const v = nx * 0.55 + ny;
        let col = C.furM;
        if (v < -0.55) col = C.furL; else if (v > 0.62) col = C.furD;
        set(x, y, col);
      }
    }
    // cheek ruff tufts
    [[7, 19, 6, 26], [33, 19, 34, 26]].forEach(([x1, y1, x2, y2]) => {
      set(x1, y1, C.furM); set(x2, (y1 + y2) / 2, C.furD); set(x2, y2, C.furD);
    });

    // ── fox mask (around eyes) ──
    for (let y = 10; y < 20; y++) for (let x = 4; x < 36; x++) {
      if (!inEll(x, y, cx, 21, 14, 13)) continue;
      if (inEll(x, y, 14, 15, 5, 4) || inEll(x, y, 26, 15, 5, 4) || (y >= 12 && y <= 16 && x >= 14 && x <= 26)) {
        set(x, y, C.mask);
      }
    }

    // ── cream muzzle (lower face) ──
    for (let y = 20; y < 36; y++) for (let x = 0; x < GRID; x++) {
      if (inEll(x, y, cx, 28, 8, 7) && inEll(x, y, cx, 21, 14.5, 13.5)) {
        set(x, y, (y > 31 ? C.creamD : C.cream));
      }
    }
    // blush
    if (E.blush) { [[12, 27], [28, 27]].forEach(([x, y]) => { set(x, y, C.blush); set(x + 1, y, C.blush); }); }

    // ── eyes ──
    const eyes = [[14, 16], [26, 16]];
    const dims = { open: [2.4, 2.8], wide: [2.6, 3.2], huge: [2.9, 3.5], half: [2.4, 1.6] }[E.eye] || [2.4, 2.8];
    const lk = E.look || [lookX, lookY];
    eyes.forEach(([ex, ey]) => {
      if (E.eye === 'happy') { // closed, happy arc ^
        [[-2, 1], [-1, 0], [0, -1], [1, 0], [2, 1]].forEach(([dx, dy]) => set(ex + dx, ey + dy, C.mask));
        return;
      }
      if (blinkOn) { for (let dx = -2; dx <= 2; dx++) set(ex + dx, ey, C.mask); return; }
      const [rx, ry] = dims;
      for (let y = -4; y <= 4; y++) for (let x = -4; x <= 4; x++) {
        if (inEll(x, y, 0, 0, rx, ry)) set(ex + x, ey + y, C.white);
      }
      for (let y = -3; y <= 3; y++) for (let x = -3; x <= 3; x++) {
        if (inEll(x, y, 0, 0.3, rx - 0.7, ry - 0.8)) set(ex + x, ey + y, y > 0 ? C.irisD : C.iris);
      }
      const px = Math.max(-1, Math.min(1, Math.round(lk[0]))), py = Math.max(-1, Math.min(1, Math.round(lk[1])));
      set(ex + px, ey + py, C.pupil); set(ex + px, ey + py + 1, C.pupil);
      set(ex + px - 1, ey + py - 1, C.white); // catchlight
    });

    // ── nose ──
    [[19, 24], [20, 24], [21, 24], [20, 25]].forEach(([x, y]) => set(x, y, C.nose));

    // ── mouth ──
    const m = E.mouth, my = 28;
    if (m === 'smirk') { set(19, my + 1, C.nose); set(20, my + 1, C.nose); set(21, my, C.nose); set(22, my, C.nose); }
    else if (m === 'smile') { [[17, my], [18, my + 1], [20, my + 1], [22, my + 1], [23, my]].forEach(([x, y]) => set(x, y, C.nose)); }
    else if (m === 'tiny') { set(19, my, C.nose); set(20, my + 1, C.nose); set(21, my, C.nose); }
    else if (m === 'grin') { for (let x = 17; x <= 23; x++) { set(x, my, C.nose); set(x, my + 1, C.nose); } set(19, my + 2, C.blush); set(20, my + 2, C.blush); set(21, my + 2, C.blush); }
    else if (m === 'open') { for (let y = my; y <= my + 2; y++) for (let x = 19; x <= 21; x++) set(x, y, C.nose); }
    else if (m === 'o') { set(20, my, C.nose); set(19, my + 1, C.nose); set(21, my + 1, C.nose); set(20, my + 2, C.nose); }
    else if (m === 'wavy') { [[17, my], [18, my + 1], [19, my], [20, my + 1], [21, my], [22, my + 1], [23, my]].forEach(([x, y]) => set(x, y, C.nose)); }

    // ── sparkles ──
    if (E.spark) { [[5, 7], [35, 9], [33, 3]].forEach(([x, y]) => { set(x, y, C.spark); set(x - 1, y, C.spark); set(x + 1, y, C.spark); set(x, y - 1, C.spark); set(x, y + 1, C.spark); }); }

    return g;
  }

  function TanuPixel({ mood = null, size = 220, interactive = true, alive = true, greet = false,
                       pulse = 0, onPoke, className = '', styleProp = {} }) {
    const canvasRef = useRef(null);
    const wrapRef = useRef(null);
    const [look, setLook] = useState([0, 0]);
    const [blink, setBlink] = useState(false);
    const [transient, setTransient] = useState(null);
    const [bounce, setBounce] = useState('');
    const raf = useRef(0);

    const effMood = transient || mood || 'idle';

    // draw
    useEffect(() => {
      const cv = canvasRef.current; if (!cv) return;
      const ctx = cv.getContext('2d');
      ctx.clearRect(0, 0, GRID, GRID);
      const g = paint(C, effMood, look[0], look[1], blink && alive);
      for (let y = 0; y < GRID; y++) for (let x = 0; x < GRID; x++) {
        const c = g[y * GRID + x]; if (!c) continue; ctx.fillStyle = c; ctx.fillRect(x, y, 1, 1);
      }
    }, [effMood, look, blink, alive]);

    // blink + idle
    useEffect(() => {
      if (!alive) return;
      let on = true; const timers = [];
      const loop = () => {
        if (!on) return;
        const t = setTimeout(() => {
          if (!on) return;
          setBlink(true); timers.push(setTimeout(() => setBlink(false), 120));
          if (!mood && !transient && interactive && Math.random() < 0.4) {
            const gx = Math.random() < 0.5 ? -1.2 : 1.2; setLook([gx, 0]);
            timers.push(setTimeout(() => setLook([0, 0]), 900));
          }
          loop();
        }, 2600 + Math.random() * 2600);
        timers.push(t);
      };
      loop();
      return () => { on = false; timers.forEach(clearTimeout); };
    }, [alive, mood, transient, interactive]);

    // greet
    useEffect(() => {
      if (!greet) return;
      setTransient('success'); setBounce('fox-bounce');
      const a = setTimeout(() => setBounce(''), 900);
      const b = setTimeout(() => setTransient(null), 1500);
      return () => { clearTimeout(a); clearTimeout(b); };
    }, [greet]);

    // type pulse
    const lastPulse = useRef(pulse);
    useEffect(() => {
      if (pulse === lastPulse.current) return; lastPulse.current = pulse;
      if (mood && mood !== 'listening') return;
      setBlink(true); const a = setTimeout(() => setBlink(false), 90);
      return () => clearTimeout(a);
    }, [pulse, mood]);

    const poke = useCallback((e) => {
      e && e.preventDefault();
      setTransient('surprised'); setBounce('fox-squish'); onPoke && onPoke();
      setTimeout(() => { setBounce(''); setTransient('success'); }, 440);
      setTimeout(() => setTransient(null), 1100);
    }, [onPoke]);

    const fxCls = effMood === 'error' ? 'fox-shake' : bounce;

    return (
      <div className={`fox-wrap ${className}`} ref={wrapRef}
        style={{ width: size, height: size, display: 'inline-block', ...styleProp }}
        onPointerDown={interactive ? poke : undefined}
        role={interactive ? 'button' : undefined} aria-label="Tanu (pixel)">
        <div className={fxCls} style={{ width: '100%', height: '100%', transformBox: 'fill-box' }}>
          <canvas ref={canvasRef} width={GRID} height={GRID}
            style={{ width: '100%', height: '100%', imageRendering: 'pixelated', display: 'block' }} />
        </div>
      </div>
    );
  }

  window.TanuPixel = TanuPixel;
})();
