/* Pulso — minigame de ritmo minimalista, no vocabulário Petit.
   Exposto em window.MinigamePulso. Requer window.React e window.PetitUI. */
(function () {
  const { Manchete, Botao, Tag } = window.PetitUI;

  // Geometria do gabinete (px)
  const PLAY_H = 360;
  const HIT_Y = 292;      // centro da linha de acerto
  const NOTE_H = 20;
  const TRAVEL = 1.45;    // segundos do topo até a linha
  const HIT_WIN = 0.17;   // janela de acerto (s)
  const MISS_WIN = 0.13;  // tolerância após o tempo antes de contar erro (s)

  const LANE_KEYS = ['j', 'k', 'l'];
  const LANE_LABEL = ['J', 'K', 'L'];
  const LANE_COR = ['var(--petit-limao)', 'var(--petit-ciano)', 'var(--petit-sol)'];
  const LANE_FREQ = [523.25, 659.25, 783.99]; // C5 · E5 · G5

  // Chart determinístico (groove fixo, sensação intencional)
  const CHART = (function () {
    const pat = [0, 1, 2, 1, 0, 2, 1, 2, 0, 1, 0, 1, 2, 1, 2, 0, 1, 2, 1, 0, 2, 1, 0, 2];
    const notes = [];
    let t = 2.0;
    const spacing = 0.42;
    for (let i = 0; i < 52; i++) {
      notes.push({ hitTime: t, lane: pat[i % pat.length] });
      t += spacing;
    }
    return notes;
  })();
  const LAST_TIME = CHART[CHART.length - 1].hitTime;
  const TOTAL = CHART.length;

  const CSS = `
  .pf-cabinet { max-width: 336px; margin: 0 auto; background: var(--petit-tinta); color: var(--petit-limao); padding: var(--space-3); }
  .pf-cab-hud { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 12px; font-family: var(--font-mono); text-transform: uppercase; letter-spacing: 0.08em; font-size: 11px; }
  .pf-cab-hud .lbl { opacity: 0.65; }
  .pf-cab-hud b { display: block; font-size: 22px; line-height: 1; margin-top: 3px; letter-spacing: 0; }
  .pf-cab-som { font-family: var(--font-mono); font-weight: 700; text-transform: uppercase; font-size: 10px; letter-spacing: 0.1em; background: transparent; color: var(--petit-limao); border: 1px solid currentColor; padding: 6px 9px; cursor: pointer; border-radius: 0; }
  .pf-play { position: relative; height: ${PLAY_H}px; border: 1px solid color-mix(in srgb, var(--petit-limao) 22%, transparent); overflow: hidden; }
  .pf-lanes { position: absolute; inset: 0; display: grid; grid-template-columns: 1fr 1fr 1fr; }
  .pf-lane { position: relative; }
  .pf-lane + .pf-lane { border-left: 1px solid color-mix(in srgb, var(--petit-limao) 12%, transparent); }
  .pf-hitline { position: absolute; left: 0; right: 0; top: ${HIT_Y - 1}px; height: 2px; background: var(--petit-limao); }
  .pf-note { position: absolute; top: 0; left: 10%; width: 80%; height: ${NOTE_H}px; will-change: transform; }
  .pf-note.is-hit { opacity: 0; transition: opacity 0.12s ease-out; }
  .pf-judge { position: absolute; left: 0; right: 0; top: ${HIT_Y - 46}px; text-align: center; font-family: var(--font-mono); font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em; font-size: 13px; pointer-events: none; opacity: 0; }
  .pf-judge.show { animation: pf-judge 0.5s ease-out; }
  @keyframes pf-judge { 0% { opacity: 0; transform: translateY(6px); } 20% { opacity: 1; transform: none; } 100% { opacity: 0; } }
  .pf-keys { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; margin-top: 12px; }
  .pf-key { border: 2px solid currentColor; background: transparent; font-family: var(--font-mono); font-weight: 700; font-size: 15px; padding: 12px; text-align: center; cursor: pointer; border-radius: 0; min-height: 46px; transition: background-color 0.08s, color 0.08s; }
  .pf-key.flash { background: currentColor; }
  .pf-key.flash span { color: var(--petit-tinta); }
  .pf-overlay { position: absolute; inset: 0; background: color-mix(in srgb, var(--petit-tinta) 90%, transparent); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; text-align: center; padding: 22px; }
  .pf-overlay p { font-family: var(--font-mono); font-size: 12px; line-height: 1.5; text-transform: uppercase; letter-spacing: 0.06em; margin: 0; opacity: 0.85; }
  .pf-final { display: flex; gap: var(--space-4); font-family: var(--font-mono); }
  .pf-final div .n { font-size: 30px; font-weight: 700; }
  .pf-final div .l { font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em; opacity: 0.7; }
  `;

  function MinigamePulso() {
    const { useState, useRef, useEffect, useCallback } = window.React;
    const [estado, setEstado] = useState('idle'); // idle | playing | over
    const [score, setScore] = useState(0);
    const [combo, setCombo] = useState(0);
    const [maxCombo, setMaxCombo] = useState(0);
    const [hits, setHits] = useState(0);
    const [judge, setJudge] = useState(null);
    const [som, setSom] = useState(true);
    const somRef = useRef(true);

    const playRef = useRef(null);
    const laneRefs = [useRef(null), useRef(null), useRef(null)];
    const keyRefs = [useRef(null), useRef(null), useRef(null)];
    const judgeRef = useRef(null);

    const raf = useRef(0);
    const startPerf = useRef(0);
    const nextIdx = useRef(0);
    const active = useRef([]);
    const ctxRef = useRef(null);
    const masterRef = useRef(null);
    const comboRef = useRef(0);
    const maxRef = useRef(0);

    useEffect(() => { somRef.current = som; if (masterRef.current) masterRef.current.gain.value = som ? 0.9 : 0; }, [som]);

    const mostraJudge = useCallback((txt, cor) => {
      setJudge({ txt, cor });
      const el = judgeRef.current;
      if (el) { el.classList.remove('show'); void el.offsetWidth; el.classList.add('show'); }
    }, []);

    // ---- áudio ----
    const blip = (at, freq, vol, type) => {
      const ctx = ctxRef.current; if (!ctx) return;
      const o = ctx.createOscillator(), g = ctx.createGain();
      o.type = type || 'triangle'; o.frequency.setValueAtTime(freq, at);
      g.gain.setValueAtTime(0, at);
      g.gain.linearRampToValueAtTime(vol, at + 0.005);
      g.gain.exponentialRampToValueAtTime(0.0001, at + 0.16);
      o.connect(g).connect(masterRef.current); o.start(at); o.stop(at + 0.18);
    };
    const kick = (at) => {
      const ctx = ctxRef.current; if (!ctx) return;
      const o = ctx.createOscillator(), g = ctx.createGain();
      o.type = 'sine'; o.frequency.setValueAtTime(140, at); o.frequency.exponentialRampToValueAtTime(52, at + 0.12);
      g.gain.setValueAtTime(0.32, at); g.gain.exponentialRampToValueAtTime(0.0001, at + 0.14);
      o.connect(g).connect(masterRef.current); o.start(at); o.stop(at + 0.16);
    };
    const agendaTrilha = () => {
      const ctx = ctxRef.current; const a0 = ctx.currentTime + (2.0 - (performance.now() - startPerf.current) / 1000);
      CHART.forEach((n, i) => {
        blip(a0 + (n.hitTime - 2.0), LANE_FREQ[n.lane], 0.16);
        if (i % 4 === 0) kick(a0 + (n.hitTime - 2.0));
      });
    };

    const limpaNotas = () => {
      active.current.forEach(n => n.el && n.el.remove());
      active.current = [];
    };

    const encerra = useCallback(() => {
      cancelAnimationFrame(raf.current);
      limpaNotas();
      setEstado('over');
    }, []);

    const frame = useCallback((now) => {
      const t = (now - startPerf.current) / 1000;
      while (nextIdx.current < CHART.length && CHART[nextIdx.current].hitTime - t <= TRAVEL) {
        const n = CHART[nextIdx.current]; nextIdx.current++;
        const el = document.createElement('div');
        el.className = 'pf-note'; el.style.background = LANE_COR[n.lane];
        if (laneRefs[n.lane].current) laneRefs[n.lane].current.appendChild(el);
        active.current.push({ hitTime: n.hitTime, lane: n.lane, el, hit: false });
      }
      for (let i = active.current.length - 1; i >= 0; i--) {
        const n = active.current[i];
        const p = (t - (n.hitTime - TRAVEL)) / TRAVEL;
        n.el.style.transform = 'translateY(' + (p * HIT_Y - NOTE_H / 2) + 'px)';
        if (!n.hit && t - n.hitTime > MISS_WIN) {
          n.el.remove(); active.current.splice(i, 1);
          comboRef.current = 0; setCombo(0); mostraJudge('Passou', 'var(--petit-magenta)');
        }
      }
      if (nextIdx.current >= CHART.length && active.current.length === 0 && t > LAST_TIME + 0.6) {
        encerra(); return;
      }
      raf.current = requestAnimationFrame(frame);
    }, [encerra, mostraJudge]);

    const acertaPista = useCallback((lane) => {
      // flash da tecla
      const k = keyRefs[lane].current;
      if (k) { k.classList.add('flash'); setTimeout(() => k.classList.remove('flash'), 90); }
      const t = (performance.now() - startPerf.current) / 1000;
      let best = null, bd = 1e9;
      for (const n of active.current) {
        if (n.lane !== lane || n.hit) continue;
        const d = Math.abs(t - n.hitTime);
        if (d < bd) { bd = d; best = n; }
      }
      if (best && bd <= HIT_WIN) {
        best.hit = true; best.el.classList.add('is-hit');
        const el = best.el; setTimeout(() => el.remove(), 120);
        active.current = active.current.filter(x => x !== best);
        let pts, txt, cor;
        if (bd < 0.06) { pts = 100; txt = 'Perfeito'; cor = 'var(--petit-limao)'; }
        else if (bd < 0.11) { pts = 60; txt = 'Bom'; cor = 'var(--petit-ciano)'; }
        else { pts = 30; txt = 'Ok'; cor = 'var(--petit-sol)'; }
        comboRef.current += 1;
        maxRef.current = Math.max(maxRef.current, comboRef.current);
        setCombo(comboRef.current); setMaxCombo(maxRef.current);
        setScore(s => s + Math.round(pts * (1 + comboRef.current * 0.03)));
        setHits(h => h + 1);
        mostraJudge(txt, cor);
        if (somRef.current) blip(ctxRef.current.currentTime, LANE_FREQ[lane] * 2, 0.12, 'square');
      }
    }, [mostraJudge]);

    const inicia = useCallback(() => {
      limpaNotas();
      setScore(0); setCombo(0); setMaxCombo(0); setHits(0); setJudge(null);
      comboRef.current = 0; maxRef.current = 0; nextIdx.current = 0;
      try {
        const AC = window.AudioContext || window.webkitAudioContext;
        ctxRef.current = new AC();
        masterRef.current = ctxRef.current.createGain();
        masterRef.current.gain.value = somRef.current ? 0.9 : 0;
        masterRef.current.connect(ctxRef.current.destination);
      } catch (e) { ctxRef.current = null; }
      startPerf.current = performance.now();
      if (ctxRef.current) agendaTrilha();
      setEstado('playing');
      raf.current = requestAnimationFrame(frame);
    }, [frame]);

    // teclado
    useEffect(() => {
      if (estado !== 'playing') return;
      const down = (e) => {
        const i = LANE_KEYS.indexOf(e.key.toLowerCase());
        if (i >= 0) { e.preventDefault(); acertaPista(i); }
      };
      window.addEventListener('keydown', down);
      return () => window.removeEventListener('keydown', down);
    }, [estado, acertaPista]);

    useEffect(() => () => { cancelAnimationFrame(raf.current); if (ctxRef.current) ctxRef.current.close(); }, []);

    const acc = hits ? Math.round((hits / TOTAL) * 100) : 0;

    return window.React.createElement('div', { className: 'pf-cabinet' },
      window.React.createElement('style', { dangerouslySetInnerHTML: { __html: CSS } }),
      window.React.createElement('div', { className: 'pf-cab-hud' },
        window.React.createElement('div', null,
          window.React.createElement('span', { className: 'lbl' }, 'Pontos'),
          window.React.createElement('b', null, score)),
        window.React.createElement('button', { className: 'pf-cab-som', onClick: () => setSom(s => !s), type: 'button' }, som ? 'Som ◼' : 'Som ◻'),
        window.React.createElement('div', { style: { textAlign: 'right' } },
          window.React.createElement('span', { className: 'lbl' }, 'Combo'),
          window.React.createElement('b', null, 'x' + combo))
      ),
      window.React.createElement('div', { className: 'pf-play', ref: playRef },
        window.React.createElement('div', { className: 'pf-lanes' },
          [0, 1, 2].map(i => window.React.createElement('div', { className: 'pf-lane', key: i, ref: laneRefs[i] }))),
        window.React.createElement('div', { className: 'pf-hitline' }),
        window.React.createElement('div', { className: 'pf-judge', ref: judgeRef, style: { color: judge ? judge.cor : 'transparent' } }, judge ? judge.txt : ''),
        estado === 'idle' && window.React.createElement('div', { className: 'pf-overlay' },
          window.React.createElement('p', null, 'Toque J K L (ou as pistas)', window.React.createElement('br'), 'quando o bloco cruzar a linha'),
          window.React.createElement(Botao, { variante: 'primario', onClick: inicia }, 'Tocar')),
        estado === 'over' && window.React.createElement('div', { className: 'pf-overlay' },
          window.React.createElement(Manchete, { nivel: 3, escala: 'titulo', cor: 'limao' }, 'Fim'),
          window.React.createElement('div', { className: 'pf-final' },
            window.React.createElement('div', null, window.React.createElement('div', { className: 'n' }, score), window.React.createElement('div', { className: 'l' }, 'Pontos')),
            window.React.createElement('div', null, window.React.createElement('div', { className: 'n' }, 'x' + maxCombo), window.React.createElement('div', { className: 'l' }, 'Combo máx')),
            window.React.createElement('div', null, window.React.createElement('div', { className: 'n' }, acc + '%'), window.React.createElement('div', { className: 'l' }, 'Precisão'))),
          window.React.createElement(Botao, { variante: 'primario', onClick: inicia }, 'De novo'))
      ),
      window.React.createElement('div', { className: 'pf-keys' },
        [0, 1, 2].map(i => window.React.createElement('button', {
          key: i, type: 'button', className: 'pf-key', ref: keyRefs[i],
          style: { color: LANE_COR[i] },
          onPointerDown: (e) => { e.preventDefault(); if (estado === 'playing') acertaPista(i); },
          'aria-label': 'Pista ' + LANE_LABEL[i]
        }, window.React.createElement('span', null, LANE_LABEL[i]))))
    );
  }

  window.MinigamePulso = MinigamePulso;
})();
