/* ============================================================
   EDUCARE — Teste de Hayling Infantil (wizard de aplicação)
   Funções executivas: iniciação (Parte A) e inibição (Parte B)
   ============================================================ */
(function () {
  const { useState, useEffect, useRef } = React;
  const Icon = window.Icon;

  const HAYLING_KEY = "educare-hayling-config";
  const CUR_VERSION = 3; // bump quando a estrutura de tipos muda (força migração de configs antigas)

  // Parte A — iniciação: 9 tipos fixos (acerto = C ou PC)
  const DEFAULT_TIPOS_A = [
    { code: "C", score: 1, acerto: true, desc: "Palavra que completa corretamente a frase" },
    { code: "PC", score: 1, acerto: true, desc: "Perseveração correta (idêntica à da frase anterior, mas relacionada à frase atual)" },
    { code: "N", score: 0, acerto: false, desc: "Neologismo (palavra inventada)" },
    { code: "O", score: 0, acerto: false, desc: "Omissão (não evoca nenhuma palavra)" },
    { code: "PE", score: 0, acerto: false, desc: "Perseveração incorreta (idêntica à anterior e sem relação com a frase)" },
    { code: "UL", score: 0, acerto: false, desc: "Semanticamente relacionada a frases anteriores e sem sentido no contexto" },
    { code: "E", score: 0, acerto: false, desc: "Mais de uma palavra ou expressão" },
    { code: "MM", score: 0, acerto: false, desc: "Não está de acordo com a estrutura gramatical da frase" },
    { code: "E/MM", score: 0, acerto: false, desc: "E e MM juntos" },
  ];
  // Parte B — inibição (LÓGICA INVERTIDA: 0 ponto = acerto; quanto mais pontos, pior a inibição)
  const DEFAULT_TIPOS_B = [
    { code: "C", score: 3, acerto: false, desc: "Palavra que completa a frase dando-lhe sentido" },
    { code: "CP", score: 3, acerto: false, desc: "Perseveração de palavra já usada antes e que dá sentido à frase" },
    { code: "O/N", score: 2, acerto: false, desc: "Omissão (em 60s) ou uso de neologismo" },
    { code: "PU", score: 2, acerto: false, desc: "Perseveração de palavra já usada que NÃO dá sentido à frase atual" },
    { code: "SO", score: 1, acerto: false, desc: "Palavra que é o contrário daquela relacionada ao sentido da frase" },
    { code: "SA", score: 1, acerto: false, desc: "Resposta semanticamente relacionada ao assunto da frase" },
    { code: "SB", score: 1, acerto: false, desc: "Resposta semanticamente relacionada à que completaria a frase" },
    { code: "SC", score: 1, acerto: false, desc: "Palavra vagamente relacionada, de modo lúdico/irônico/grosseiro" },
    { code: "UR", score: 0, acerto: true, desc: "Sem relação semântica, mas que pode estar no local da aplicação" },
    { code: "UL", score: 0, acerto: true, desc: "Sem relação com a frase, mas dita em frase anterior" },
    { code: "URL", score: 0, acerto: true, desc: "Objeto existente na sala de aplicação" },
    { code: "U", score: 0, acerto: true, desc: "Sem relação semântica, não é objeto da sala nem perseverativa" },
    { code: "MM", score: 0, acerto: true, desc: "Discordante com a estrutura gramatical da frase (gênero, número, grau)" },
  ];

  const DEFAULT = {
    version: CUR_VERSION,
    partA: {
      tipos: DEFAULT_TIPOS_A,
      instruction: "Peça à criança que complete cada frase com uma palavra que FAÇA sentido. As duas primeiras são de treino e não entram na pontuação.",
      examples: ["O ônibus leva as crianças para o", "A mulher usa tesoura para cortar o"],
      sentences: [
        "O bombeiro apagou o",
        "A criança colocou o caderno na",
        "O avô tomou remédio para dor no",
        "O aluno escolheu um livro de história na",
        "A galinha botou os ovos no",
        "O fazendeiro tira o leite da",
        "Para dormir a criança veste um",
        "A mulher guarda os biscoitos na",
        "O cachorro mordeu o",
        "No meu quarto há muitos",
      ],
    },
    partB: {
      tipos: DEFAULT_TIPOS_B,
      instruction: "Peça à criança que complete cada frase com uma palavra que NÃO combine com a frase. As duas primeiras são de treino e não entram na pontuação.",
      examples: ["O peixe nada na", "O homem colocou o leite na"],
      sentences: [
        "O jardineiro molhou a",
        "A mãe guardou a roupa no",
        "O jogador de futebol chutou a bola na",
        "A turma visitou a jaula dos leões no",
        "O príncipe deu um beijo na",
        "O menino serve o suco no",
        "Para casar a noiva veste um",
        "O homem guarda seu dinheiro no",
        "O temporal derrubou a",
        "No meu aniversário havia muitos",
      ],
    },
  };

  function normalizeTipos(arr, fallback) {
    if (!Array.isArray(arr) || !arr.length) return JSON.parse(JSON.stringify(fallback));
    return arr.map((t) => ({
      code: t.code, score: Number(t.score) || 0,
      acerto: typeof t.acerto === "boolean" ? t.acerto : (t.code === "C" || t.code === "PC"),
      desc: t.desc || "",
    }));
  }
  function loadConfig() {
    try {
      const s = JSON.parse(localStorage.getItem(HAYLING_KEY) || "null");
      if (s && s.partA && s.partB && Array.isArray(s.partA.sentences) && Array.isArray(s.partB.sentences)) {
        if (s.version !== CUR_VERSION) {
          // config antiga: força as tabelas de tipos fixas atuais (Parte A e Parte B)
          s.partA.tipos = JSON.parse(JSON.stringify(DEFAULT_TIPOS_A));
          s.partB.tipos = JSON.parse(JSON.stringify(DEFAULT_TIPOS_B));
          s.version = CUR_VERSION;
        } else {
          s.partA.tipos = normalizeTipos(s.partA.tipos, DEFAULT_TIPOS_A);
          s.partB.tipos = normalizeTipos(s.partB.tipos, DEFAULT_TIPOS_B);
        }
        delete s.tipos;
        return s;
      }
    } catch (e) { /* ignora */ }
    return JSON.parse(JSON.stringify(DEFAULT));
  }
  function saveConfig(cfg) { try { localStorage.setItem(HAYLING_KEY, JSON.stringify({ ...cfg, version: CUR_VERSION })); } catch (e) { /* indisponível */ } }

  /* ---------------- Tabelas normativas (âncoras de percentil) ----------------
     Cada variável é um array de 5 âncoras na ordem [p95, p75, p50, p25, p5].
     Conversão: âncora mais próxima do escore → percentil; empate → percentil menor.
     Valores PROVISÓRIOS (transcritos do manual) — editáveis em "Tabelas normativas". */
  const NORMS_KEY = "educare-hayling-norms";
  const NORM_PCT = [95, 75, 50, 25, 5];
  const NORM_AGES = [6, 7, 8, 9, 10, 11, 12];
  const NORM_SCHOOLS = ["privada", "publica"];

  // VARS: key (no teste) → norm (campo na tabela)
  const VARS = [
    { key: "timeA", norm: "tempoA", label: "Tempo A (s)", part: "A" },
    { key: "errorsA", norm: "errosA", label: "Erros A", part: "A" },
    { key: "timeB", norm: "tempoB", label: "Tempo B (s)", part: "B" },
    { key: "errorsB", norm: "errosB", label: "Erros B", part: "B" },
    { key: "catErrors", norm: "catB", label: "Erros de categorização", part: "B" },
    { key: "diff", norm: "difBA", label: "Diferença (B − A)", part: "B" },
    { key: "ratio", norm: "relBA", label: "Relação (B / A)", part: "B" },
  ];
  const NORM_VARS = [
    { field: "tempoA", label: "Tempo A (s)" },
    { field: "errosA", label: "Erros A" },
    { field: "tempoB", label: "Tempo B (s)" },
    { field: "errosB", label: "Erros B" },
    { field: "catB", label: "Erros de categorização" },
    { field: "difBA", label: "Diferença (B − A)" },
    { field: "relBA", label: "Relação (B / A)" },
  ];

  const DEFAULT_NORMS = {
    "6_privada": { tempoA: [13, 19, 22, 27, 30], errosA: [0, 0, 1, 1, 3], tempoB: [24, 36, 40, 55, 70], errosB: [1, 3, 4, 5, 8], catB: [2, 6, 9, 13, 22], difBA: [1, 12, 19, 29, 54], relBA: [1, 2, 2, 3, 4] },
    "6_publica": { tempoA: [19, 23, 31, 36, 41], errosA: [0, 1, 2, 2, 3], tempoB: [37, 45, 54, 64, 109], errosB: [5, 6, 7, 8, 9], catB: [10, 12, 17, 21, 27], difBA: [5, 13, 20, 40, 90], relBA: [1, 2, 2, 4, 6] },
    "7_privada": { tempoA: [11, 13, 17, 20, 26], errosA: [0, 0, 0, 1, 2], tempoB: [25, 35, 39, 43, 55], errosB: [1, 3, 4, 5, 6], catB: [1, 7, 9, 10, 13], difBA: [13, 15, 21, 26, 36], relBA: [1, 2, 2, 3, 4] },
    "7_publica": { tempoA: [9, 18, 21, 29, 52], errosA: [0, 1, 2, 2, 3], tempoB: [29, 30, 36, 50, 105], errosB: [1, 5, 6, 7, 8], catB: [3, 9, 14, 18, 20], difBA: [7, 12, 15, 21, 54], relBA: [1, 2, 2, 2, 3] },
    "8_privada": { tempoA: [8, 10, 15, 19, 27], errosA: [0, 0, 0, 0, 2], tempoB: [19, 32, 41, 44, 51], errosB: [1, 3, 5, 6, 7], catB: [2, 5, 10, 13, 17], difBA: [7, 16, 22, 30, 40], relBA: [1, 2, 3, 3, 5] },
    "8_publica": { tempoA: [10, 16, 19, 31, 49], errosA: [0, 0, 0, 1, 3], tempoB: [29, 43, 49, 62, 84], errosB: [3, 4, 6, 7, 8], catB: [7, 9, 11, 13, 17], difBA: [9, 23, 31, 38, 57], relBA: [1, 2, 3, 3, 5] },
    "9_privada": { tempoA: [7, 11, 15, 18, 32], errosA: [0, 0, 0, 1, 2], tempoB: [15, 27, 32, 42, 94], errosB: [1, 2, 4, 5, 8], catB: [1, 4, 8, 11, 17], difBA: [5, 13, 21, 27, 77], relBA: [1, 2, 2, 4, 5] },
    "9_publica": { tempoA: [9, 13, 15, 21, 34], errosA: [0, 0, 0, 1, 2], tempoB: [17, 31, 36, 51, 82], errosB: [2, 3, 5, 6, 8], catB: [2, 5, 9, 12, 21], difBA: [1, 15, 20, 33, 56], relBA: [1, 2, 2, 3, 4] },
    "10_privada": { tempoA: [8, 12, 14, 15, 22], errosA: [0, 0, 0, 0, 1], tempoB: [14, 24, 32, 34, 45], errosB: [1, 3, 4, 4, 7], catB: [1, 5, 8, 9, 12], difBA: [6, 12, 18, 21, 26], relBA: [1, 2, 2, 3, 3] },
    "10_publica": { tempoA: [12, 16, 20, 28, 42], errosA: [0, 0, 0, 1, 1], tempoB: [23, 34, 38, 48, 93], errosB: [1, 3, 5, 6, 8], catB: [1, 6, 10, 13, 19], difBA: [3, 12, 20, 26, 52], relBA: [1, 2, 2, 2, 3] },
    "11_privada": { tempoA: [4, 8, 11, 14, 20], errosA: [0, 0, 0, 1, 1], tempoB: [12, 20, 28, 32, 48], errosB: [1, 2, 4, 4, 6], catB: [2, 4, 5, 10, 14], difBA: [2, 14, 19, 21, 28], relBA: [1, 2, 2, 3, 4] },
    "11_publica": { tempoA: [10, 15, 17, 20, 34], errosA: [0, 0, 0, 1, 2], tempoB: [27, 35, 38, 42, 67], errosB: [1, 3, 5, 6, 8], catB: [1, 6, 9, 13, 19], difBA: [8, 17, 21, 25, 36], relBA: [1, 2, 2, 3, 4] },
    "12_privada": { tempoA: [5, 9, 11, 13, 17], errosA: [0, 0, 0, 1, 1], tempoB: [10, 23, 28, 33, 39], errosB: [1, 1, 3, 4, 7], catB: [1, 3, 7, 9, 16], difBA: [3, 14, 18, 21, 32], relBA: [1, 2, 2, 3, 5] },
    "12_publica": { tempoA: [8, 11, 15, 18, 25], errosA: [0, 0, 0, 1, 2], tempoB: [18, 30, 34, 45, 57], errosB: [0, 3, 4, 5, 7], catB: [1, 4, 7, 10, 17], difBA: [5, 18, 20, 25, 53], relBA: [1, 2, 3, 3, 6] },
  };

  function loadNorms() {
    try {
      const o = JSON.parse(localStorage.getItem(NORMS_KEY) || "null");
      if (o && !Array.isArray(o) && typeof o === "object" && Object.keys(o).length) return o;
    } catch (e) { /* ignora */ }
    return JSON.parse(JSON.stringify(DEFAULT_NORMS));
  }
  function saveNorms(obj) { try { localStorage.setItem(NORMS_KEY, JSON.stringify(obj)); } catch (e) { /* indisponível */ } }

  // Âncora mais próxima → { percentile, anchor }. Empate: percentil menor (mais conservador).
  function lookupAnchor(norms, age, school, normField, value) {
    if (age == null || isNaN(age) || !school) return null;
    const tbl = (norms || {})[age + "_" + school];
    if (!tbl) return null;
    const anchors = tbl[normField];
    if (!Array.isArray(anchors) || anchors.length !== 5) return null;
    const v = Number(value);
    let bestDist = Infinity, bestPct = null, bestAnchor = null;
    for (let i = 0; i < 5; i++) {
      const d = Math.abs(v - Number(anchors[i]));
      if (d < bestDist || (d === bestDist && NORM_PCT[i] < bestPct)) {
        bestDist = d; bestPct = NORM_PCT[i]; bestAnchor = Number(anchors[i]);
      }
    }
    return { percentile: bestPct, anchor: bestAnchor };
  }

  // Tabela universal fixa: percentil → classificação
  function classify(p) {
    if (p == null || isNaN(p)) return null;
    if (p > 98) return "Excepcionalmente Alta";
    if (p >= 91) return "Acima da Média";
    if (p >= 75) return "Média Superior";
    if (p >= 25) return "Média";
    if (p >= 9) return "Média Inferior";
    if (p >= 2) return "Abaixo da Média";
    return "Excepcionalmente Baixa";
  }

  const fmtRaw = (key, val) => {
    if (val == null || (typeof val === "number" && isNaN(val))) return "—";
    if (key === "timeA" || key === "timeB" || key === "diff") return Number(val).toFixed(1) + "s";
    if (key === "ratio") return Number(val).toFixed(2);
    return String(val);
  };

  const num = (t) => { const n = parseFloat(String(t).replace(",", ".")); return isNaN(n) ? 0 : n; };
  const tipoOf = (tipos, code) => (tipos || []).find((x) => x.code === code);
  const scoreOf = (tipos, code) => { const t = tipoOf(tipos, code); return t ? (t.score || 0) : 0; };

  function totalsOf(items, tipos) {
    const totalTime = items.reduce((s, it) => s + num(it.time), 0);
    const pontos = items.reduce((s, it) => { const t = tipoOf(tipos, it.tipo); return s + (t ? (t.score || 0) : 0); }, 0);
    const maxScore = (tipos || []).reduce((m, t) => Math.max(m, t.score || 0), 0);
    const acertos = items.filter((it) => { const t = tipoOf(tipos, it.tipo); return !!(t && t.acerto); }).length;
    const erros = items.filter((it) => { const t = tipoOf(tipos, it.tipo); return !!(it.tipo && t && !t.acerto); }).length;
    return { totalTime, pontos, max: maxScore * items.length, acertos, erros };
  }

  /* ---------------- Cronômetro ---------------- */
  function Stopwatch({ value, onChange }) {
    const [running, setRunning] = useState(false);
    const [elapsed, setElapsed] = useState(0);
    const startRef = useRef(0);

    useEffect(() => {
      if (!running) return;
      startRef.current = Date.now();
      const id = setInterval(() => setElapsed((Date.now() - startRef.current) / 1000), 100);
      return () => clearInterval(id);
    }, [running]);

    function toggle() {
      if (running) { setRunning(false); onChange(elapsed.toFixed(1)); }
      else { setElapsed(0); setRunning(true); }
    }

    return (
      <div className="sw">
        <input className="ctrl sw-in" inputMode="decimal" placeholder="seg"
          value={running ? elapsed.toFixed(1) : (value || "")}
          readOnly={running}
          onChange={(e) => onChange(e.target.value)} />
        <button type="button" className={"sw-btn" + (running ? " on" : "")} onClick={toggle}>
          {running ? "Parar" : "Iniciar"}
        </button>
      </div>
    );
  }

  /* ---------------- Linha de item ---------------- */
  function ItemRow({ n, sentence, item, onChange, tipos }) {
    const set = (k, v) => onChange({ ...item, [k]: v });
    const pont = item.tipo ? scoreOf(tipos, item.tipo) : null;
    return (
      <div className="hay-item">
        <div className="hi-sentence"><span className="hi-n">{n}</span><span>{sentence} <i className="hi-gap">______</i></span></div>
        <div className="hi-fields">
          <div className="fld"><label>Resposta da criança</label>
            <input className="ctrl" value={item.resp} onChange={(e) => set("resp", e.target.value)} placeholder="palavra dita" /></div>
          <div className="fld"><label>Tempo (s)</label><Stopwatch value={item.time} onChange={(v) => set("time", v)} /></div>
          <div className="fld"><label>Tipo</label>
            <select className="ctrl" value={item.tipo || ""} onChange={(e) => set("tipo", e.target.value)}>
              <option value="">—</option>
              {tipos.map((t) => <option key={t.code} value={t.code}>{t.code}</option>)}
            </select>
          </div>
          <div className="fld"><label>Pontuação</label>
            <div className="pont-box">{pont == null ? "—" : pont}</div>
          </div>
        </div>
      </div>
    );
  }

  /* ---------------- Etapa de uma parte ---------------- */
  function PartStep({ part, items, setItems, tipos, crossTA, pontLabel }) {
    const t = totalsOf(items, tipos);
    const setItem = (i, v) => setItems(items.map((it, idx) => (idx === i ? v : it)));
    return (
      <div>
        <div className="hay-instruction"><Icon name="help" size={18} /> {part.instruction}</div>
        <div className="hay-examples">
          <b>Exemplos (treino — não pontuam)</b>
          {part.examples.map((ex, i) => (
            <div className="hay-ex" key={i}><span className="hi-n ex">{String.fromCharCode(97 + i)}</span>{ex} <i className="hi-gap">______</i></div>
          ))}
        </div>

        {part.sentences.map((s, i) => (
          <ItemRow key={i} n={i + 1} sentence={s} item={items[i]} tipos={tipos} onChange={(v) => setItem(i, v)} />
        ))}
        <div className="hay-foot">
          <div className="stat"><span>Tempo total</span><b>{t.totalTime.toFixed(1)}s</b></div>
          <div className="stat"><span>Total de Erros</span><b>{t.erros}/10</b></div>
          <div className="stat"><span>Total de Acertos</span><b>{t.acertos}/10</b></div>
          {pontLabel && <div className="stat"><span>{pontLabel}</span><b>{t.pontos}/{t.max}</b></div>}
        </div>
        {crossTA != null && (
          <div className="hay-foot">
            <div className="stat"><span>TB − TA (Tempo B − Tempo A)</span><b>{(t.totalTime - crossTA).toFixed(1)}s</b></div>
            <div className="stat"><span>TB / TA (Tempo B ÷ Tempo A)</span><b>{(crossTA > 0 ? t.totalTime / crossTA : 0).toFixed(2)}</b></div>
          </div>
        )}
      </div>
    );
  }

  /* ---------------- Análise (mock IA, baseada em regras) ---------------- */
  function buildInsight(tA, tB, diff, ratio) {
    const p = [];
    p.push(`Parte A (iniciação): ${tA.acertos}/10 acertos (${tA.erros} erros) em ${tA.totalTime.toFixed(1)}s. Parte B (inibição): ${tB.acertos}/10 acertos (${tB.erros} erros) em ${tB.totalTime.toFixed(1)}s.`);
    if (ratio >= 1.5) p.push(`A razão TB/TA = ${ratio.toFixed(2)} aponta custo de tempo elevado para inibir a resposta automática, sugerindo maior demanda do controle inibitório.`);
    else if (ratio >= 1.1) p.push(`A razão TB/TA = ${ratio.toFixed(2)} indica custo de tempo moderado na inibição, dentro do que se espera para a faixa.`);
    else if (ratio > 0) p.push(`A razão TB/TA = ${ratio.toFixed(2)} mostra pouca diferença de tempo entre iniciar e inibir — pode indicar bom controle inibitório ou baixa adesão à instrução da Parte B.`);
    p.push(`O custo absoluto de inibição (TB − TA) foi de ${diff.toFixed(1)}s.`);
    if (tB.erros >= 5) p.push(`Na Parte B houve ${tB.erros}/10 erros: a criança tendeu a completar com palavras que combinam com a frase, sinalizando dificuldade em suprimir a resposta dominante.`);
    else if (tB.erros >= 2) p.push(`Na Parte B houve ${tB.erros}/10 erros: inibição parcialmente preservada, com alguns deslizes para respostas relacionadas.`);
    else p.push(`Na Parte B os erros foram baixos (${tB.erros}/10): boa capacidade de gerar respostas não relacionadas (inibição preservada).`);
    return p.join(" ");
  }

  /* ---------------- Exportar PDF (via impressão do navegador) ---------------- */
  function esc(s) { return String(s == null ? "" : s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); }
  function exportPDF({ patientName, idade, escolaLabel, date, normLabel, scoreRows, analysis }) {
    const fmtR = (r) => fmtRaw(r.key, r.raw);
    const pctCell = (r) => r.p == null ? "—" : (r.p + (r.anchor != null ? " <span style='color:#888;font-size:10px'>(≈ " + r.anchor + ")</span>" : ""));
    const rowsHtml = (pt) => scoreRows.filter((r) => r.part === pt).map((r) =>
      `<tr><td>${esc(r.label)}</td><td class="c">${esc(fmtR(r))}</td><td class="c">${pctCell(r)}</td><td>${esc(r.cls || "— (norma não cadastrada)")}</td></tr>`).join("");
    const head = `<tr><th>Variável</th><th>Escore bruto</th><th>Percentil</th><th>Classificação</th></tr>`;
    const html = `<!DOCTYPE html><html lang="pt-BR"><head><meta charset="UTF-8"><title>Folha de Registro — Hayling Infantil — ${esc(patientName)}</title>
      <style>
        body{font-family:Arial,Helvetica,sans-serif;color:#142826;padding:24px;font-size:12px}
        h1{font-size:16px;text-align:center;margin:0 0 4px}
        h2{font-size:13px;background:#E9F0EE;padding:6px 8px;border-radius:6px;margin:18px 0 6px}
        .meta{margin:8px 0 4px}
        table{width:100%;border-collapse:collapse;margin-top:4px}
        th,td{border:1px solid #B9C9C5;padding:6px 8px;text-align:left;vertical-align:top}
        th{background:#D8E5E2;font-size:11px}
        td.c{text-align:center}
        .an{margin-top:16px;font-size:12px;line-height:1.5}
        .an b{display:block;margin-bottom:4px}
        .foot{margin-top:14px;font-size:10px;color:#666}
      </style></head><body>
      <h1>FOLHA DE REGISTRO — TESTE HAYLING INFANTIL</h1>
      <div class="meta"><b>Paciente:</b> ${esc(patientName) || "—"} &nbsp;&nbsp; <b>Idade:</b> ${esc(idade) || "—"}${idade ? " anos" : ""} &nbsp;&nbsp; <b>Escola:</b> ${esc(escolaLabel)} &nbsp;&nbsp; <b>Data:</b> ${esc(date)}<br/><b>Norma aplicada:</b> ${esc(normLabel || "—")}</div>
      <h2>PARTE A — Iniciação</h2>
      <table>${head}${rowsHtml("A")}</table>
      <h2>PARTE B — Inibição</h2>
      <table>${head}${rowsHtml("B")}</table>
      <p class="an"><b>Análise</b>${esc(analysis)}</p>
      <p class="foot">Classificação por percentil baseada nas tabelas normativas cadastradas. Interpretação automática de apoio — não substitui o julgamento clínico.</p>
      </body></html>`;
    const w = window.open("", "_blank");
    if (!w) { alert("Permita pop-ups para exportar o PDF."); return; }
    w.document.write(html); w.document.close(); w.focus();
    setTimeout(() => { try { w.print(); } catch (e) { /* usuário imprime manualmente */ } }, 300);
  }

  /* ---------------- Editor de frases e pontuação ---------------- */
  function ConfigEditor({ config, onSave, onClose }) {
    const [c, setC] = useState(() => JSON.parse(JSON.stringify(config)));
    const upd = (part, field, val) => setC((prev) => ({ ...prev, [part]: { ...prev[part], [field]: val } }));
    const updArr = (part, field, i, val) => setC((prev) => {
      const arr = prev[part][field].slice(); arr[i] = val;
      return { ...prev, [part]: { ...prev[part], [field]: arr } };
    });
    const updTipo = (pk, i, field, val) => setC((prev) => ({
      ...prev, [pk]: { ...prev[pk], tipos: prev[pk].tipos.map((t, idx) => (idx === i ? { ...t, [field]: val } : t)) },
    }));
    const addTipo = (pk) => setC((prev) => ({ ...prev, [pk]: { ...prev[pk], tipos: [...prev[pk].tipos, { code: "", score: 0, acerto: false, desc: "" }] } }));
    const removeTipo = (pk, i) => setC((prev) => ({ ...prev, [pk]: { ...prev[pk], tipos: prev[pk].tipos.filter((_, idx) => idx !== i) } }));

    const renderPart = (pk, label) => (
      <div className="form-card">
        <div className="sec-head">{label}</div>
        <div className="fld"><label>Instrução para o profissional</label>
          <textarea className="ctrl" value={c[pk].instruction} onChange={(e) => upd(pk, "instruction", e.target.value)}></textarea></div>
        <div className="fld"><label>Exemplos de treino</label>
          {c[pk].examples.map((ex, i) => (
            <input key={i} className="ctrl" style={{ marginBottom: 6 }} value={ex} onChange={(e) => updArr(pk, "examples", i, e.target.value)} />
          ))}
        </div>

        <div className="fld">
          <label>Tipos de resposta e pontuação</label>
          <div className="tipo-grid2 tipo-head2"><span>Código</span><span>Pontos</span><span>Acerto?</span><span>Descrição</span><span></span></div>
          {c[pk].tipos.map((t, i) => (
            <div className="tipo-grid2" key={i}>
              <input className="ctrl" value={t.code} onChange={(e) => updTipo(pk, i, "code", e.target.value)} placeholder="ex: C" />
              <input className="ctrl" type="number" value={t.score} onChange={(e) => updTipo(pk, i, "score", parseInt(e.target.value || "0", 10) || 0)} />
              <label className="tipo-chk"><input type="checkbox" checked={!!t.acerto} onChange={(e) => updTipo(pk, i, "acerto", e.target.checked)} /></label>
              <input className="ctrl" value={t.desc} onChange={(e) => updTipo(pk, i, "desc", e.target.value)} placeholder="descrição" />
              <button className="icon-btn-plain" onClick={() => removeTipo(pk, i)} aria-label="Remover"><Icon name="x" size={16} /></button>
            </div>
          ))}
          <button className="btn btn-ghost btn-xs" style={{ marginTop: 8 }} onClick={() => addTipo(pk)}><Icon name="plus" size={14} /> Adicionar tipo</button>
        </div>

        <div className="fld" style={{ marginBottom: 0 }}><label>Frases (10)</label>
          {c[pk].sentences.map((s, i) => (
            <div key={i} style={{ display: "flex", gap: 8, marginBottom: 6, alignItems: "center" }}>
              <span className="hi-n">{i + 1}</span>
              <input className="ctrl" value={s} onChange={(e) => updArr(pk, "sentences", i, e.target.value)} />
            </div>
          ))}
        </div>
      </div>
    );

    return (
      <div className="form-wrap">
        <button className="pt-back" onClick={onClose}><Icon name="chevL" size={16} /> Voltar</button>
        <div className="page-head"><div><h1>Gerenciar teste — Hayling Infantil</h1><p className="sub">Edite os tipos de resposta/pontuação (acerto = conta como acerto no placar), instruções, exemplos e frases. Fica salvo neste dispositivo.</p></div></div>

        {renderPart("partA", "Parte A — Relacionadas (iniciação)")}
        {renderPart("partB", "Parte B — Não relacionadas (inibição)")}
        <div className="save-bar"><button className="btn btn-primary" onClick={() => onSave(c)}><Icon name="clipboardCheck" size={18} /> Salvar configuração</button></div>
        <div style={{ textAlign: "center", marginTop: 8 }}>
          <button className="text-link" onClick={() => setC(JSON.parse(JSON.stringify(DEFAULT)))}>Restaurar padrão</button>
        </div>
      </div>
    );
  }

  /* ---------------- Editor de tabelas normativas (âncoras) ---------------- */
  function NormsEditor({ norms, onSave, onClose }) {
    const [n, setN] = useState(() => JSON.parse(JSON.stringify(norms || {})));
    const [age, setAge] = useState("8");
    const [escola, setEscola] = useState("privada");
    const key = age + "_" + escola;
    const tbl = n[key] || {};

    const setAnchor = (field, idx, val) => setN((prev) => {
      const cur = prev[key] ? { ...prev[key] } : {};
      const arr = (cur[field] ? cur[field].slice() : [0, 0, 0, 0, 0]);
      arr[idx] = parseFloat(String(val).replace(",", ".")) || 0;
      cur[field] = arr;
      return { ...prev, [key]: cur };
    });
    const resetThis = () => setN((prev) => {
      const def = DEFAULT_NORMS[key];
      return { ...prev, [key]: def ? JSON.parse(JSON.stringify(def)) : {} };
    });

    return (
      <div className="form-wrap">
        <button className="pt-back" onClick={onClose}><Icon name="chevL" size={16} /> Voltar</button>
        <div className="page-head"><div><h1>Tabelas normativas — Hayling Infantil</h1><p className="sub">5 âncoras por variável, na ordem dos percentis [95, 75, 50, 25, 5]. Valores PROVISÓRIOS (transcritos do manual) — revise antes de uso clínico.</p></div></div>

        <div className="form-card">
          <div className="sec-head">Faixa</div>
          <div className="grid-2">
            <div className="fld" style={{ marginBottom: 0 }}>
              <label>Idade</label>
              <select className="ctrl" value={age} onChange={(e) => setAge(e.target.value)}>
                {NORM_AGES.map((a) => <option key={a} value={String(a)}>{a} anos</option>)}
              </select>
            </div>
            <div className="fld" style={{ marginBottom: 0 }}>
              <label>Tipo de escola</label>
              <select className="ctrl" value={escola} onChange={(e) => setEscola(e.target.value)}>
                <option value="privada">Particular</option>
                <option value="publica">Pública</option>
              </select>
            </div>
          </div>
        </div>

        <div className="form-card">
          <div className="sec-head">Âncoras — {age} anos · {escola === "privada" ? "Particular" : "Pública"}</div>
          <div className="anchor-grid anchor-head">
            <span>Variável</span><span>P95</span><span>P75</span><span>P50</span><span>P25</span><span>P5</span>
          </div>
          {NORM_VARS.map((v) => {
            const arr = tbl[v.field] || [0, 0, 0, 0, 0];
            return (
              <div className="anchor-grid" key={v.field}>
                <span className="an-label">{v.label}</span>
                {[0, 1, 2, 3, 4].map((i) => (
                  <input key={i} className="ctrl" type="number" step="any" value={arr[i] == null ? "" : arr[i]} onChange={(e) => setAnchor(v.field, i, e.target.value)} />
                ))}
              </div>
            );
          })}
          <div style={{ marginTop: 10 }}>
            <button className="btn btn-ghost btn-xs" onClick={resetThis}><Icon name="chevL" size={14} /> Restaurar esta faixa ao padrão</button>
          </div>
        </div>

        <div className="save-bar"><button className="btn btn-primary" onClick={() => onSave(n)}><Icon name="clipboardCheck" size={18} /> Salvar tabelas</button></div>
      </div>
    );
  }

  /* ---------------- Wizard principal ---------------- */
  function newItems() { return Array.from({ length: 10 }, () => ({ resp: "", time: "", tipo: "" })); }

  function HaylingTest({ onBack, onOpenPatient, onRail }) {
    const [config, setConfig] = useState(loadConfig);
    const [norms, setNorms] = useState(loadNorms);
    const [editing, setEditing] = useState(false);
    const [editingNorms, setEditingNorms] = useState(false);
    const [step, setStep] = useState(1);
    const [pid, setPid] = useState("");
    const [date, setDate] = useState(window.todayISO());
    const [idade, setIdade] = useState("");
    const [escola, setEscola] = useState(""); // "publica" | "privada"
    const [aItems, setAItems] = useState(newItems);
    const [bItems, setBItems] = useState(newItems);
    const [err, setErr] = useState("");
    const [saved, setSaved] = useState(false);
    const [avalId, setAvalId] = useState(null);
    const [ia, setIa] = useState(null);
    const [iaBusy, setIaBusy] = useState(false);
    const [iaErro, setIaErro] = useState("");

    // Mostra a legenda dos tipos no painel direito durante a Parte A/B
    useEffect(() => {
      if (!onRail) return;
      if (step === 2) onRail({ kind: "hayling", title: "Tipos de resposta — Parte A", tipos: config.partA.tipos });
      else if (step === 3) onRail({ kind: "hayling", title: "Tipos de resposta — Parte B", tipos: config.partB.tipos });
      else onRail(null);
      return () => { onRail(null); };
    }, [step, config, onRail]);

    if (editing) {
      return <ConfigEditor config={config} onClose={() => setEditing(false)} onSave={(c) => { saveConfig(c); setConfig(c); setEditing(false); }} />;
    }
    if (editingNorms) {
      return <NormsEditor norms={norms} onClose={() => setEditingNorms(false)} onSave={(rows) => { saveNorms(rows); setNorms(rows); setEditingNorms(false); }} />;
    }

    const tA = totalsOf(aItems, config.partA.tipos);
    const tB = totalsOf(bItems, config.partB.tipos);
    const diff = tB.totalTime - tA.totalTime;
    const ratio = tA.totalTime > 0 ? tB.totalTime / tA.totalTime : 0;
    const patient = (window.PATIENTS || []).find((p) => p.id === pid);
    const escolaLabel = escola === "publica" ? "Pública" : escola === "privada" ? "Particular" : "—";
    const STEPS = ["Identificação", "Parte A", "Parte B", "Resultado"];

    const ageNum = parseInt(idade, 10);
    const raw = { timeA: tA.totalTime, errorsA: tA.erros, timeB: tB.totalTime, errorsB: tB.erros, catErrors: tB.pontos, diff: diff, ratio: ratio };
    const scoreRows = VARS.map((v) => {
      const r = lookupAnchor(norms, ageNum, escola, v.norm, raw[v.key]);
      const p = r ? r.percentile : null;
      return { key: v.key, label: v.label, part: v.part, raw: raw[v.key], p: p, anchor: r ? r.anchor : null, cls: classify(p) };
    });
    const byKey = {}; scoreRows.forEach((r) => { byKey[r.key] = r; });
    const hasNorms = scoreRows.some((r) => r.p != null);
    const missingIdent = !escola || isNaN(ageNum);
    const normLabel = missingIdent ? "—" : (norms[ageNum + "_" + escola] ? (ageNum + " anos · " + escolaLabel) : ("não cadastrada (" + ageNum + " anos · " + escolaLabel + ")"));

    function sheetAnalysis() {
      let txt = buildInsight(tA, tB, diff, ratio);
      if (missingIdent) { return txt + " Informe a idade e o tipo de escola (Etapa 1) para calcular os percentis."; }
      if (!hasNorms) { return txt + " As tabelas normativas para esta idade/escola não foram cadastradas — classificação por percentil indisponível."; }
      const ORDER = { "Excepcionalmente Alta": 7, "Acima da Média": 6, "Média Superior": 5, "Média": 4, "Média Inferior": 3, "Abaixo da Média": 2, "Excepcionalmente Baixa": 1 };
      const sc = (k) => (byKey[k] && byKey[k].cls ? ORDER[byKey[k].cls] : null);
      const avg = (a) => (a.length ? a.reduce((s, x) => s + x, 0) / a.length : null);
      const vel = avg([sc("timeA"), sc("timeB")].filter((x) => x != null));
      const prec = avg([sc("errorsA"), sc("errorsB"), sc("catErrors")].filter((x) => x != null));
      if (vel != null && prec != null) {
        if (vel >= 5 && prec <= 3) txt += " Padrão sugestivo de desempenho RÁPIDO PORÉM IMPRECISO: boa velocidade de resposta, mas precisão/inibição abaixo do esperado para a faixa.";
        else if (vel <= 3 && prec >= 5) txt += " Padrão sugestivo de desempenho LENTO PORÉM PRECISO: prioriza a acurácia em detrimento da velocidade.";
        else if (vel >= 5 && prec >= 5) txt += " Velocidade e precisão acima da média — bom controle executivo.";
        else if (vel <= 3 && prec <= 3) txt += " Velocidade e precisão abaixo da média — sugere maior necessidade de apoio nas funções executivas.";
        else txt += " Velocidade e precisão em níveis intermediários/equilibrados.";
      }
      txt += " Classificação por percentil — " + scoreRows.filter((r) => r.cls).map((r) => `${r.label}: ${r.cls} (P${r.p})`).join("; ") + ".";
      return txt;
    }

    /* Payload da IA — escores e percentis já calculados aqui, mais as
       respostas da criança. Só o primeiro nome. */
    function payloadIA() {
      return {
        primeiro_nome: patient ? String(patient.name).split(" ")[0] : "",
        idade: idade || null,
        tipo_de_escola: escolaLabel,
        data: date,
        norma_aplicada: normLabel,
        normas_cadastradas: hasNorms,
        razao_tempoB_sobre_tempoA: Number(ratio.toFixed(2)),
        acertos_parte_a: tA.acertos,
        acertos_parte_b: tB.acertos,
        variaveis: scoreRows.map((r) => ({
          variavel: r.label,
          parte: r.part,
          escore_bruto: fmtRaw(r.key, r.raw),
          percentil: r.p,
          classificacao: r.cls || null,
        })),
        protocolo: {
          parte_a: config.partA.sentences.map((s, i) => ({
            frase: s, resposta: aItems[i].resp, tempo_s: aItems[i].time, tipo: aItems[i].tipo,
          })),
          parte_b: config.partB.sentences.map((s, i) => ({
            frase: s, resposta: bItems[i].resp, tempo_s: bItems[i].time, tipo: bItems[i].tipo,
          })),
        },
      };
    }
    async function gerarIA() {
      setIaBusy(true); setIaErro("");
      try { setIa(await window.EducareIA.gerar("hayling", payloadIA())); }
      catch (e) { setIaErro(e.message || "Falha ao gerar a análise."); }
      finally { setIaBusy(false); }
    }
    /* Texto único para o prontuário e o PDF. */
    const analiseFinal = window.EducareIA.texto(ia, sheetAnalysis());

    function pickPatient(id) {
      setPid(id);
      const p = (window.PATIENTS || []).find((x) => x.id === id);
      if (p && p.age != null) setIdade(String(p.age));
      if (p && p.escola) setEscola(p.escola);
    }

    function next() {
      if (step === 1 && !pid) { setErr("Selecione o paciente para continuar."); return; }
      setErr("");
      setStep((s) => Math.min(4, s + 1));
      const sc = document.querySelector(".main-scroll"); if (sc) sc.scrollTop = 0;
    }
    function prev() {
      if (step === 1) { onBack(); return; }
      setStep((s) => s - 1);
    }

    /* Conteúdo gravado no prontuário — usado ao salvar e ao ressincronizar
       depois que a IA responde. */
    function registro() {
      const ratioCls = byKey.ratio && byKey.ratio.cls ? ` · TB/TA ${byKey.ratio.cls}` : "";
      const ident = `Idade: ${idade || "—"}${idade ? " anos" : ""} · Escola: ${escolaLabel}. `;
      const mkProto = (sentences, items, tipos) => sentences.map((s, i) => ({
        sentence: s, resp: items[i].resp, time: items[i].time, tipo: items[i].tipo,
        pont: items[i].tipo ? scoreOf(tipos, items[i].tipo) : null,
      }));
      return {
        result: `TB/TA ${ratio.toFixed(2)} · A ${tA.acertos}/10 · B ${tB.acertos}/10${ratioCls}`,
        note: ident + analiseFinal,
        sheet: {
          idade: idade || "—", escola: escolaLabel, normLabel,
          rows: scoreRows.map((r) => ({ label: r.label, part: r.part, raw: fmtRaw(r.key, r.raw), p: r.p, anchor: r.anchor, cls: r.cls })),
          protocol: {
            A: mkProto(config.partA.sentences, aItems, config.partA.tipos),
            B: mkProto(config.partB.sentences, bItems, config.partB.tipos),
          },
          ia,
        },
      };
    }

    function save() {
      const rec = window.addAvaliacao({ pid, name: "Teste de Hayling Infantil", date, ...registro() });
      setAvalId(rec ? rec.id : null);
      setSaved(true);
    }

    /* Laudo gerado depois de salvar reescreve o registro já gravado. */
    useEffect(() => {
      if (!avalId || !ia) return;
      window.atualizarAvaliacao(avalId, registro());
    }, [ia, avalId]);

    return (
      <div className="form-wrap">
        <div className="hay-top">
          <button className="pt-back" onClick={onBack}><Icon name="chevL" size={16} /> Voltar para avaliações</button>
          {step === 1 && (
            <span style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
              <button className="btn btn-ghost btn-xs" onClick={() => setEditingNorms(true)}><Icon name="chart" size={14} /> Tabelas normativas</button>
              <button className="btn btn-ghost btn-xs" onClick={() => setEditing(true)}><Icon name="fileText" size={14} /> Gerenciar teste</button>
            </span>
          )}
        </div>

        <div className="page-head" style={{ marginBottom: 14 }}>
          <div>
            <h1><Icon name="clipboardList" size={25} /> Teste de Hayling Infantil</h1>
            <p className="sub">Funções executivas — iniciação (Parte A) e inibição de resposta (Parte B).</p>
          </div>
        </div>

        <div className="hay-stepper">
          {STEPS.map((s, i) => (
            <button type="button" key={s} className={"hs" + (i + 1 === step ? " on" : "") + (i + 1 < step ? " done" : "")}
              onClick={() => { setStep(i + 1); const el = document.querySelector(".main-scroll"); if (el) el.scrollTop = 0; }}>
              <span className="hs-n">{i + 1 < step ? <Icon name="clipboardCheck" size={14} stroke={3} /> : i + 1}</span>{s}
            </button>
          ))}
        </div>

        {step === 1 && (
          <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>Idade da criança</label>
                <input className="ctrl" type="number" min="0" max="18" value={idade} onChange={(e) => setIdade(e.target.value)} placeholder="anos" />
              </div>
              <div className="fld">
                <label>Tipo de escola</label>
                <select className="ctrl" value={escola} onChange={(e) => setEscola(e.target.value)}>
                  <option value="">Selecione...</option>
                  <option value="publica">Pública</option>
                  <option value="privada">Privada</option>
                </select>
              </div>
            </div>
            <div className="fld" style={{ marginBottom: 0 }}>
              <label>Data do teste</label>
              <input className="ctrl" type="date" value={date} onChange={(e) => setDate(e.target.value)} />
            </div>
          </div>
        )}

        {step === 2 && <div className="form-card"><div className="sec-head"><span className="si"><Icon name="activity" size={18} /></span> Parte A — Iniciação</div><PartStep part={config.partA} items={aItems} setItems={setAItems} tipos={config.partA.tipos} /></div>}

        {step === 3 && <div className="form-card"><div className="sec-head"><span className="si"><Icon name="activity" size={18} /></span> Parte B — Inibição</div><PartStep part={config.partB} items={bItems} setItems={setBItems} tipos={config.partB.tipos} crossTA={tA.totalTime} pontLabel="Erros de categorização" /></div>}

        {step === 4 && (
          <>
            <div className="hay-sheet-id">
              <span><b>Paciente:</b> {patient ? patient.name : "—"}</span>
              <span><b>Idade:</b> {idade || "—"}{idade ? " anos" : ""}</span>
              <span><b>Escola:</b> {escolaLabel}</span>
              <span><b>Data:</b> {date}</span>
              <span><b>Norma aplicada:</b> {normLabel}</span>
            </div>

            {missingIdent && (
              <p className="form-err" style={{ display: "flex", alignItems: "center", gap: 7, color: "var(--accent-strong)", fontWeight: 700, fontSize: 13.5, margin: "0 0 12px" }}>
                <Icon name="help" size={15} /> Informe a idade e o tipo de escola (Etapa 1) para calcular os percentis.
              </p>
            )}

            {["A", "B"].map((pt) => (
              <div className="form-card" key={pt} style={{ paddingBottom: 8 }}>
                <div className="sec-head">Folha de Registro — Parte {pt}</div>
                <table className="hay-sheet">
                  <thead><tr><th>Variável</th><th>Escore bruto</th><th>Percentil</th><th>Classificação</th></tr></thead>
                  <tbody>
                    {scoreRows.filter((r) => r.part === pt).map((r) => (
                      <tr key={r.key}>
                        <td>{r.label}</td>
                        <td className="c">{fmtRaw(r.key, r.raw)}</td>
                        <td className="c">{r.p == null ? "—" : <span>{r.p}{r.anchor != null && <span className="anchor-ref">≈ {r.anchor}</span>}</span>}</td>
                        <td>{r.cls || <span className="muted-cell">— (norma não cadastrada)</span>}</td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            ))}

            <window.IaBloco
              report={ia} fallback={sheetAnalysis()}
              busy={iaBusy} erro={iaErro} onGerar={gerarIA}
              nota="Interpretação automática de apoio — não substitui o julgamento clínico nem as normas do teste."
            />

            {saved && (
              <div className="save-ok" style={{ marginTop: 14 }}>
                <span className="so-ic"><Icon name="clipboardCheck" size={18} stroke={2.6} /></span>
                <div><b>Avaliação salva!</b><span>Vinculada ao prontuário{patient ? " de " + patient.name : ""}.</span></div>
              </div>
            )}

            <div className="save-bar" style={{ gap: 10, display: "flex", flexWrap: "wrap" }}>
              {!saved
                ? <button className="btn btn-primary" onClick={save}><Icon name="clipboardCheck" size={18} /> Salvar no prontuário</button>
                : <button className="btn btn-primary" onClick={() => onOpenPatient && pid && onOpenPatient(pid)}><Icon name="users" size={18} /> Ver no prontuário</button>}
              <button className="btn btn-ghost" onClick={() => exportPDF({ patientName: patient ? patient.name : "", idade, escolaLabel, date, normLabel, scoreRows, analysis: analiseFinal })}><Icon name="download" size={18} /> Exportar PDF da folha</button>
            </div>
          </>
        )}

        {err && step === 1 && (
          <p className="form-err" role="alert" 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>
        )}

        {step < 4 && (
          <div className="hay-nav">
            <button className="btn btn-ghost btn-sm" onClick={prev}><Icon name="chevL" size={16} /> {step === 1 ? "Cancelar" : "Voltar"}</button>
            <button className="btn btn-primary btn-sm" onClick={next}>Continuar <Icon name="arrowRight" size={16} /></button>
          </div>
        )}
      </div>
    );
  }

  window.HaylingTest = HaylingTest;
})();
