/* ============================================================
   EDUCARE — Fluência Leitora (1º ao 5º ano)
   Velocidade e precisão da leitura oral de palavras e texto,
   com classificação por palavras lidas por minuto.
   Padrões do DNOI: instância por ID, salvamento explícito,
   aviso de alterações não salvas, histórico por criança.
   ============================================================ */
(function () {
  const { useState, useRef, useEffect } = React;
  const Icon = window.Icon;
  const INSTRUMENT_NAME = "Fluência Leitora (1º ao 5º ano)";

  // ---------------- Persistência por INSTÂNCIA (ID único) ----------------
  const FL_KEY = "educare-fluencia-avaliacoes";
  function loadAvals() { try { const o = JSON.parse(localStorage.getItem(FL_KEY) || "null"); if (o && typeof o === "object") return o; } catch (e) { /* */ } return {}; }
  function saveAvals(o) { localStorage.setItem(FL_KEY, JSON.stringify(o)); }
  function loadAval(id) { return loadAvals()[id] || null; }
  function putAval(a) { const all = loadAvals(); all[a.id] = a; saveAvals(all); }
  function newAvalId() { return "fl_" + Date.now().toString(36) + "_" + Math.random().toString(36).slice(2, 8); }
  function avalsDoPaciente(pid) { const all = loadAvals(); return Object.keys(all).map((k) => all[k]).filter((a) => a && a.pid === pid).sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0)); }

  // ---------------- Materiais por ano ----------------
  const ANO1 = ["BALA", "BELO", "BOLO", "BOLA", "BULE", "BALÃO", "BOCA", "BANANA", "BEIJO", "BICO", "CABO", "CASA", "CADEADO", "COCO", "CUIDADO", "CIMA", "DADO", "DEDO", "DIA", "DURO", "FACA", "FOCA", "FICO", "FIGO", "FOGO", "FADA", "FEIO", "FINO", "GATO", "GOLA", "GULA", "GELO", "GATA", "HORA", "JACA", "JOIA", "JUJUBA", "LOBO", "LUA", "LEÃO", "LAÇO", "LATA", "LEITE", "LEGO", "LUVA", "MACA", "MACACO", "MALA", "MEL", "MELADO", "NAVIO", "NADA", "NEVE", "NOVO", "OVO", "PATO", "PANELA", "PATA", "PÉ", "PIADA", "PIPOCA", "PIRATA", "RATO", "RENA", "RIO", "ROSA", "RUA", "ROBÔ", "SAPO", "SAPATO", "SEREIA", "SINO", "SOPA", "SOCO", "SUCO"];
  const ANO2_EXTRA = ["TAPETE", "TIGRE", "TOMADA", "VACA", "XÍCARA", "ZEBRA", "ZÍPER", "BRAÇO", "BRIGA", "BLUSA", "BRANCO", "CRAVO", "CRU", "DRAGÃO", "VIDRO", "ÁRVORE", "ERVILHA", "CHAPÉU", "CHOCOLATE", "GRAMA", "TROVÃO", "FLECHA", "FRIO", "PRONTO", "FLOR"];
  const ANO345 = ["NAVIO", "NADA", "NEVE", "NOVO", "OVO", "PATO", "PANELA", "PATA", "PÉ", "PIADA", "PIPOCA", "PIRATA", "RATO", "RENA", "RIO", "ROSA", "RUA", "ROBÔ", "SAPO", "SAPATO", "SEREIA", "SINO", "SOPA", "SOCO", "SUCO", "PÁSSARO", "PRESENTE", "TRATOR", "MONTANHA", "MOCHILA", "MACHUCADO", "PRETO", "CAMINHÃO", "HORTA", "CORRETIVO", "TAPETE", "TIGRE", "TOMADA", "VACA", "XÍCARA", "ZEBRA", "ZÍPER", "BRAÇO", "BRIGA", "BLUSA", "BRANCO", "CRAVO", "CRU", "DRAGÃO", "VIDRO", "ÁRVORE", "ERVILHA", "CHAPÉU", "CHOCOLATE", "GRAMA", "TROVÃO", "FLECHA", "FRIO", "PRONTO", "FLOR", "FRACO", "PRATO", "PLANTA", "VENTILADOR", "VENTO", "LANCHE", "GRANDE", "LIVRO", "LIVRE", "ALMOFADA", "BRINCO", "GELADEIRA", "BICICLETA", "BRIGADEIRO", "AMIZADE", "LIBERDADE", "LIBÉLULA", "BRINCADEIRA", "ANIVERSÁRIO", "TELEFONE", "ENVELOPE", "ANTIGO", "EMPADA", "BOMBOM", "PAREDE", "NATUREZA", "PROFESSOR", "HELICÓPTERO", "FAXINA", "REPOLHO", "CHINELO", "CHOCALHO", "FOLHA", "FARINHA", "GALINHA", "DINHEIRO", "MINHOCA", "NINHO", "RELÂMPAGO", "TEMPESTADE", "CHUVA", "SINCERO", "NUBLADO", "ENSOLARADO", "INTELIGENTE", "BORBOLETA", "GIRASSOL", "COMPUTADOR", "CACHORRO", "ENERGIA", "ESCURIDÃO", "BIBLIOTECA", "GRÁVIDA", "FOTOGRAFIA", "ASSUSTADO", "RAINHA", "IMAGINAÇÃO", "ESCONDER", "ENGENHEIRO", "TELEVISÃO", "PORTA", "HOSPITAL", "FORMIGA", "ASTRONAUTA", "BORRACHA", "TRAVESSEIRO", "FUTEBOL", "GOLEIRO", "MARAVILHOSO", "PERIGO", "CORREDOR", "PARQUE", "QUEIJO", "FELICIDADE", "DENTADURA", "CACHEADO", "REFRIGERANTE", "AVIÃO", "CIENTISTA", "BATOM"];
  function palavrasDoAno(ano) { if (ano === "1") return ANO1; if (ano === "2") return ANO1.concat(ANO2_EXTRA); return ANO345; }

  const TEXTOS = {
    "1": { titulo: "O Gato e o Pato", caixaAlta: true, linhas: ["O GATO VIU O PATO.", "O PATO PULA NA LAMA.", "O GATO PULA NA GRAMA.", "O PATO NADA NO LAGO.", "O GATO MIA NA CASA.", "O PATO FAZ QUAC, QUAC!", "O GATO E O PATO SÃO AMIGOS."] },
    "2": { titulo: "O Passeio de Bia", caixaAlta: false, linhas: ["Bia foi ao parque com sua mãe. Ela levou uma bola vermelha. No parque, Bia viu um cachorro e uma borboleta. O cachorro correu atrás da bola. A borboleta voou bem alto no céu. Bia deu risada e correu atrás do cachorro. Foi um dia muito divertido no parque!"] },
    "345": { titulo: "O Mistério da Mochila Azul", caixaAlta: false, linhas: ["João chegou da escola e notou algo diferente. Sua mochila azul estava aberta, e o estojo não estava no lugar. Ele procurou debaixo da cama, atrás da porta e até no quintal. Quando foi à sala, viu seu cachorro com o estojo na boca! João riu alto, pegou o estojo e disse: — Você é um cachorro muito curioso!"] },
  };
  function textoDoAno(ano) { return ano === "1" ? TEXTOS["1"] : ano === "2" ? TEXTOS["2"] : TEXTOS["345"]; }
  // Tokeniza em grupos (linhas), com índice global por palavra.
  function tokenizeTexto(t) { let i = 0; const grupos = (t.linhas || []).map((ln) => ln.split(/\s+/).filter(Boolean).map((w) => ({ i: i++, w }))); return { grupos, total: i }; }

  // ---------------- Tabela de proficiência (palavras/min) ----------------
  const TABELA = {
    "1": { basico: 15, adequado: 35, avancado: 60, disp: ["< 15", "15–34", "35–59", "> 59"] },
    "2": { basico: 35, adequado: 60, avancado: 85, disp: ["< 35", "35–59", "60–84", "> 84"] },
    "3": { basico: 60, adequado: 85, avancado: 100, disp: ["< 60", "60–84", "85–99", "> 99"] },
    "4": { basico: 85, adequado: 100, avancado: 115, disp: ["< 85", "85–99", "100–114", "> 114"] },
    "5": { basico: 100, adequado: 115, avancado: 125, disp: ["< 100", "100–114", "115–124", "> 124"] },
  };
  const NIVEIS = [
    { key: "abaixo", l: "Abaixo do Básico", col: "#DC2626" },
    { key: "basico", l: "Básico", col: "#F59E0B" },
    { key: "adequado", l: "Adequado", col: "#EAB308" },
    { key: "avancado", l: "Avançado", col: "#16A34A" },
  ];
  function classificar(ano, total) {
    const t = TABELA[ano]; if (!t) return null;
    if (total < t.basico) return NIVEIS[0];
    if (total < t.adequado) return NIVEIS[1];
    if (total < t.avancado) return NIVEIS[2];
    return NIVEIS[3];
  }

  const INSTRUCOES = [
    "Vá para um lugar silencioso.",
    "Grave o áudio para ouvir quando necessário.",
    "É indicado 1 minuto para leitura de palavras e 2 minutos para leitura do texto.",
    "Peça para a criança realizar a leitura da melhor forma que puder.",
    "Você pode apontar as palavras e frases se achar melhor para o aluno.",
    "Utilize um cronômetro ou relógio para marcar o tempo e encerre-o após os minutos indicados.",
    "Após os testes, faça o registro dos erros e acertos.",
    "Analise cada criança de acordo com a tabela de proficiência leitora — palavras lidas por minuto.",
  ];

  const SI = { width: 32, height: 32, borderRadius: 10, background: "var(--primary-soft)", color: "var(--primary)", display: "grid", placeItems: "center", flex: "none" };
  const fmtTempo = (s) => { s = Math.max(0, Math.floor(s || 0)); const m = Math.floor(s / 60); const ss = s % 60; return m + ":" + (ss < 10 ? "0" : "") + ss; };

  // Som de sino sintetizado (Web Audio API — sem arquivo). vezes = 1 (início) ou 2 (fim).
  function tocarSino(vezes) {
    try {
      const AC = window.AudioContext || window.webkitAudioContext; if (!AC) return;
      const ctx = new AC(); if (ctx.resume) { try { ctx.resume(); } catch (e) { /* */ } }
      const badalada = (t0) => {
        [880, 1320, 2640].forEach((f, i) => {
          const osc = ctx.createOscillator(); const g = ctx.createGain();
          osc.type = "sine"; osc.frequency.value = f;
          const vol = [0.35, 0.18, 0.08][i];
          g.gain.setValueAtTime(0.0001, t0);
          g.gain.exponentialRampToValueAtTime(vol, t0 + 0.008);
          g.gain.exponentialRampToValueAtTime(0.0001, t0 + 1.3);
          osc.connect(g); g.connect(ctx.destination);
          osc.start(t0); osc.stop(t0 + 1.35);
        });
      };
      const now = ctx.currentTime;
      const n = vezes || 1;
      for (let k = 0; k < n; k++) badalada(now + k * 0.42);
      setTimeout(() => { try { ctx.close(); } catch (e) { /* */ } }, 900 + n * 500);
    } catch (e) { /* */ }
  }

  // ---------------- Player de áudio (play/pause, seek, velocidade) ----------------
  function AudioPlayer({ src }) {
    const ref = useRef(null);
    const [playing, setPlaying] = useState(false);
    const [cur, setCur] = useState(0); const [dur, setDur] = useState(0); const [rate, setRate] = useState(1);
    useEffect(() => {
      const a = ref.current; if (!a) return; let fixing = false;
      const setD = () => { if (isFinite(a.duration) && a.duration > 0) setDur(a.duration); };
      const onLoaded = () => { if (!isFinite(a.duration) || a.duration === 0) { fixing = true; try { a.currentTime = 1e101; } catch (e) { /* */ } } else setD(); };
      const onTime = () => { if (!fixing) setCur(a.currentTime || 0); };
      const onDur = () => { if (fixing && isFinite(a.duration)) { fixing = false; setD(); try { a.currentTime = 0; } catch (e) { /* */ } setCur(0); } };
      const onEnd = () => setPlaying(false);
      a.addEventListener("loadedmetadata", onLoaded); a.addEventListener("durationchange", onDur); a.addEventListener("timeupdate", onTime); a.addEventListener("ended", onEnd);
      if (a.readyState >= 1) onLoaded();
      return () => { a.removeEventListener("loadedmetadata", onLoaded); a.removeEventListener("durationchange", onDur); a.removeEventListener("timeupdate", onTime); a.removeEventListener("ended", onEnd); };
    }, [src]);
    const toggle = () => { const a = ref.current; if (!a) return; if (a.paused) { a.play(); setPlaying(true); } else { a.pause(); setPlaying(false); } };
    const seek = (e) => { const a = ref.current; if (!a) return; const t = parseFloat(e.target.value); a.currentTime = t; setCur(t); };
    const setSpeed = (r) => { const a = ref.current; if (a) a.playbackRate = r; setRate(r); };
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "8px 12px", borderRadius: 10, background: "var(--surface-2)", border: "1px solid var(--border)", marginBottom: 10, flexWrap: "wrap" }}>
        <audio ref={ref} src={src} preload="metadata" />
        <button type="button" onClick={toggle} aria-label={playing ? "Pausar" : "Reproduzir"} style={{ width: 34, height: 34, flex: "none", borderRadius: "50%", border: "none", cursor: "pointer", background: "var(--primary)", color: "#fff", fontSize: 14, display: "grid", placeItems: "center" }}>{playing ? "⏸" : "▶"}</button>
        <input type="range" min={0} max={dur || 0} step="0.01" value={cur > (dur || 0) ? (dur || 0) : cur} onChange={seek} style={{ flex: 1, minWidth: 120, accentColor: "var(--primary)", cursor: "pointer" }} />
        <span style={{ fontSize: 12, fontWeight: 700, color: "var(--text-2)", flex: "none" }}>{fmtTempo(cur)} / {fmtTempo(dur)}</span>
        <div style={{ display: "flex", gap: 4, flex: "none" }}>
          {[1, 0.75, 0.5].map((r) => (
            <button key={r} type="button" onClick={() => setSpeed(r)} style={{ fontSize: 11.5, fontWeight: 800, padding: "3px 7px", borderRadius: 7, cursor: "pointer", border: "1px solid " + (rate === r ? "var(--primary)" : "var(--border-2)"), background: rate === r ? "var(--primary)" : "transparent", color: rate === r ? "#fff" : "var(--text-2)" }}>{r}x</button>
          ))}
        </div>
      </div>
    );
  }

  // ---------------- Gravador com cronômetro regressivo ----------------
  function Gravador({ durationSec, hasAudio, onAudio }) {
    const [rec, setRec] = useState(false);
    const [remaining, setRemaining] = useState(durationSec);
    const [flash, setFlash] = useState(false);
    const mrRef = useRef(null); const timerRef = useRef(null); const streamRef = useRef(null);
    function stop() {
      if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; }
      const mr = mrRef.current; if (mr && mr.state !== "inactive") { try { mr.stop(); } catch (e) { /* */ } }
      setRec(false);
    }
    function start() {
      if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia || !window.MediaRecorder) { alert("Gravação de áudio não suportada neste navegador."); return; }
      stop();
      navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
        streamRef.current = stream;
        const mr = new MediaRecorder(stream); const chunks = [];
        mr.ondataavailable = (e) => { if (e.data && e.data.size) chunks.push(e.data); };
        mr.onstop = () => { stream.getTracks().forEach((t) => t.stop()); const blob = new Blob(chunks, { type: mr.mimeType || "audio/webm" }); const r = new FileReader(); r.onloadend = () => onAudio(r.result); r.readAsDataURL(blob); };
        mrRef.current = mr; setRemaining(durationSec); setRec(true); mr.start();
        tocarSino(1); // sino de início
        timerRef.current = setInterval(() => setRemaining((x) => x - 1), 1000);
      }).catch(() => alert("Não foi possível acessar o microfone — verifique a permissão do navegador."));
    }
    useEffect(() => { if (rec && remaining <= 0) { setFlash(true); tocarSino(2); setTimeout(() => setFlash(false), 1500); stop(); } }, [remaining, rec]);
    useEffect(() => () => { if (timerRef.current) clearInterval(timerRef.current); const s = streamRef.current; if (s) s.getTracks().forEach((t) => t.stop()); }, []);
    return (
      <div style={{ display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
        {!rec
          ? <button type="button" className="btn btn-coral btn-sm" onClick={start}><Icon name="headset" size={15} /> {hasAudio ? "Regravar" : "Gravar"} ({fmtTempo(durationSec)})</button>
          : <button type="button" className="btn btn-sm" style={{ background: "#DC2626", color: "#fff" }} onClick={stop}><Icon name="x" size={15} /> Parar</button>}
        {rec && <span style={{ fontSize: 15, fontWeight: 800, color: remaining <= 5 ? "#DC2626" : "var(--primary)", fontVariantNumeric: "tabular-nums" }}>⏱ {fmtTempo(Math.max(0, remaining))}</span>}
        {flash && <span style={{ fontSize: 13, fontWeight: 800, color: "#16A34A" }}>⏰ Tempo encerrado!</span>}
        {!rec && hasAudio && <span style={{ fontSize: 12, color: "var(--primary)", fontWeight: 700, display: "inline-flex", alignItems: "center", gap: 4 }}><Icon name="clipboardCheck" size={13} /> áudio gravado</span>}
      </div>
    );
  }

  // Estilo de uma palavra tocável (lida/erro/não lida)
  function estiloPalavra(i, lastIdx, erros, big) {
    const lida = lastIdx >= 0 && i <= lastIdx;
    const erro = erros.indexOf(i) >= 0;
    return {
      cursor: "pointer", userSelect: "none", borderRadius: 7, padding: big ? "6px 8px" : "2px 5px", textAlign: "center",
      fontSize: big ? 17 : 16, fontWeight: 700, lineHeight: 1.35, transition: "background .1s",
      background: erro ? "#DC2626" : (lida ? "var(--primary-soft)" : "transparent"),
      color: erro ? "#fff" : (lida ? "var(--text)" : "var(--text-3)"),
      border: "1px solid " + (erro ? "#DC2626" : (i === lastIdx ? "var(--primary)" : "transparent")),
    };
  }

  // ---------------- Componente principal ----------------
  function Fluencia({ onBack, onOpenPatient }) {
    const ETAPA0 = { audio: "", lastIdx: -1, erros: [] };
    const [view, setView] = useState("ident"); // ident | palavras | texto | resultado
    const [pid, setPid] = useState("");
    const [avalId, setAvalId] = useState(null);
    const [readonly, setReadonly] = useState(false);
    const [ano, setAno] = useState("");
    const [date, setDate] = useState((window.todayISO ? window.todayISO() : "2026-06-04"));
    const [dados, setDados] = useState({ palavras: { ...ETAPA0 }, texto: { ...ETAPA0 }, obs: "" });
    const [savedJson, setSavedJson] = useState("");
    const [savedAt, setSavedAt] = useState(null);
    const [dirty, setDirty] = useState(false);
    const [saving, setSaving] = useState(false);
    const [toast, setToast] = useState(null);
    const [leaveGuard, setLeaveGuard] = useState(null);
    const [showClear, setShowClear] = useState(false);
    const [instrOpen, setInstrOpen] = useState(true);
    const [modo, setModo] = useState({ palavras: "last", texto: "last" }); // last | err
    const [err, setErr] = useState("");

    const patient = (window.PATIENTS || []).find((p) => p.id === pid);
    const listaAvals = pid ? avalsDoPaciente(pid) : [];
    const STEPS = ["Identificação", "Palavras", "Texto", "Resultado"];
    const stepIdx = { ident: 1, palavras: 2, texto: 3, resultado: 4 }[view];

    useEffect(() => { if (!dirty) return; const h = (e) => { e.preventDefault(); e.returnValue = ""; return ""; }; window.addEventListener("beforeunload", h); return () => window.removeEventListener("beforeunload", h); }, [dirty]);
    useEffect(() => { if (!toast) return; const id = setTimeout(() => setToast(null), toast.kind === "err" ? 5000 : 2600); return () => clearTimeout(id); }, [toast]);

    function goTop() { const el = document.querySelector(".main-scroll"); if (el) el.scrollTop = 0; }
    function limparInstancia() { setAvalId(null); setReadonly(false); setAno(""); setDados({ palavras: { ...ETAPA0 }, texto: { ...ETAPA0 }, obs: "" }); setSavedJson(""); setSavedAt(null); setDirty(false); }
    function resetTudo() { limparInstancia(); setPid(""); setDate((window.todayISO ? window.todayISO() : "2026-06-04")); setErr(""); setView("ident"); }

    function pickPatient(id) { setPid(id); limparInstancia(); }

    function novaAvaliacao() {
      if (!pid) { setErr("Selecione o paciente."); return; }
      if (!ano) { setErr("Selecione o ano escolar."); return; }
      const id = newAvalId();
      const base = { palavras: { ...ETAPA0 }, texto: { ...ETAPA0 }, obs: "" };
      try { putAval({ id, pid, patientName: patient ? patient.name : "", date, ano, dados: base, finalizada: false, savedAt: null, createdAt: Date.now(), finishedAt: null }); } catch (e) { /* */ }
      setAvalId(id); setReadonly(false); setDados(base); setSavedJson(JSON.stringify(base)); setSavedAt(null); setDirty(false);
      setErr(""); setView("palavras"); goTop();
    }
    function abrirAval(a, ro) {
      setAvalId(a.id); setReadonly(!!ro); setPid(a.pid); setAno(a.ano || ""); setDate(a.date || (window.todayISO ? window.todayISO() : "2026-06-04"));
      const d = a.dados || { palavras: { ...ETAPA0 }, texto: { ...ETAPA0 }, obs: "" };
      setDados(d); setSavedJson(JSON.stringify(d)); setSavedAt(a.savedAt); setDirty(false);
      setErr(""); setView("palavras"); goTop();
    }

    function setEtapa(which, patch) { if (readonly) return; setDados((d) => ({ ...d, [which]: { ...d[which], ...patch } })); setDirty(true); }
    function setObs(v) { if (readonly) return; setDados((d) => ({ ...d, obs: v })); setDirty(true); }
    function tapWord(which, i) {
      if (readonly) return;
      if (modo[which] === "last") { setEtapa(which, { lastIdx: i }); }
      else { const cur = dados[which].erros; const has = cur.indexOf(i) >= 0; setEtapa(which, { erros: has ? cur.filter((x) => x !== i) : cur.concat(i) }); }
    }

    // contagens
    const lidas = (w) => (dados[w].lastIdx >= 0 ? dados[w].lastIdx + 1 : 0);
    const errosN = (w) => dados[w].erros.length;
    const lidasPal = lidas("palavras"), lidasTxt = lidas("texto");
    const total = lidasPal + lidasTxt;
    const classif = ano ? classificar(ano, total) : null;

    function persistAval(finalizar) {
      const when = new Date().toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
      const prev = loadAval(avalId);
      const a = { id: avalId, pid, patientName: patient ? patient.name : "", date, ano, dados, finalizada: !!finalizar, savedAt: when, createdAt: (prev && prev.createdAt) || Date.now(), finishedAt: finalizar ? Date.now() : (prev && prev.finishedAt) || null, total, classif: classif ? classif.l : null };
      putAval(a); return { when, a };
    }
    async function salvar() {
      if (readonly) return false;
      if (!avalId) { setErr("Inicie uma avaliação primeiro."); return false; }
      setSaving(true); setToast(null);
      try { const { when } = persistAval(false); setSavedJson(JSON.stringify(dados)); setSavedAt(when); setDirty(false); setToast({ kind: "ok", text: "Avaliação salva" }); return true; }
      catch (e) { setToast({ kind: "err", text: window.classifyStorageError ? window.classifyStorageError(e) : "Não foi possível salvar — tente novamente." }); return false; }
      finally { setSaving(false); }
    }
    function gravarNoProntuario(a) {
      if (!window.addAvaliacao) return;
      const cl = a.classif || "—";
      const result = `${a.total} palavras/min · ${cl} (${a.ano}º ano)`;
      const note = `Fluência Leitora (${a.ano}º ano). Palavras (lista): ${lidasPal}, texto: ${lidasTxt}, total: ${a.total}. Erros: lista ${errosN("palavras")}, texto ${errosN("texto")}. Classificação: ${cl}.`;
      window.addAvaliacao({ pid: a.pid, name: INSTRUMENT_NAME, date: a.date, result, note, sheet: { kind: "fluencia-final", avalId: a.id, ano: a.ano, total: a.total, classif: cl } });
    }
    async function finalizar() {
      if (readonly || !avalId) return;
      setSaving(true); setToast(null);
      try { const { a } = persistAval(true); gravarNoProntuario(a); setDirty(false); setToast({ kind: "ok", text: "Avaliação finalizada e salva no prontuário" }); resetTudo(); goTop(); }
      catch (e) { console.error("[Fluência] finalizar:", e); setToast({ kind: "err", text: "Não foi possível finalizar — tente novamente." }); }
      finally { setSaving(false); }
    }

    function guardedLeave(proceed) { if (dirty) setLeaveGuard({ proceed }); else proceed(); }
    function discardAndLeave() { try { setDados(JSON.parse(savedJson)); } catch (e) { /* */ } setDirty(false); const p = leaveGuard && leaveGuard.proceed; setLeaveGuard(null); if (p) p(); }
    async function saveAndLeave() { const ok = await salvar(); if (!ok) return; const p = leaveGuard && leaveGuard.proceed; setLeaveGuard(null); if (p) p(); }

    function apagarDoPaciente() {
      if (!pid) return;
      try { const all = loadAvals(); Object.keys(all).forEach((id) => { if (all[id] && all[id].pid === pid) delete all[id]; }); saveAvals(all); } catch (e) { /* */ }
      setShowClear(false); limparInstancia(); setToast({ kind: "ok", text: "Avaliações desta criança apagadas." });
    }

    const accordion = (open, toggle, icon, title) => (
      <button type="button" onClick={toggle} style={{ width: "100%", display: "flex", alignItems: "center", gap: 10, background: "none", border: "none", padding: "14px 16px", cursor: "pointer", textAlign: "left", color: "var(--text)" }}>
        <span style={SI}><Icon name={icon} size={18} /></span><b style={{ flex: 1, fontSize: 15 }}>{title}</b>
        <Icon name="chevDown" size={18} style={{ transform: open ? "rotate(180deg)" : "none", transition: "transform .2s" }} />
      </button>
    );

    // Render de uma etapa (palavras ou texto)
    function renderEtapa(which, palavrasArr, textoTok, durationSec, titulo, tempoLabel) {
      const d = dados[which];
      const modoAtual = modo[which];
      const setModoW = (m) => setModo((o) => ({ ...o, [which]: m }));
      return (
        <>
          <div className="form-card">
            <div className="sec-head" style={{ flexWrap: "wrap", gap: 8 }}><span className="si"><Icon name="headset" size={18} /></span> {titulo} <span style={{ fontSize: 12.5, fontWeight: 700, color: "var(--text-3)" }}>({tempoLabel})</span></div>
            {!readonly && <Gravador durationSec={durationSec} hasAudio={!!d.audio} onAudio={(a) => setEtapa(which, { audio: a })} />}
            {d.audio
              ? <div style={{ marginTop: 10 }}><AudioPlayer src={d.audio} /></div>
              : <p className="help-text" style={{ marginBottom: 0 }}>Grave a leitura da criança e depois ouça para corrigir tocando nas palavras.</p>}
          </div>

          <div className="form-card">
            <div className="sec-head" style={{ flexWrap: "wrap", gap: 8 }}>
              <span className="si"><Icon name="clipboardList" size={18} /></span> Correção por toque
              {!readonly && (
                <div style={{ display: "flex", gap: 6, marginLeft: "auto" }}>
                  <button type="button" onClick={() => setModoW("last")} className={"btn btn-sm " + (modoAtual === "last" ? "btn-primary" : "btn-ghost")}>Última lida</button>
                  <button type="button" onClick={() => setModoW("err")} className={"btn btn-sm " + (modoAtual === "err" ? "btn-primary" : "btn-ghost")} style={modoAtual === "err" ? { background: "#DC2626", color: "#fff" } : null}>Marcar erros</button>
                </div>
              )}
            </div>
            <p className="help-text" style={{ marginTop: 0 }}>{modoAtual === "last" ? "Toque na última palavra que a criança leu." : "Toque nas palavras lidas erradas (toque de novo para desfazer)."} {which === "palavras" ? `${palavrasArr.length} palavras.` : ""}</p>

            {which === "palavras" ? (
              <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(96px, 1fr))", gap: 6 }}>
                {palavrasArr.map((w, i) => (
                  <span key={i} onClick={() => tapWord(which, i)} style={estiloPalavra(i, d.lastIdx, d.erros, false)}>{w}</span>
                ))}
              </div>
            ) : (
              <div style={{ lineHeight: 2 }}>
                {textoTok.grupos.map((g, gi) => (
                  <div key={gi} style={{ marginBottom: 6, display: "flex", flexWrap: "wrap", gap: 4 }}>
                    {g.map((tk) => (
                      <span key={tk.i} onClick={() => tapWord(which, tk.i)} style={estiloPalavra(tk.i, d.lastIdx, d.erros, true)}>{tk.w}</span>
                    ))}
                  </div>
                ))}
              </div>
            )}

            <div className="hay-scores" style={{ marginTop: 14 }}>
              <div className="hsc"><span>Palavras lidas</span><b>{lidas(which)}</b></div>
              <div className="hsc"><span>Erros</span><b>{errosN(which)}</b></div>
            </div>
          </div>
        </>
      );
    }

    const anoNum = ano || "1";
    const palArr = ano ? palavrasDoAno(ano) : [];
    const txtObj = ano ? textoDoAno(ano) : { linhas: [] };
    const txtTok = tokenizeTexto(txtObj);

    return (
      <div className="form-wrap">
        <div className="hay-top">
          <button className="pt-back" onClick={() => guardedLeave(onBack)}><Icon name="chevL" size={16} /> Voltar para avaliações</button>
        </div>
        <div className="page-head" style={{ marginBottom: 14 }}>
          <div><h1><Icon name="book" size={25} /> Fluência Leitora <span style={{ fontSize: 15, fontWeight: 700, color: "var(--text-3)" }}>(1º ao 5º ano)</span></h1><p className="sub">Velocidade e precisão da leitura oral, com classificação por palavras lidas por minuto.</p></div>
        </div>

        {view !== "ident" && (
          <div className="hay-stepper">
            {STEPS.map((s, i) => (
              <button type="button" key={i} className={"hs" + (i + 1 === stepIdx ? " on" : "") + (i + 1 < stepIdx ? " done" : "")} onClick={() => { const v = ["ident", "palavras", "texto", "resultado"][i]; if (v === "ident") { guardedLeave(() => { resetTudo(); }); } else if (avalId) { setView(v); goTop(); } }}>
                <span className="hs-n">{i + 1 < stepIdx ? <Icon name="clipboardCheck" size={14} stroke={3} /> : i + 1}</span>{s}
              </button>
            ))}
          </div>
        )}

        {view !== "ident" && (
          <div className="hay-sheet-id" style={{ marginBottom: 14 }}>
            <span><b>Aluno(a):</b> {patient ? patient.name : "—"}</span>
            <span><b>Ano:</b> {ano}º ano</span>
            <span><b>Data:</b> {date}</span>
            {readonly && <span style={{ color: "var(--text-3)" }}><b>Modo consulta</b> (finalizada)</span>}
          </div>
        )}

        {/* ---------- Identificação ---------- */}
        {view === "ident" && (
          <>
            <div className="form-card">
              <div className="sec-head"><span className="si"><Icon name="users" size={18} /></span> Identificação</div>
              <div className="fld"><label>Paciente <span className="req">*</span></label><window.PatientPicker value={pid} onChange={pickPatient} /></div>
              <div className="grid-2">
                <div className="fld"><label>Ano escolar <span className="req">*</span></label>
                  <select className="ctrl" value={ano} onChange={(e) => setAno(e.target.value)}>
                    <option value="">Selecione…</option>
                    {["1", "2", "3", "4", "5"].map((a) => <option key={a} value={a}>{a}º ano</option>)}
                  </select>
                </div>
                <div className="fld"><label>Data</label><input className="ctrl" type="date" value={date} onChange={(e) => setDate(e.target.value)} /></div>
              </div>
              <p className="help-text" style={{ marginBottom: 0 }}>O ano determina a lista de palavras, o texto e a tabela de classificação.</p>
            </div>

            {/* Instruções */}
            <div className="form-card" style={{ padding: 0, overflow: "hidden" }}>
              {accordion(instrOpen, () => setInstrOpen((o) => !o), "fileText", "Instruções de Aplicação")}
              {instrOpen && (
                <div style={{ padding: "0 16px 16px" }}>
                  <ol style={{ margin: 0, paddingLeft: 20 }}>
                    {INSTRUCOES.map((t, i) => <li key={i} style={{ fontSize: 13.5, lineHeight: 1.6, marginBottom: 5, color: "var(--text-2)" }}>{t}</li>)}
                  </ol>
                </div>
              )}
            </div>

            {err && <p className="form-err" style={{ display: "flex", alignItems: "center", gap: 7, color: "#EF4444", fontWeight: 700, fontSize: 13.5, margin: "12px 0 0" }}><Icon name="x" size={15} /> {err}</p>}

            <div className="hay-nav">
              <button className="btn btn-ghost btn-sm" onClick={onBack}><Icon name="chevL" size={16} /> Cancelar</button>
              <button className="btn btn-primary btn-sm" onClick={novaAvaliacao} disabled={!pid}><Icon name="plus" size={16} /> Iniciar nova avaliação</button>
            </div>

            {pid && listaAvals.length > 0 && (
              <>
                <p className="section-label">AVALIAÇÕES DESTA CRIANÇA</p>
                {(() => {
                  const finais = listaAvals.filter((a) => a.finalizada && typeof a.total === "number").slice().reverse();
                  if (finais.length < 2) return null;
                  const vals = finais.map((a) => a.total); const max = Math.max.apply(null, vals) || 1;
                  const W = 300, H = 70, n = finais.length;
                  const pts = finais.map((a, i) => [10 + (i * (W - 20)) / Math.max(1, n - 1), H - 8 - ((a.total / max) * (H - 20))]);
                  const poly = pts.map((p) => p[0].toFixed(1) + "," + p[1].toFixed(1)).join(" ");
                  return (
                    <div className="form-card">
                      <div className="sec-head" style={{ fontSize: 13.5 }}><span className="si"><Icon name="chart" size={16} /></span> Evolução — palavras/min</div>
                      <svg viewBox={"0 0 " + W + " " + H} style={{ width: "100%", height: 80 }}>
                        <polyline points={poly} fill="none" stroke="var(--primary)" strokeWidth="2.5" />
                        {pts.map((p, i) => <circle key={i} cx={p[0]} cy={p[1]} r="3.5" fill="var(--primary)" />)}
                      </svg>
                      <div style={{ display: "flex", justifyContent: "space-between", fontSize: 11, color: "var(--text-3)" }}>
                        {finais.map((a, i) => <span key={i}>{a.date} · {a.total}</span>)}
                      </div>
                    </div>
                  );
                })()}
                {listaAvals.map((a) => (
                  <div className="team-row" key={a.id}>
                    <span className="ic-icon" style={{ width: 38, height: 38, flex: "none" }}><Icon name="book" size={18} /></span>
                    <div className="tr-info" style={{ flex: 1 }}>
                      <b>{a.patientName || "—"} — {a.ano}º ano {a.finalizada && <span className="tag ok" style={{ marginLeft: 4, fontSize: 10.5, padding: "1px 7px" }}><i className="d"></i>Finalizada</span>}</b>
                      <span>{a.date}{typeof a.total === "number" ? " · " + a.total + " palavras/min" : ""}{a.classif ? " · " + a.classif : (a.finalizada ? "" : " · em andamento")}</span>
                    </div>
                    {a.finalizada
                      ? <button className="btn btn-ghost btn-sm" onClick={() => abrirAval(a, true)}><Icon name="eye" size={15} /> Ver</button>
                      : <button className="btn btn-primary btn-sm" onClick={() => abrirAval(a, false)}><Icon name="arrowRight" size={15} /> Retomar</button>}
                  </div>
                ))}
                <div style={{ marginTop: 14, textAlign: "center" }}>
                  <button type="button" onClick={() => setShowClear(true)} style={{ background: "none", border: "none", cursor: "pointer", color: "#DC2626", fontSize: 12.5, fontWeight: 700, display: "inline-flex", alignItems: "center", gap: 6 }}>
                    <Icon name="x" size={14} /> Apagar as avaliações de {patient ? patient.name : "esta criança"}
                  </button>
                </div>
              </>
            )}
          </>
        )}

        {/* ---------- Etapa 1: Palavras ---------- */}
        {view === "palavras" && renderEtapa("palavras", palArr, txtTok, 60, "Leitura de Palavras", "1 minuto")}

        {/* ---------- Etapa 2: Texto ---------- */}
        {view === "texto" && (
          <>
            <div className="form-card">
              <div className="sec-head"><span className="si"><Icon name="book" size={18} /></span> Texto — {txtObj.titulo}</div>
              <p className="help-text" style={{ marginTop: 0 }}>Peça para a criança ler o texto. Depois corrija por toque abaixo.</p>
            </div>
            {renderEtapa("texto", palArr, txtTok, 120, "Leitura de Texto", "2 minutos")}
          </>
        )}

        {/* ---------- Resultado ---------- */}
        {view === "resultado" && (
          <>
            <div className="form-card">
              <div className="sec-head"><span className="si"><Icon name="clipboardCheck" size={18} /></span> Resultados</div>
              <div className="hay-scores">
                <div className="hsc"><span>Palavras lidas (lista)</span><b>{lidasPal}</b></div>
                <div className="hsc"><span>Palavras lidas (texto)</span><b>{lidasTxt}</b></div>
                <div className="hsc"><span>Erros (lista)</span><b>{errosN("palavras")}</b></div>
                <div className="hsc"><span>Erros (texto)</span><b>{errosN("texto")}</b></div>
                <div className="hsc"><span>Total (lista + texto)</span><b>{total}</b></div>
              </div>
            </div>

            <div className="form-card">
              <div className="sec-head"><span className="si"><Icon name="chart" size={18} /></span> Classificação — {ano}º ano</div>
              {classif && (
                <div style={{ display: "inline-flex", alignItems: "center", gap: 8, padding: "8px 16px", borderRadius: 99, background: classif.col, color: "#fff", fontWeight: 800, fontSize: 15, marginBottom: 12 }}>
                  {classif.l} · {total} palavras/min
                </div>
              )}
              <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(120px, 1fr))", gap: 8 }}>
                {NIVEIS.map((nv, i) => {
                  const on = classif && classif.key === nv.key;
                  return (
                    <div key={nv.key} style={{ borderRadius: 10, padding: "10px 12px", textAlign: "center", border: "2px solid " + nv.col, background: on ? nv.col : "transparent", color: on ? "#fff" : "var(--text-2)" }}>
                      <div style={{ fontSize: 12, fontWeight: 800 }}>{nv.l}</div>
                      <div style={{ fontSize: 13, fontWeight: 700, marginTop: 2 }}>{TABELA[ano] ? TABELA[ano].disp[i] : ""}</div>
                    </div>
                  );
                })}
              </div>
            </div>

            <div className="form-card">
              <div className="sec-head"><span className="si"><Icon name="fileText" size={18} /></span> Observações do examinador</div>
              <textarea className="ctrl" style={{ minHeight: 70 }} placeholder="Observações sobre a leitura, comportamento, estratégias…" value={dados.obs || ""} onChange={(e) => setObs(e.target.value)} disabled={readonly} />
            </div>
          </>
        )}

        {/* ---------- Rodapé (etapas) ---------- */}
        {view !== "ident" && (
          readonly ? (
            <div className="hay-nav" style={{ flexWrap: "wrap", gap: 10 }}>
              <button className="btn btn-ghost btn-sm" onClick={() => { const order = ["palavras", "texto", "resultado"]; const i = order.indexOf(view); if (i > 0) { setView(order[i - 1]); goTop(); } else { resetTudo(); } }}><Icon name="chevL" size={16} /> Voltar</button>
              <span style={{ marginLeft: "auto", display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12.5, fontWeight: 700, color: "var(--text-3)" }}><Icon name="eye" size={14} /> Modo consulta (somente leitura)</span>
              {view !== "resultado" && <button className="btn btn-primary btn-sm" onClick={() => { const order = ["palavras", "texto", "resultado"]; setView(order[order.indexOf(view) + 1]); goTop(); }}>Continuar <Icon name="arrowRight" size={16} /></button>}
            </div>
          ) : (
            <div className="hay-nav" style={{ flexWrap: "wrap", gap: 10 }}>
              <button className="btn btn-ghost btn-sm" onClick={() => { const order = ["palavras", "texto", "resultado"]; const i = order.indexOf(view); if (i > 0) { setView(order[i - 1]); goTop(); } else { guardedLeave(() => resetTudo()); } }}><Icon name="chevL" size={16} /> {view === "palavras" ? "Voltar aos dados" : "Voltar"}</button>
              <div style={{ display: "flex", alignItems: "center", gap: 10, marginLeft: "auto", flexWrap: "wrap" }}>
                {dirty
                  ? <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12.5, fontWeight: 700, color: "var(--accent-strong)" }}><span style={{ width: 8, height: 8, borderRadius: "50%", background: "var(--accent)" }} /> Alterações não salvas</span>
                  : (savedAt && <span style={{ fontSize: 12.5, fontWeight: 700, color: "var(--primary)" }}>Salvo ✓ {savedAt}</span>)}
                <button className="btn btn-primary btn-sm" onClick={salvar} disabled={saving || !dirty}><Icon name="clipboardCheck" size={16} /> {saving ? "Salvando…" : "Salvar avaliação"}</button>
                {view === "resultado"
                  ? <button className="btn btn-primary btn-sm" onClick={finalizar} disabled={saving}><Icon name="clipboardCheck" size={16} /> {saving ? "Salvando…" : "Finalizar avaliação"}</button>
                  : <button className="btn btn-primary btn-sm" onClick={() => { const order = ["palavras", "texto", "resultado"]; setView(order[order.indexOf(view) + 1]); goTop(); }}>Continuar <Icon name="arrowRight" size={16} /></button>}
              </div>
            </div>
          )
        )}

        {leaveGuard && (
          <div className="modal-scrim" onClick={() => setLeaveGuard(null)}>
            <div className="modal" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 440 }}>
              <div className="modal-head"><div><h3>Alterações não salvas</h3><p>Há alterações não salvas nesta avaliação.</p></div><button className="x" onClick={() => setLeaveGuard(null)} aria-label="Fechar"><Icon name="x" size={18} /></button></div>
              <div className="modal-body">
                <p className="help-text" style={{ marginTop: 0 }}>Deseja salvar antes de sair?</p>
                <div className="form-actions" style={{ marginTop: 14, display: "flex", gap: 8, flexWrap: "wrap", justifyContent: "flex-end" }}>
                  <button className="btn btn-ghost btn-sm" onClick={() => setLeaveGuard(null)}>Cancelar</button>
                  <button className="btn btn-ghost btn-sm" onClick={discardAndLeave}>Sair sem salvar</button>
                  <button className="btn btn-primary btn-sm" onClick={saveAndLeave} disabled={saving}><Icon name="clipboardCheck" size={15} /> {saving ? "Salvando…" : "Salvar e sair"}</button>
                </div>
              </div>
            </div>
          </div>
        )}

        {showClear && (
          <div className="modal-scrim" onClick={() => setShowClear(false)}>
            <div className="modal" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 440 }}>
              <div className="modal-head"><div><h3>Apagar as avaliações desta criança?</h3><p>Esta ação não pode ser desfeita.</p></div><button className="x" onClick={() => setShowClear(false)} aria-label="Fechar"><Icon name="x" size={18} /></button></div>
              <div className="modal-body">
                <p className="help-text" style={{ marginTop: 0 }}>Todas as avaliações de Fluência Leitora de <b>{patient ? patient.name : "esta criança"}</b> serão apagadas permanentemente. As demais crianças não são afetadas.</p>
                <div className="form-actions" style={{ marginTop: 14, display: "flex", gap: 8, flexWrap: "wrap", justifyContent: "flex-end" }}>
                  <button className="btn btn-ghost btn-sm" onClick={() => setShowClear(false)}>Cancelar</button>
                  <button className="btn btn-sm" style={{ background: "#DC2626", color: "#fff" }} onClick={apagarDoPaciente}><Icon name="x" size={15} /> Apagar</button>
                </div>
              </div>
            </div>
          </div>
        )}

        {toast && (
          <div style={{ position: "fixed", left: "50%", bottom: 22, transform: "translateX(-50%)", zIndex: 90, display: "flex", alignItems: "center", gap: 9, padding: "11px 16px", borderRadius: 12, boxShadow: "var(--shadow-lg)", fontSize: 13.5, fontWeight: 700, color: "#fff", background: toast.kind === "err" ? "#DC2626" : "var(--primary)" }}>
            <Icon name={toast.kind === "err" ? "x" : "clipboardCheck"} size={16} /> {toast.text}
          </div>
        )}
      </div>
    );
  }

  window.Fluencia = Fluencia;
})();
