// ─── Perfil do Paciente — Histórico e Dashboard ──────────────────────────────
const { useState: useStatePP, useEffect: useEffectPP, useRef: useRefPP, useCallback: useCallbackPP, useMemo: useMemoP } = React;

// Normaliza nome de exame (duplicado de exam-viewer para evitar dependência global)
function normNomePP(nome) {
  return (nome || '')
    .toLowerCase()
    .normalize('NFD').replace(/[̀-ͯ]/g, '')
    .replace(/[\s\-_./]+/g, ' ')
    .trim();
}

// Override de categoria por nome de exame. A IA às vezes classifica um exame na
// seção errada (ex: ureia caindo em "Bioquímica" em vez de "Função Renal").
// Aqui forçamos a categoria correta para exames específicos, independentemente
// do que veio da IA. Retorna a categoria desejada ou null (mantém a original).
const OVERRIDE_CATEGORIA = [
  ['Função Renal', ['ureia', 'ureia serica']],
  ['Bioquímica', ['acido urico', 'ac urico', 'uricemia']],
  ['Vitaminas e Hormônios', [
    'tsh', 'hormonio tireoestimulante', 'hormonio tireoestimulante ultrassensivel tsh',
    't4 livre', 't4l', 'tiroxina livre', 't4 l', 'tiroxina livre t4 livre',
    't3', 'triiodotironina', 't3 total',
    'pth', 'paratormonio', 'hormonio da paratireoide',
    'acido folico', 'ac folico', 'folato',
    'vitamina b12', 'vit b12', 'b12', 'cobalamina',
    'vitamina d', 'vit d', '25 oh vitamina d', '25 hidroxivitamina d', '25 oh vit d',
  ]],
];
function categoriaOverridePP(nomeExame) {
  const n = normNomePP(nomeExame);
  for (const [categoria, nomes] of OVERRIDE_CATEGORIA) {
    if (nomes.some(p => n === normNomePP(p))) return categoria;
  }
  return null;
}

// Extrai todos os exames com valor numérico de uma submission
// Retorna map: nomeNorm → { nome, valor (float), unidade, status, referencia }
function extraiExames(submission) {
  const map = {};
  const adiciona = (ex) => {
    if (!ex || !ex.nome || !ex.valor) return;
    const num = parseFloat(String(ex.valor).replace(',', '.'));
    if (isNaN(num)) return;
    const key = normNomePP(ex.nome);
    if (!map[key]) map[key] = { nome: ex.nome, unidade: ex.unidade || '', referencia: ex.referencia || '', status: ex.status };
    // guarda o valor numérico (usa o mais recente por nome duplicado)
    map[key].valor = num;
    map[key].valorStr = ex.valor;
  };
  for (const b of submission.lab_blocks || []) {
    for (const ex of b.exames || []) adiciona(ex);
  }
  for (const ex of submission.demais_exames || []) adiciona(ex);
  return map;
}

// Cores do tema
const PP = {
  sage:       '#5C8A6E',
  sageDark:   '#2C4A35',
  sageDeep:   '#1E3326',
  sagePale:   '#EAF2ED',
  sageMid:    '#7AAD8A',
  cream:      '#F5F0E8',
  creamDark:  '#EDE7D9',
  bg:         '#F8F5EF',
  white:      '#FFFFFF',
  text:       '#1A2B1E',
  textMid:    '#4A6355',
  textMuted:  '#8FAA96',
  border:     '#DDD8CE',
  borderLight:'#EDE9E2',
  red:        '#B84040',
  redLight:   '#FFF0F0',
  blue:       '#2A5A68',
  blueLight:  '#EBF5F8',
  amber:      '#C8973A',
  amberLight: '#FBF5E6',
};

function fmtDataCurta(iso) {
  if (!iso) return '—';
  // Data pura "YYYY-MM-DD" (data_coleta) seria lida como UTC e recuaria 1 dia em
  // fuso negativo (Recife, UTC-3); trata como data LOCAL nesse caso.
  const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(iso).trim());
  const d = m ? new Date(+m[1], +m[2] - 1, +m[3]) : new Date(iso);
  return d.toLocaleDateString('pt-BR', { day: '2-digit', month: 'short', year: 'numeric' });
}

function fmtDataNascimento(iso) {
  if (!iso) return null;
  const d = new Date(iso + 'T12:00:00');
  return d.toLocaleDateString('pt-BR', { day: '2-digit', month: 'long', year: 'numeric' });
}

// Intervalo legível entre duas datas ISO: "46 dias", "3 meses", "1 ano e 2 meses"
function intervaloLegivel(isoA, isoB) {
  if (!isoA || !isoB) return null;
  const a = new Date(isoA), b = new Date(isoB);
  if (isNaN(a) || isNaN(b)) return null;
  const dias = Math.round(Math.abs(b - a) / 86400000);
  if (dias < 45) return dias + (dias === 1 ? ' dia' : ' dias');
  const meses = Math.round(dias / 30.44);
  if (meses < 12) return meses + (meses === 1 ? ' mês' : ' meses');
  const anos = Math.floor(meses / 12), rm = meses % 12;
  let s = anos + (anos === 1 ? ' ano' : ' anos');
  if (rm > 0) s += ' e ' + rm + (rm === 1 ? ' mês' : ' meses');
  return s;
}

// ── Sparkline SVG ─────────────────────────────────────────────────────────────
const Sparkline = ({ nome, unidade, pontos, refMin, refMax }) => {
  const W = 200, H = 64, PAD = 10;
  const innerW = W - PAD * 2;
  const innerH = H - PAD * 2;

  const vals = pontos.map(p => p.valor);
  const dataMin = Math.min(...vals);
  const dataMax = Math.max(...vals);
  const span = dataMax - dataMin || 1;
  const yMin = dataMin - span * 0.2;
  const yMax = dataMax + span * 0.2;
  const ySpan = yMax - yMin || 1;

  const toX = (i) => PAD + (i / Math.max(pontos.length - 1, 1)) * innerW;
  const toY = (v) => PAD + innerH - ((v - yMin) / ySpan) * innerH;

  const pts = pontos.map((p, i) => `${toX(i)},${toY(p.valor)}`).join(' ');

  // área preenchida sob a linha
  const areaPath = pontos.length > 1
    ? `M${toX(0)},${H - PAD} ` + pontos.map((p, i) => `L${toX(i)},${toY(p.valor)}`).join(' ') + ` L${toX(pontos.length - 1)},${H - PAD} Z`
    : null;

  let refBand = null;
  if (refMin !== null && refMax !== null) {
    const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
    const ry1 = clamp(toY(refMax), PAD, H - PAD);
    const ry2 = clamp(toY(refMin), PAD, H - PAD);
    if (ry2 > ry1) refBand = <rect x={PAD} y={ry1} width={innerW} height={ry2 - ry1} fill="rgba(92,138,110,0.12)" rx="2"/>;
  }

  const ultimo = pontos[pontos.length - 1];
  const anterior = pontos.length > 1 ? pontos[pontos.length - 2] : null;
  const foraRef = ultimo?.status === 'alto' || ultimo?.status === 'baixo';
  const corLinha = foraRef ? PP.red : PP.sage;
  const tendencia = anterior ? (ultimo.valor > anterior.valor ? '↑' : ultimo.valor < anterior.valor ? '↓' : '→') : null;

  return (
    <div style={{display: 'flex', flexDirection: 'column', gap: 6}}>
      {/* linha superior: nome + valor atual */}
      <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 6}}>
        <span style={{fontSize: 12, fontWeight: 600, color: PP.textMid, lineHeight: 1.3, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'}} title={nome}>{nome}</span>
        <div style={{display: 'flex', alignItems: 'baseline', gap: 3, flexShrink: 0}}>
          {tendencia && <span style={{fontSize: 11, color: foraRef ? PP.red : PP.sageMid, fontWeight: 700}}>{tendencia}</span>}
          <span style={{fontSize: 14, fontWeight: 700, color: foraRef ? PP.red : PP.text}}>{ultimo?.valorStr || '—'}</span>
          {unidade && <span style={{fontSize: 10, color: PP.textMuted}}>{unidade}</span>}
        </div>
      </div>

      {/* gráfico */}
      <svg width="100%" viewBox={`0 0 ${W} ${H}`} style={{display: 'block', overflow: 'visible'}}>
        {refBand}
        {areaPath && (
          <path d={areaPath} fill={foraRef ? 'rgba(184,64,64,0.06)' : 'rgba(92,138,110,0.08)'}/>
        )}
        {pontos.length > 1 && (
          <polyline points={pts} fill="none" stroke={corLinha} strokeWidth="2" strokeLinejoin="round" strokeLinecap="round" opacity="0.9"/>
        )}
        {pontos.map((p, i) => {
          const cor = (p.status === 'alto' || p.status === 'baixo') ? PP.red : PP.sage;
          const isLast = i === pontos.length - 1;
          return <circle key={i} cx={toX(i)} cy={toY(p.valor)} r={isLast ? 4 : 3} fill={cor} stroke={PP.white} strokeWidth="1.5"/>;
        })}
      </svg>

      {/* datas */}
      <div style={{display: 'flex', justifyContent: 'space-between'}}>
        <span style={{fontSize: 10, color: PP.textMuted}}>{fmtDataCurta(pontos[0]?.data)}</span>
        {pontos.length > 1 && <span style={{fontSize: 10, color: PP.textMuted}}>{fmtDataCurta(pontos[pontos.length - 1]?.data)}</span>}
      </div>
    </div>
  );
};

// Parseia "valor mín - valor máx" ou "até X" ou "X a Y" → { min, max } ou null
function parseRef(ref) {
  if (!ref) return { min: null, max: null };
  const clean = ref.replace(',', '.');
  // "X - Y" ou "X a Y"
  const m = clean.match(/([\d.]+)\s*[-a]\s*([\d.]+)/i);
  if (m) return { min: parseFloat(m[1]), max: parseFloat(m[2]) };
  // "até X" ou "< X"
  const ate = clean.match(/(?:até|<)\s*([\d.]+)/i);
  if (ate) return { min: null, max: parseFloat(ate[1]) };
  // "≥ X" ou "> X"
  const maior = clean.match(/(?:≥|>)\s*([\d.]+)/i);
  if (maior) return { min: parseFloat(maior[1]), max: null };
  return { min: null, max: null };
}

// Comentário local instantâneo (sem API): compara último vs penúltimo + posição vs referência
function comentarioLocal(ex) {
  const p = ex.pontos;
  if (!p || p.length === 0) return { cls: 'neutral', ico: '→', txt: '' };
  const last = p[p.length - 1];
  const un = ex.unidade ? ` ${ex.unidade}` : ''; // unidade com espaço só quando existe
  if (p.length === 1) {
    const fora = last.status === 'alto' || last.status === 'baixo';
    return {
      cls: fora ? 'bad' : 'good',
      ico: fora ? '⚠' : '✓',
      txt: `Primeiro registro: ${last.valorStr}${un}${ex.referencia ? ` (ref: ${ex.referencia})` : ''}.`,
    };
  }
  const prev = p[p.length - 2];
  const pct = prev.valor === 0 ? 0 : Math.round(((last.valor - prev.valor) / prev.valor) * 100);
  const intervalo = intervaloLegivel(prev.data, last.data) || 'período desconhecido';
  const dir = last.valor > prev.valor ? 'subiu' : last.valor < prev.valor ? 'caiu' : 'manteve-se';
  const { min, max } = parseRef(ex.referencia);
  const fora = last.status === 'alto' || last.status === 'baixo';
  const ref = ex.referencia || '—';
  const pctStr = (pct > 0 ? '+' : '') + pct + '%';

  // aproximou/afastou da faixa — considera qual limite está violado
  let aprox = false, afast = false;
  if (last.status === 'alto' && max != null) {       // acima do teto: descer aproxima
    if (last.valor < prev.valor) aprox = true;
    if (last.valor > prev.valor) afast = true;
  } else if (last.status === 'baixo' && min != null) { // abaixo do piso: subir aproxima
    if (last.valor > prev.valor) aprox = true;
    if (last.valor < prev.valor) afast = true;
  } else if (max != null) {                            // fallback: trata como teto
    if (last.valor < prev.valor) aprox = true;
    if (last.valor > prev.valor) afast = true;
  } else if (min != null) {                            // fallback: trata como piso
    if (last.valor > prev.valor) aprox = true;
    if (last.valor < prev.valor) afast = true;
  }

  if (fora) {
    if (aprox) return { cls: 'good', ico: '✓', txt: `Valor ${dir} de ${prev.valorStr} para ${last.valorStr}${un} (${pctStr}) em ${intervalo} — movendo na direção da referência (${ref}), mas ainda fora dela.` };
    if (afast) return { cls: 'bad', ico: '⚠', txt: `Valor ${dir} de ${prev.valorStr} para ${last.valorStr}${un} (${pctStr}) em ${intervalo} — afastando-se ainda mais da referência (${ref}). Acompanhar.` };
    return { cls: 'neutral', ico: '→', txt: `Valor estável em ${last.valorStr}${un} nos últimos ${intervalo}, porém fora da referência (${ref}).` };
  }
  if (dir === 'manteve-se') return { cls: 'good', ico: '✓', txt: `Valor estável em ${last.valorStr}${un} nos últimos ${intervalo}, dentro da referência (${ref}).` };
  return { cls: 'good', ico: '✓', txt: `Valor ${dir} de ${prev.valorStr} para ${last.valorStr}${un} (${pctStr}) em ${intervalo}, permanecendo dentro da referência (${ref}).` };
}

// ── SparklineGrid ─────────────────────────────────────────────────────────────
const SparklineGrid = ({ submissions }) => {
  const ordenadas = [...submissions].sort((a, b) => new Date(a.submitted_at) - new Date(b.submitted_at));

  // Monta grupos mantendo a ordem dos blocos do lab
  // grupoMap: nomeGrupo → { exames: { [nomeNorm]: { nome, unidade, referencia, pontos[] } } }
  const grupoMap = {};
  const grupoOrder = []; // preserva ordem de aparição

  for (const sub of ordenadas) {
    // lab_blocks têm nome de grupo
    for (const block of sub.lab_blocks || []) {
      const grupoBloco = block.categoria || block.nome || 'Outros';
      for (const ex of block.exames || []) {
        if (!ex || !ex.nome || !ex.valor) continue;
        const num = parseFloat(String(ex.valor).replace(',', '.'));
        if (isNaN(num)) continue;
        const grupo = categoriaOverridePP(ex.nome) || grupoBloco;
        if (!grupoMap[grupo]) { grupoMap[grupo] = {}; grupoOrder.push(grupo); }
        const key = normNomePP(ex.nome);
        if (!grupoMap[grupo][key]) grupoMap[grupo][key] = { nome: ex.nome, unidade: ex.unidade || '', referencia: ex.referencia || '', pontos: [] };
        grupoMap[grupo][key].pontos.push({ data: sub.submitted_at, valor: num, valorStr: ex.valor, status: ex.status });
      }
    }
    // demais_exames sem grupo
    for (const ex of sub.demais_exames || []) {
      if (!ex || !ex.nome || !ex.valor) continue;
      const num = parseFloat(String(ex.valor).replace(',', '.'));
      if (isNaN(num)) continue;
      const grupo = categoriaOverridePP(ex.nome) || 'Outros Exames';
      if (!grupoMap[grupo]) { grupoMap[grupo] = {}; grupoOrder.push(grupo); }
      const key = normNomePP(ex.nome);
      if (!grupoMap[grupo][key]) grupoMap[grupo][key] = { nome: ex.nome, unidade: ex.unidade || '', referencia: ex.referencia || '', pontos: [] };
      grupoMap[grupo][key].pontos.push({ data: sub.submitted_at, valor: num, valorStr: ex.valor, status: ex.status });
    }
  }

  // Filtra: só exames com ≥ 2 pontos (tendência visível)
  const grupos = grupoOrder
    .map(nome => ({
      nome,
      series: Object.values(grupoMap[nome]).filter(s => s.pontos.length >= 2),
    }))
    .filter(g => g.series.length > 0);

  const totalSeries = grupos.reduce((acc, g) => acc + g.series.length, 0);

  if (totalSeries === 0) {
    return (
      <div style={{padding: '24px', color: PP.textMuted, fontSize: 13, textAlign: 'center', background: PP.white, borderRadius: 14, border: `1.5px solid ${PP.borderLight}`}}>
        São necessários pelo menos 2 envios de exames para exibir tendências.
      </div>
    );
  }

  return (
    <div style={{display: 'flex', flexDirection: 'column', gap: 20}}>
      {grupos.map(grupo => {
        const temAlterado = grupo.series.some(s => {
          const ult = s.pontos[s.pontos.length - 1];
          return ult?.status === 'alto' || ult?.status === 'baixo';
        });
        return (
          <div key={grupo.nome} style={{background: PP.white, border: `1.5px solid ${PP.borderLight}`, borderRadius: 16, overflow: 'hidden', boxShadow: '0 1px 4px rgba(30,51,38,0.04)'}}>
            {/* cabeçalho do grupo */}
            <div style={{display: 'flex', alignItems: 'center', gap: 10, padding: '14px 18px', borderBottom: `1px solid ${PP.borderLight}`, background: PP.sagePale}}>
              <span style={{fontSize: 13, fontWeight: 700, color: PP.sageDark, letterSpacing: '0.01em'}}>{grupo.nome}</span>
              <span style={{fontSize: 11, color: PP.textMuted, background: PP.white, borderRadius: 20, padding: '1px 8px', border: `1px solid ${PP.border}`}}>
                {grupo.series.length} exame{grupo.series.length !== 1 ? 's' : ''}
              </span>
              {temAlterado && (
                <span style={{marginLeft: 'auto', fontSize: 11, fontWeight: 600, color: PP.red, background: PP.redLight, borderRadius: 20, padding: '2px 10px', border: `1px solid #F5C5C5`}}>
                  ⚠ Valor alterado
                </span>
              )}
            </div>
            {/* grid de sparklines */}
            <div style={{display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '1px', background: PP.borderLight}}>
              {grupo.series.map(s => {
                const { min, max } = parseRef(s.referencia);
                const ult = s.pontos[s.pontos.length - 1];
                const foraRef = ult?.status === 'alto' || ult?.status === 'baixo';
                return (
                  <div key={s.nome} style={{background: foraRef ? PP.redLight : PP.white, padding: '16px 16px 12px'}}>
                    <Sparkline nome={s.nome} unidade={s.unidade} pontos={s.pontos} refMin={min} refMax={max}/>
                    {s.referencia && (
                      <div style={{marginTop: 4, fontSize: 10, color: PP.textMuted}}>Ref: {s.referencia}</div>
                    )}
                  </div>
                );
              })}
            </div>
          </div>
        );
      })}
    </div>
  );
};

// ── TrendComment ──────────────────────────────────────────────────────────────
const TrendComment = ({ submissions }) => {
  const [estado, setEstado] = useStatePP('idle'); // idle | loading | done | erro
  const [resultado, setResultado] = useStatePP(null);

  const gerar = async () => {
    setEstado('loading');
    try {
      const r = await window.API.patients.trend(submissions);
      setResultado(r);
      setEstado('done');
    } catch (err) {
      console.error('[TrendComment]', err.message);
      setEstado('erro');
    }
  };

  const corDestaque = (d) => d === 'melhora' ? PP.sage : d === 'piora' ? PP.red : PP.amber;
  const labelDestaque = (d) => d === 'melhora' ? '↓ Melhora' : d === 'piora' ? '↑ Piora' : '→ Estável';

  return (
    <div style={{background: PP.sagePale, border: `1.5px solid #B8D9C4`, borderRadius: 14, padding: '16px 18px', display: 'flex', flexDirection: 'column', gap: 12}}>
      <div style={{display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12}}>
        <div style={{display: 'flex', alignItems: 'center', gap: 8}}>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={PP.sageDark} strokeWidth="2"><path d="M12 2a10 10 0 1 0 0 20A10 10 0 0 0 12 2z"/><path d="M12 8v4l3 3"/></svg>
          <span style={{fontSize: 13, fontWeight: 700, color: PP.sageDark}}>Análise de Tendência</span>
          <span style={{fontSize: 11, color: PP.textMid, background: PP.white, borderRadius: 20, padding: '1px 8px', border: `1px solid ${PP.border}`}}>IA</span>
        </div>
        {estado === 'idle' && (
          <button onClick={gerar}
            style={{padding: '6px 14px', borderRadius: 8, border: 'none', background: PP.sageDark, color: PP.white, fontSize: 12, fontWeight: 600, cursor: 'pointer', flexShrink: 0}}>
            Gerar análise
          </button>
        )}
        {estado === 'loading' && (
          <div style={{display: 'flex', alignItems: 'center', gap: 6, color: PP.textMid, fontSize: 12}}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={PP.sage} strokeWidth="2.5" style={{animation: 'ppSpin 1s linear infinite'}}><path d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0"/></svg>
            Analisando...
          </div>
        )}
        {(estado === 'done' || estado === 'erro') && (
          <button onClick={() => { setEstado('idle'); setResultado(null); }}
            style={{padding: '4px 10px', borderRadius: 7, border: `1px solid ${PP.border}`, background: PP.white, color: PP.textMid, fontSize: 11, cursor: 'pointer'}}>
            Gerar novamente
          </button>
        )}
      </div>

      {estado === 'idle' && (
        <p style={{margin: 0, fontSize: 12, color: PP.textMid, lineHeight: 1.6}}>
          Clique em "Gerar análise" para que a IA compare os exames ao longo do tempo e identifique tendências relevantes.
        </p>
      )}

      {estado === 'erro' && (
        <p style={{margin: 0, fontSize: 12, color: PP.red}}>Não foi possível gerar a análise. Tente novamente.</p>
      )}

      {estado === 'done' && resultado && (
        <div style={{display: 'flex', flexDirection: 'column', gap: 12}}>
          <p style={{margin: 0, fontSize: 13, color: PP.text, lineHeight: 1.7, whiteSpace: 'pre-line'}}>{resultado.comentario}</p>
          {resultado.destaques && resultado.destaques.length > 0 && (
            <div style={{display: 'flex', flexWrap: 'wrap', gap: 8}}>
              {resultado.destaques.map((d, i) => (
                <div key={i} style={{display: 'flex', alignItems: 'center', gap: 6, background: PP.white, border: `1.5px solid ${PP.border}`, borderRadius: 20, padding: '4px 12px'}}>
                  <span style={{fontSize: 11, fontWeight: 700, color: corDestaque(d.direcao)}}>{labelDestaque(d.direcao)}</span>
                  <span style={{fontSize: 11, color: PP.textMid}}>{d.nome}</span>
                  {d.valores && d.valores.length > 0 && (
                    <span style={{fontSize: 10, color: PP.textMuted}}>{d.valores.join(' → ')}</span>
                  )}
                </div>
              ))}
            </div>
          )}
        </div>
      )}
    </div>
  );
};

// ── SubmissionItem ─────────────────────────────────────────────────────────────
const SubmissionItem = ({ sub, onOpen, onDelete }) => {
  const [excluindo, setExcluindo] = useStatePP(false);
  const [confirmando, setConfirmando] = useStatePP(false);
  const [erroExcluir, setErroExcluir] = useStatePP(false);
  const statusMap = {
    novo:      { label: 'Novo',      color: PP.amber,   bg: PP.amberLight, border: '#E8D5A0' },
    lido:      { label: 'Lido',      color: PP.sage,    bg: PP.sagePale,   border: '#B8D9C4' },
    revisado:  { label: 'Revisado',  color: PP.sageDark,bg: PP.sagePale,   border: '#B8D9C4' },
    erro:      { label: 'Erro',      color: PP.red,     bg: PP.redLight,   border: '#F5C5C5' },
  };
  const st = statusMap[sub.status] || statusMap.lido;
  const hasAI = (sub.lab_blocks || []).length > 0;
  const nFiles = (sub.files || []).length;

  const handleExcluir = (e) => {
    e.stopPropagation();
    if (excluindo) return;
    setErroExcluir(false);
    setConfirmando(true);
  };

  const confirmarExclusao = async () => {
    if (excluindo) return;
    setErroExcluir(false);
    setExcluindo(true);
    try {
      await window.API.submissions.remove(sub.id);
      onDelete && onDelete(sub.id);
    } catch (err) {
      console.error('[SubmissionItem] erro ao excluir', err.message);
      setErroExcluir(true);
      setExcluindo(false);
    }
  };

  return (
    <>
    <div onClick={() => onOpen(sub)}
      role="button" tabIndex={0}
      onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onOpen(sub); } }}
      style={{display: 'flex', alignItems: 'center', gap: 14, padding: '12px 16px', background: PP.white, borderRadius: 12, border: `1.5px solid ${PP.borderLight}`, cursor: 'pointer', textAlign: 'left', width: '100%', fontFamily: 'inherit', transition: 'all 0.15s', boxSizing: 'border-box', opacity: excluindo ? 0.5 : 1}}
      onMouseEnter={e => { e.currentTarget.style.borderColor = PP.sage; e.currentTarget.style.boxShadow = `0 3px 14px rgba(92,138,110,0.12)`; }}
      onMouseLeave={e => { e.currentTarget.style.borderColor = PP.borderLight; e.currentTarget.style.boxShadow = 'none'; }}>

      {/* Ícone calendário */}
      <div style={{width: 40, height: 40, borderRadius: 10, background: PP.sagePale, border: `1.5px solid #B8D9C4`, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0}}>
        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={PP.sage} strokeWidth="2"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
      </div>

      <div style={{flex: 1, minWidth: 0}}>
        <div style={{display: 'flex', alignItems: 'center', gap: 8, marginBottom: 3, flexWrap: 'wrap'}}>
          <span style={{fontSize: 13, fontWeight: 600, color: PP.text}}>{fmtDataCurta(sub.submitted_at)}</span>
          <span style={{fontSize: 11, fontWeight: 600, color: st.color, background: st.bg, border: `1px solid ${st.border}`, borderRadius: 20, padding: '1px 9px'}}>{st.label}</span>
          {hasAI && <span style={{fontSize: 11, color: PP.sageDark, background: PP.sagePale, borderRadius: 20, padding: '1px 8px', border: `1px solid #B8D9C4`}}>IA pronta</span>}
        </div>
        <div style={{fontSize: 12, color: PP.textMuted}}>{nFiles} arquivo{nFiles !== 1 ? 's' : ''}</div>
      </div>

      {/* Botão excluir */}
      <button type="button" onClick={handleExcluir} disabled={excluindo}
        title="Excluir carregamento"
        aria-label="Excluir carregamento"
        style={{display: 'flex', alignItems: 'center', justifyContent: 'center', width: 32, height: 32, borderRadius: 8, border: `1.5px solid ${PP.borderLight}`, background: PP.white, color: PP.textMuted, cursor: excluindo ? 'default' : 'pointer', flexShrink: 0, fontFamily: 'inherit', transition: 'all 0.15s'}}
        onMouseEnter={e => { if (excluindo) return; e.currentTarget.style.color = PP.red; e.currentTarget.style.borderColor = '#F5C5C5'; e.currentTarget.style.background = PP.redLight; }}
        onMouseLeave={e => { e.currentTarget.style.color = PP.textMuted; e.currentTarget.style.borderColor = PP.borderLight; e.currentTarget.style.background = PP.white; }}>
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>
      </button>

      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={PP.textMuted} strokeWidth="2" style={{flexShrink: 0}}><polyline points="9 18 15 12 9 6"/></svg>
    </div>

    {confirmando && (
      <div
        onClick={e => { e.stopPropagation(); if (e.target === e.currentTarget && !excluindo) setConfirmando(false); }}
        style={{position:'fixed',inset:0,background:'rgba(30,51,38,0.45)',backdropFilter:'blur(3px)',display:'flex',alignItems:'center',justifyContent:'center',zIndex:300,padding:16,animation:'ppFadeIn 0.15s ease'}}
        role="dialog" aria-modal="true" aria-labelledby="excluir-titulo">
        <style>{`@keyframes ppFadeIn{from{opacity:0}to{opacity:1}}@keyframes ppPop{from{opacity:0;transform:scale(0.96) translateY(6px)}to{opacity:1;transform:scale(1) translateY(0)}}`}</style>
        <div onClick={e => e.stopPropagation()}
          style={{background:PP.white,borderRadius:18,boxShadow:'0 24px 64px rgba(30,51,38,0.28)',width:'100%',maxWidth:380,padding:'24px 22px 20px',border:`1px solid ${PP.borderLight}`,animation:'ppPop 0.18s cubic-bezier(0.34,1.3,0.64,1)'}}>

          {/* Ícone de alerta */}
          <div style={{width:46,height:46,borderRadius:'50%',background:PP.redLight,border:'1.5px solid #F5C5C5',display:'flex',alignItems:'center',justifyContent:'center',marginBottom:14}}>
            <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke={PP.red} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
          </div>

          <h3 id="excluir-titulo" style={{margin:'0 0 8px',fontSize:17,fontWeight:700,color:PP.text,fontFamily:'Lora, serif'}}>
            Excluir este carregamento?
          </h3>
          <p style={{margin:'0 0 4px',fontSize:13,color:PP.textMid,lineHeight:1.6}}>
            Envio de <strong style={{color:PP.text}}>{fmtDataCurta(sub.submitted_at)}</strong> · {nFiles} arquivo{nFiles !== 1 ? 's' : ''}
          </p>
          <p style={{margin:'0 0 18px',fontSize:13,color:PP.textMuted,lineHeight:1.6}}>
            Os arquivos e os dados de exames serão apagados permanentemente. Esta ação não pode ser desfeita.
          </p>

          {erroExcluir && (
            <div style={{display:'flex',alignItems:'center',gap:8,background:PP.redLight,border:'1px solid #F5C5C5',borderRadius:10,padding:'9px 12px',marginBottom:14,fontSize:12.5,color:PP.red,lineHeight:1.5}}>
              <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{flexShrink:0}}><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
              Não foi possível excluir. Tente novamente.
            </div>
          )}

          <div style={{display:'flex',gap:10}}>
            <button type="button" onClick={() => setConfirmando(false)} disabled={excluindo}
              style={{flex:1,padding:'11px',borderRadius:11,border:`1.5px solid ${PP.border}`,background:PP.white,color:PP.textMid,fontSize:13.5,fontWeight:600,cursor:excluindo?'default':'pointer',fontFamily:'inherit',opacity:excluindo?0.5:1,transition:'all 0.15s'}}
              onMouseEnter={e => { if (excluindo) return; e.currentTarget.style.background = PP.borderLight; }}
              onMouseLeave={e => { e.currentTarget.style.background = PP.white; }}>
              Cancelar
            </button>
            <button type="button" onClick={confirmarExclusao} disabled={excluindo}
              style={{flex:1,padding:'11px',borderRadius:11,border:'none',background:PP.red,color:PP.white,fontSize:13.5,fontWeight:700,cursor:excluindo?'wait':'pointer',fontFamily:'inherit',display:'flex',alignItems:'center',justifyContent:'center',gap:7,opacity:excluindo?0.85:1,transition:'all 0.15s'}}
              onMouseEnter={e => { if (excluindo) return; e.currentTarget.style.background = '#9E3636'; }}
              onMouseLeave={e => { e.currentTarget.style.background = PP.red; }}>
              {excluindo ? (
                <>
                  <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" style={{animation:'ppSpin 0.7s linear infinite'}}><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>
                  Excluindo…
                </>
              ) : 'Excluir'}
            </button>
          </div>
          <style>{`@keyframes ppSpin{to{transform:rotate(360deg)}}`}</style>
        </div>
      </div>
    )}
    </>
  );
};

// ── ExamDashboardModal ────────────────────────────────────────────────────────
// Botão de análise IA para um único exame (reusa endpoint de trend)
const ExamAIButton = ({ nome, unidade, referencia, pontos }) => {
  const [estado, setEstado] = useStatePP('idle');
  const [texto, setTexto] = useStatePP('');
  const gerar = async () => {
    setEstado('loading');
    try {
      const subs = (pontos || []).map(pt => ({
        submitted_at: pt.data,
        lab_blocks: [{ nome: 'Exame', exames: [{ nome, valor: pt.valorStr, unidade, referencia, status: pt.status }] }],
        demais_exames: [],
      }));
      const r = await window.API.patients.trend(subs);
      setTexto(r.comentario || 'Sem análise disponível.');
      setEstado('done');
    } catch (err) {
      console.error('[ExamAIButton]', err.message);
      setEstado('erro');
    }
  };
  if (estado === 'done') {
    return (
      <div style={{background:'linear-gradient(135deg,#F0F7F2,#EAF2ED)', border:'1.5px solid #B8D9C4', borderRadius:13, padding:'14px 16px', fontSize:13, lineHeight:1.65, color:PP.text}}>
        <div style={{fontSize:10, fontWeight:700, color:PP.sage, textTransform:'uppercase', letterSpacing:'0.07em', marginBottom:6}}>✨ Análise gerada por IA</div>
        <span style={{whiteSpace:'pre-line'}}>{texto}</span>
      </div>
    );
  }
  return (
    <button onClick={gerar} disabled={estado === 'loading'}
      style={{alignSelf:'flex-start', display:'inline-flex', alignItems:'center', gap:7, padding:'8px 15px', borderRadius:10, border:'1.5px solid #B8D9C4', background:PP.sagePale, color:PP.sageDark, fontSize:12.5, fontWeight:600, cursor:'pointer', fontFamily:'inherit', opacity: estado==='loading'?0.6:1}}>
      {estado === 'loading' ? 'Analisando…' : estado === 'erro' ? 'Erro — tentar novamente' : '✨ Análise IA detalhada'}
    </button>
  );
};
const ExamChartBlock = ({ exame }) => {
  const { nome, unidade, referencia, pontos } = exame;
  const [mostrarFaixa, setMostrarFaixa] = useStatePP(false);
  const [tooltipIdx, setTooltipIdx]     = useStatePP(null);

  if (!pontos || pontos.length === 0) return null;

  const fmtDt = (iso) => {
    if (!iso) return '—';
    // "YYYY-MM-DD" (data_coleta) como data LOCAL p/ não recuar 1 dia em UTC-3.
    const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(iso).trim());
    const d = m ? new Date(+m[1], +m[2] - 1, +m[3]) : new Date(iso);
    return `${String(d.getDate()).padStart(2,'0')}/${String(d.getMonth()+1).padStart(2,'0')}/${String(d.getFullYear()).slice(2)}`;
  };

  const ultimo  = pontos[pontos.length - 1];
  const foraRef = ultimo.status === 'alto' || ultimo.status === 'baixo';
  const corLinha = foraRef ? PP.red : PP.sage;

  const W = 320, H = 120, PAD_X = 20, PAD_TOP = 26, PAD_BOT = 22;
  const innerW = W - PAD_X * 2;
  const innerH = H - PAD_TOP - PAD_BOT;
  const vals  = pontos.map(p => p.valor);
  const dMin  = Math.min(...vals);
  const dMax  = Math.max(...vals);
  const span  = dMax - dMin || 1;
  const yMin  = dMin - span * 0.28;
  const yMax  = dMax + span * 0.28;
  const ySpan = yMax - yMin || 1;
  const toX = (i) => PAD_X + (i / Math.max(pontos.length - 1, 1)) * innerW;
  const toY = (v) => PAD_TOP + innerH - ((v - yMin) / ySpan) * innerH;

  const pts = pontos.map((p, i) => `${toX(i)},${toY(p.valor)}`).join(' ');
  const areaPath = pontos.length > 1
    ? `M${toX(0)},${PAD_TOP + innerH} ` + pontos.map((p, i) => `L${toX(i)},${toY(p.valor)}`).join(' ') + ` L${toX(pontos.length - 1)},${PAD_TOP + innerH} Z`
    : null;

  const { min: refMin, max: refMax } = parseRef(referencia);
  let refBand = null;
  if (mostrarFaixa && refMin !== null && refMax !== null) {
    const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
    const ry1 = clamp(toY(refMax), PAD_TOP, PAD_TOP + innerH);
    const ry2 = clamp(toY(refMin), PAD_TOP, PAD_TOP + innerH);
    if (ry2 > ry1) refBand = <rect x={PAD_X} y={ry1} width={innerW} height={ry2 - ry1} fill="rgba(92,138,110,0.15)" rx="2"/>;
  }

  const statusBadge = (st) => {
    if (st === 'alto')  return { label: '↑ Alto',  bg: PP.redLight, color: PP.red,      border: '#F5C5C5' };
    if (st === 'baixo') return { label: '↓ Baixo', bg: '#EBF5F8',  color: '#2A5A68',   border: '#B0D4DC' };
    return                     { label: 'Normal',  bg: PP.sagePale, color: PP.sageDark,  border: '#B8D9C4' };
  };

  const renderTooltip = (p, i) => {
    if (tooltipIdx !== i) return null;
    const b = statusBadge(p.status);
    const c = comentarioLocal({ nome, unidade, referencia, pontos: [p] });
    const cx = toX(i);
    const pct = cx / W;
    const isAlerta = p.status === 'alto' || p.status === 'baixo';
    return (
      <div key={i} style={{
        position:'absolute', bottom:'100%',
        left:`${Math.min(Math.max(pct * 100, 10), 70)}%`,
        transform:'translateX(-40%)', marginBottom:8,
        background:PP.white, border:`1.5px solid ${isAlerta?'#F5C5C5':'#B8D9C4'}`,
        borderRadius:11, padding:'10px 13px', width:200,
        boxShadow:'0 8px 24px rgba(30,51,38,0.13)', zIndex:10, pointerEvents:'none',
      }}>
        <div style={{display:'flex',alignItems:'center',gap:7,marginBottom:5}}>
          <span style={{fontSize:10,fontWeight:700,padding:'2px 8px',borderRadius:6,background:b.bg,color:b.color,border:`1px solid ${b.border}`,whiteSpace:'nowrap'}}>{b.label}</span>
          <span style={{fontSize:14,fontWeight:700,color:isAlerta?PP.red:PP.text}}>{p.valorStr}{unidade&&<span style={{fontSize:10,color:PP.textMuted,fontWeight:400,marginLeft:2}}>{unidade}</span>}</span>
        </div>
        {c.txt && <div style={{fontSize:11,color:PP.textMid,lineHeight:1.45,marginBottom:4}}>{c.txt}</div>}
        <div style={{fontSize:10,color:PP.textMuted}}>{fmtDt(p.data)}</div>
      </div>
    );
  };

  const bgBlock   = foraRef ? '#FFF8F8' : PP.bg;
  const nomeLabel = foraRef
    ? `${nome}${ultimo.status === 'alto' ? ' ↑' : ' ↓'}`
    : nome;
  const corNome   = foraRef ? PP.red : PP.sageDark;

  return (
    <div style={{background:bgBlock,borderRadius:14,padding:'14px 16px 10px',border:`1px solid ${PP.borderLight}`,position:'relative'}}>
      <div style={{display:'flex',alignItems:'flex-start',justifyContent:'space-between',marginBottom:8}}>
        <div>
          <div style={{fontSize:13,fontWeight:700,color:corNome}}>{nomeLabel}</div>
          <div style={{fontSize:11,color:PP.textMuted,marginTop:1}}>
            Evolução no período{unidade ? ` · ${unidade}` : ''}
          </div>
        </div>
        {(refMin !== null || refMax !== null) && (
          <label style={{display:'flex',alignItems:'center',gap:5,fontSize:11,color:PP.textMuted,cursor:'pointer',userSelect:'none',marginTop:2}}>
            <input type="checkbox" checked={mostrarFaixa} onChange={e => setMostrarFaixa(e.target.checked)}
              style={{accentColor:PP.sage,width:13,height:13}}/>
            Faixa de referência
          </label>
        )}
      </div>

      <div style={{position:'relative'}}>
        <svg width="100%" viewBox={`0 0 ${W} ${H}`} style={{display:'block',overflow:'visible'}}>
          {refBand}
          {areaPath && <path d={areaPath} fill={foraRef?'rgba(184,64,64,0.07)':'rgba(92,138,110,0.09)'}/>}
          {pontos.length > 1 && <polyline points={pts} fill="none" stroke={corLinha} strokeWidth="2.5" strokeLinejoin="round" strokeLinecap="round"/>}
          {pontos.map((p, i) => {
            const cor = (p.status==='alto'||p.status==='baixo') ? PP.red : PP.sage;
            const cx = toX(i); const cy = toY(p.valor);
            const isLast = i === pontos.length - 1;
            return (
              <g key={i} onMouseEnter={() => setTooltipIdx(i)} onMouseLeave={() => setTooltipIdx(null)} style={{cursor:'pointer'}}>
                {tooltipIdx === i && <circle cx={cx} cy={cy} r={10} fill={cor} opacity={0.15}/>}
                <circle cx={cx} cy={cy} r={isLast ? 5.5 : 4.5} fill={cor} stroke={PP.white} strokeWidth="1.5"/>
                <text x={cx} y={cy - 10} fontSize="9.5" fill={cor} textAnchor="middle" fontWeight="700">{p.valorStr}</text>
              </g>
            );
          })}
          {pontos.map((p, i) => (
            <text key={i} x={toX(i)} y={H} fontSize="9" fill={PP.textMuted} textAnchor="middle">{fmtDt(p.data)}</text>
          ))}
        </svg>
        {pontos.map((p, i) => renderTooltip(p, i))}
      </div>
    </div>
  );
};
const ExamDashboardModal = ({ exames, examesDisponiveis, onClose, onRemove, onAdd }) => {
  const [addAberto, setAddAberto] = useStatePP(false);

  useEffectPP(() => {
    const handler = (e) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', handler);
    return () => document.removeEventListener('keydown', handler);
  }, [onClose]);

  if (!exames || exames.length === 0) return null;

  const nomesSelecionados = exames.map(e => e.nome);
  const disponiveis = (examesDisponiveis || []).filter(e => !nomesSelecionados.includes(e.nome));

  return (
    <div
      onClick={onClose}
      style={{position:'fixed',inset:0,background:'rgba(30,51,38,0.45)',backdropFilter:'blur(2px)',display:'flex',alignItems:'flex-start',justifyContent:'center',zIndex:100,padding:'16px',paddingTop:'env(safe-area-inset-top, 16px)',overflowY:'auto'}}>
      <div
        onClick={e => e.stopPropagation()}
        style={{background:PP.white,borderRadius:20,boxShadow:'0 24px 64px rgba(30,51,38,0.22)',width:'100%',maxWidth:480,overflowY:'auto',display:'flex',flexDirection:'column',border:`1px solid ${PP.borderLight}`,marginTop:'auto',marginBottom:'auto',flexShrink:0}}>

        {/* Topo: tabs + botão fechar */}
        <div style={{display:'flex',alignItems:'flex-start',justifyContent:'space-between',padding:'14px 16px 0',gap:8,flexShrink:0}}>
          <div style={{display:'flex',flexWrap:'wrap',gap:6,flex:1}}>
            {exames.map(ex => {
              const ultimo = ex.pontos?.[ex.pontos.length - 1];
              const foraRef = ultimo?.status === 'alto' || ultimo?.status === 'baixo';
              return (
                <div key={ex.nome} style={{
                  display:'inline-flex', alignItems:'center', gap:5,
                  padding:'4px 8px 4px 11px', borderRadius:20,
                  fontSize:12, fontWeight:600, cursor:'default',
                  border:`1.5px solid ${foraRef ? '#F3C9C9' : PP.sageDark}`,
                  background: foraRef ? PP.redLight : PP.sageDark,
                  color: foraRef ? PP.red : PP.white,
                }}>
                  {ex.nome}{foraRef ? (ultimo.status === 'alto' ? ' ↑' : ' ↓') : ''}
                  <button
                    onClick={() => onRemove(ex.nome)}
                    style={{background:'none',border:'none',cursor:'pointer',color:'inherit',opacity:0.7,fontSize:13,lineHeight:1,padding:'0 0 0 2px',fontFamily:'inherit'}}>
                    ✕
                  </button>
                </div>
              );
            })}

            {/* Botão + exame com dropdown */}
            {disponiveis.length > 0 && (
              <div style={{position: 'relative'}}>
                <button onClick={() => setAddAberto(v => !v)}
                  style={{
                    display: 'inline-flex', alignItems: 'center', gap: 4,
                    padding: '4px 11px', borderRadius: 20, fontSize: 12, fontWeight: 600,
                    border: `1.5px dashed ${PP.sage}`, background: 'none', color: PP.sageDark,
                    cursor: 'pointer', fontFamily: 'inherit',
                  }}>
                  + exame
                </button>
                {addAberto && (
                  <div style={{
                    position: 'absolute', top: '100%', left: 0, marginTop: 6, zIndex: 20,
                    background: PP.white, border: `1.5px solid ${PP.borderLight}`, borderRadius: 12,
                    boxShadow: '0 8px 24px rgba(30,51,38,0.16)', padding: 6, minWidth: 200, maxHeight: 260, overflowY: 'auto',
                  }}>
                    {disponiveis.map(ex => (
                      <button key={ex.nome}
                        onClick={() => { onAdd(ex); setAddAberto(false); }}
                        style={{
                          display: 'block', width: '100%', textAlign: 'left',
                          padding: '8px 10px', borderRadius: 8, border: 'none', background: 'none',
                          fontSize: 12.5, color: PP.text, cursor: 'pointer', fontFamily: 'inherit',
                        }}
                        onMouseEnter={e => e.currentTarget.style.background = PP.sagePale}
                        onMouseLeave={e => e.currentTarget.style.background = 'none'}>
                        {ex.nome}
                      </button>
                    ))}
                  </div>
                )}
              </div>
            )}
          </div>
          <button onClick={onClose}
            style={{width:30,height:30,borderRadius:'50%',border:'none',background:PP.creamDark,color:PP.textMuted,cursor:'pointer',display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0,fontSize:18,lineHeight:1,padding:0,marginTop:2}}>
            ×
          </button>
        </div>

        {/* Gráficos empilhados */}
        <div style={{padding:'14px 16px 18px',display:'flex',flexDirection:'column',gap:14}}>
          {exames.map(ex => (
            <ExamChartBlock key={ex.nome} exame={ex} />
          ))}
        </div>

      </div>
    </div>
  );
};
// ── ExamDashboardInline — versão sem overlay, para uso no fluxo da página ──────
const ExamDashboardInline = ({ nome, unidade, referencia, pontos, onClose }) => {
  if (!pontos || pontos.length === 0) return null;

  const ultimo   = pontos[pontos.length - 1];
  const anterior = pontos.length > 1 ? pontos[pontos.length - 2] : null;

  const tendencia = (() => {
    if (!anterior) return { label: '—', cor: PP.textMuted };
    if (ultimo.valor > anterior.valor) return { label: '↑ Subiu',   cor: PP.red };
    if (ultimo.valor < anterior.valor) return { label: '↓ Desceu',  cor: PP.sage };
    return { label: '→ Estável', cor: PP.amber };
  })();

  const W = 320, H = 100, PAD = 14;
  const innerW = W - PAD * 2;
  const innerH = H - PAD * 2;
  const vals  = pontos.map(p => p.valor);
  const dMin  = Math.min(...vals);
  const dMax  = Math.max(...vals);
  const span  = dMax - dMin || 1;
  const yMin  = dMin - span * 0.2;
  const yMax  = dMax + span * 0.2;
  const ySpan = yMax - yMin || 1;
  const toX = (i) => PAD + (i / Math.max(pontos.length - 1, 1)) * innerW;
  const toY = (v) => PAD + innerH - ((v - yMin) / ySpan) * innerH;

  const pts     = pontos.map((p, i) => `${toX(i)},${toY(p.valor)}`).join(' ');
  const areaPath = pontos.length > 1
    ? `M${toX(0)},${H - PAD} ` + pontos.map((p, i) => `L${toX(i)},${toY(p.valor)}`).join(' ') + ` L${toX(pontos.length - 1)},${H - PAD} Z`
    : null;

  const { min: refMin, max: refMax } = parseRef(referencia);
  const foraRef = ultimo.status === 'alto' || ultimo.status === 'baixo';
  const corLinha = foraRef ? PP.red : PP.sage;

  let refBand = null;
  if (refMin !== null && refMax !== null) {
    const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
    const ry1 = clamp(toY(refMax), PAD, H - PAD);
    const ry2 = clamp(toY(refMin), PAD, H - PAD);
    if (ry2 > ry1) refBand = <rect x={PAD} y={ry1} width={innerW} height={ry2 - ry1} fill="rgba(92,138,110,0.12)" rx="2"/>;
  }

  const statusBadge = (st) => {
    if (st === 'alto')  return { label: '↑ Alto',  bg: PP.redLight,   color: PP.red,     border: '#F5C5C5' };
    if (st === 'baixo') return { label: '↓ Baixo', bg: '#EBF5F8',     color: '#2A5A68',  border: '#B0D4DC' };
    return                     { label: 'Normal',  bg: PP.sagePale,   color: PP.sageDark, border: '#B8D9C4' };
  };

  const tabelaOrdenada = [...pontos].reverse();

  return (
    <div style={{
      background: PP.white,
      border: `1.5px solid ${PP.borderLight}`,
      borderRadius: 16,
      overflow: 'hidden',
      boxShadow: '0 2px 12px rgba(30,51,38,0.06)',
      animation: 'ppSlideDown 0.25s cubic-bezier(0.16,1,0.3,1)',
    }}>
      {/* Header */}
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '16px 20px',
        background: PP.sagePale,
        borderBottom: `1px solid ${PP.borderLight}`,
      }}>
        <div>
          <div style={{fontSize: 15, fontWeight: 700, color: PP.sageDark, fontFamily: 'Lora, serif'}}>
            📊 Evolução — {nome}
          </div>
          <div style={{fontSize: 11, color: PP.textMuted, marginTop: 2}}>
            {pontos.length} medição{pontos.length !== 1 ? 'ões' : ''}
          </div>
        </div>
        <button
          onClick={onClose}
          style={{
            width: 30, height: 30, borderRadius: '50%',
            border: 'none', background: PP.creamDark, color: PP.textMuted,
            cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
            fontSize: 16, lineHeight: 1, padding: 0,
          }}
          title="Fechar dashboard"
        >×</button>
      </div>

      {/* Corpo */}
      <div style={{padding: '16px 20px', display: 'flex', flexDirection: 'column', gap: 16}}>

        {/* 3 Stat cards */}
        <div style={{display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 10}}>
          <div style={{background: PP.bg, borderRadius: 12, padding: '10px 12px', border: `1px solid ${PP.borderLight}`}}>
            <div style={{fontSize: 10, color: PP.textMuted, textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 4}}>Último valor</div>
            <div style={{display: 'flex', alignItems: 'baseline', gap: 4, flexWrap: 'wrap'}}>
              <span style={{fontSize: 20, fontWeight: 700, color: foraRef ? PP.red : PP.text}}>{ultimo.valorStr}</span>
              {unidade && <span style={{fontSize: 11, color: PP.textMuted}}>{unidade}</span>}
              {ultimo.status==='alto' && <span style={{fontSize: 13, fontWeight: 700, color: PP.red}}>↑</span>}
              {ultimo.status==='baixo' && <span style={{fontSize: 13, fontWeight: 700, color: '#2A5A68'}}>↓</span>}
            </div>
          </div>
          <div style={{background: PP.bg, borderRadius: 12, padding: '10px 12px', border: `1px solid ${PP.borderLight}`}}>
            <div style={{fontSize: 10, color: PP.textMuted, textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 4}}>Referência</div>
            <div style={{fontSize: 13, fontWeight: 600, color: PP.textMid, marginTop: 4}}>{referencia || '—'}</div>
          </div>
          <div style={{background: PP.bg, borderRadius: 12, padding: '10px 12px', border: `1px solid ${PP.borderLight}`}}>
            <div style={{fontSize: 10, color: PP.textMuted, textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 4}}>Tendência</div>
            <div style={{fontSize: 13, fontWeight: 700, color: tendencia.cor, marginTop: 4}}>{tendencia.label}</div>
          </div>
        </div>

        {/* Gráfico SVG */}
        <div style={{background: PP.bg, borderRadius: 12, padding: '12px 14px', border: `1px solid ${PP.borderLight}`}}>
          <svg width="100%" viewBox={`0 0 ${W} ${H}`} style={{display: 'block', overflow: 'visible'}}>
            {refBand}
            {areaPath && <path d={areaPath} fill={foraRef ? 'rgba(184,64,64,0.06)' : 'rgba(92,138,110,0.08)'}/>}
            {pontos.length > 1 && <polyline points={pts} fill="none" stroke={corLinha} strokeWidth="2.5" strokeLinejoin="round" strokeLinecap="round" opacity="0.9"/>}
            {pontos.map((p, i) => {
              const cor = (p.status==='alto'||p.status==='baixo') ? PP.red : PP.sage;
              const isLast = i === pontos.length - 1;
              return <circle key={i} cx={toX(i)} cy={toY(p.valor)} r={isLast ? 5 : 3.5} fill={cor} stroke={PP.white} strokeWidth="1.5"/>;
            })}
            {pontos.length >= 1 && (
              <text x={toX(0)} y={H} fontSize="9" fill={PP.textMuted} textAnchor="middle">{fmtDataCurta(pontos[0].data)}</text>
            )}
            {pontos.length >= 2 && (
              <text x={toX(pontos.length-1)} y={H} fontSize="9" fill={PP.textMuted} textAnchor="middle">{fmtDataCurta(pontos[pontos.length-1].data)}</text>
            )}
          </svg>
        </div>

        {/* Tabela de histórico */}
        <div style={{background: PP.white, borderRadius: 12, border: `1.5px solid ${PP.borderLight}`, overflow: 'hidden'}}>
          {tabelaOrdenada.map((p, i) => {
            const b = statusBadge(p.status);
            return (
              <div key={i} style={{display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, padding: '10px 14px', borderBottom: i < tabelaOrdenada.length - 1 ? `1px solid ${PP.borderLight}` : 'none', flexWrap: 'wrap'}}>
                <div style={{fontSize: 12, color: PP.textMid, minWidth: 90}}>{fmtDataCurta(p.data)}</div>
                <div style={{fontSize: 14, fontWeight: 700, color: p.status==='alto' ? PP.red : p.status==='baixo' ? '#2A5A68' : PP.text, flex: 1, textAlign: 'center'}}>
                  {p.valorStr}{unidade ? <span style={{fontSize: 10, color: PP.textMuted, fontWeight: 400, marginLeft: 3}}>{unidade}</span> : null}
                </div>
                <div style={{display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0}}>
                  {referencia && <span style={{fontSize: 10, color: PP.textMuted}}>Ref: {referencia}</span>}
                  <span style={{fontSize: 10, fontWeight: 700, background: b.bg, color: b.color, border: `1px solid ${b.border}`, borderRadius: 6, padding: '2px 7px'}}>{b.label}</span>
                </div>
              </div>
            );
          })}
        </div>

      </div>
    </div>
  );
};

// Constrói a lista de exames com pontos acumulados a partir das submissions.
// Função pura reutilizada por ExamPills (cards) e PatientProfile (botão + exame do modal).
const construirExamesDisponiveis = (submissions) => {
  const examMap = {};
  const examOrder = [];
  const classesSet = new Set();
  // Ordena os pontos pela data de REALIZAÇÃO do exame (data_coleta), não pela
  // data de anexo (submitted_at). Como cada laudo tem sua própria data_coleta,
  // a ordenação por sub é só um pré-agrupamento; o sort final dos pontos é por data.
  const ordenadas = [...submissions].sort((a, b) => new Date(a.submitted_at) - new Date(b.submitted_at));

  for (const sub of ordenadas) {
    for (const block of sub.lab_blocks || []) {
      const classeBloco = block.categoria || block.nome || 'Outros';
      // Data de realização do laudo; submitted_at é só fallback p/ dados antigos.
      const dataBlock = block.data_coleta || sub.submitted_at;
      for (const ex of block.exames || []) {
        if (!ex || !ex.nome || ex.valor == null || ex.valor === '') continue;
        const num = parseFloat(String(ex.valor).replace(',', '.'));
        if (isNaN(num)) continue;
        const classe = categoriaOverridePP(ex.nome) || classeBloco;
        classesSet.add(classe);
        const key = normNomePP(ex.nome);
        if (!examMap[key]) {
          examMap[key] = { nome: ex.nome, unidade: ex.unidade || '', referencia: ex.referencia || '', classe, pontos: [] };
          examOrder.push(key);
        }
        examMap[key].pontos.push({ data: dataBlock, valor: num, valorStr: ex.valor, status: ex.status });
      }
    }
    for (const ex of sub.demais_exames || []) {
      if (!ex || !ex.nome || ex.valor == null || ex.valor === '') continue;
      const num = parseFloat(String(ex.valor).replace(',', '.'));
      if (isNaN(num)) continue;
      const key = normNomePP(ex.nome);
      if (!examMap[key]) {
        const classe = categoriaOverridePP(ex.nome) || 'Outros Exames';
        classesSet.add(classe);
        examMap[key] = { nome: ex.nome, unidade: ex.unidade || '', referencia: ex.referencia || '', classe, pontos: [] };
        examOrder.push(key);
      }
      // Cada exame descritivo tem sua própria data de realização (data_coleta).
      examMap[key].pontos.push({ data: ex.data_coleta || sub.submitted_at, valor: num, valorStr: ex.valor, status: ex.status });
    }
  }

  // Reordena os pontos de cada exame por data de realização (asc), pois data_coleta
  // pode divergir da ordem de envio (laudo antigo anexado depois).
  for (const key of examOrder) {
    examMap[key].pontos.sort((a, b) => new Date(a.data) - new Date(b.data));
  }

  return {
    exames: examOrder.map(key => examMap[key]).filter(e => e.pontos.length > 0),
    classes: [...classesSet].sort((a, b) => a.localeCompare(b, 'pt-BR')),
  };
};

// ── ExamPills (mini-cards com filtros) ───────────────────────────────────────
const ExamPills = ({ submissions, onVerAnalise }) => {
  const [buscaNome, setBuscaNome] = useStatePP('');
  const [classeAtiva, setClasseAtiva] = useStatePP(null); // null = sem filtro, 'todos' = mostra tudo, string = classe específica
  const [selecionados, setSelecionados] = useStatePP([]); // array de keys normNomePP

  const toggleSel = (key) => setSelecionados(prev =>
    prev.includes(key) ? prev.filter(k => k !== key) : [...prev, key]
  );

  const { exames, classes } = useMemoP(() => construirExamesDisponiveis(submissions), [submissions]);

  if (exames.length === 0) return null;

  // Filtragem — null = nenhum filtro ativo (cards ocultos)
  const buscaNorm = normNomePP(buscaNome);
  const temFiltro = buscaNorm.length > 0 || classeAtiva === 'todos' || (classeAtiva !== null && classeAtiva !== 'todos');

  const filtrados = temFiltro ? exames.filter(e => {
    const nomeOk = buscaNorm.length === 0 || normNomePP(e.nome).includes(buscaNorm);
    const classeOk = classeAtiva === 'todos' || classeAtiva === null || e.classe === classeAtiva;
    return nomeOk && classeOk;
  }) : [];

  // Ordena: alertas primeiro, depois alfabético
  const sorted = [...filtrados].sort((a, b) => {
    const aAlert = ['alto','baixo'].includes(a.pontos[a.pontos.length-1]?.status);
    const bAlert = ['alto','baixo'].includes(b.pontos[b.pontos.length-1]?.status);
    if (aAlert && !bAlert) return -1;
    if (!aAlert && bAlert) return 1;
    return a.nome.localeCompare(b.nome, 'pt-BR');
  });

  return (
    <div style={{display: 'flex', flexDirection: 'column', gap: 14}}>

      {/* Filtro por nome */}
      <div style={{position: 'relative'}}>
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={PP.textMuted} strokeWidth="2"
          style={{position: 'absolute', left: 12, top: '50%', transform: 'translateY(-50%)', pointerEvents: 'none'}}>
          <circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
        </svg>
        <input
          type="text"
          placeholder="Buscar exame por nome (ex: hemoglobina, glicose...)"
          value={buscaNome}
          onChange={e => setBuscaNome(e.target.value)}
          style={{
            width: '100%', padding: '9px 12px 9px 34px',
            border: `1.5px solid ${PP.borderLight}`, borderRadius: 10,
            fontSize: 13, color: PP.text, background: PP.white,
            outline: 'none', fontFamily: 'inherit',
            boxSizing: 'border-box',
          }}
          onFocus={e => { e.target.style.borderColor = PP.sage; }}
          onBlur={e => { e.target.style.borderColor = PP.borderLight; }}
        />
        {buscaNome && (
          <button onClick={() => setBuscaNome('')}
            style={{position: 'absolute', right: 10, top: '50%', transform: 'translateY(-50%)', background: 'none', border: 'none', cursor: 'pointer', color: PP.textMuted, fontSize: 16, lineHeight: 1, padding: 2}}>
            ×
          </button>
        )}
      </div>

      {/* Filtro por classe */}
      {classes.length > 0 && (
        <div style={{display: 'flex', flexWrap: 'wrap', gap: 6, alignItems: 'center'}}>
          <span style={{fontSize: 11, color: PP.textMuted, marginRight: 2}}>Classe:</span>

          {/* Chip "Todos" */}
          {(() => {
            const ativa = classeAtiva === 'todos';
            return (
              <button onClick={() => setClasseAtiva(ativa ? null : 'todos')}
                style={{
                  padding: '4px 12px', borderRadius: 20, fontSize: 12, fontWeight: ativa ? 700 : 500,
                  border: `1.5px solid ${ativa ? PP.sage : PP.borderLight}`,
                  background: ativa ? PP.sagePale : PP.white,
                  color: ativa ? PP.sageDark : PP.textMid,
                  cursor: 'pointer', fontFamily: 'inherit', transition: 'all 0.12s',
                }}>
                Todos
              </button>
            );
          })()}

          {/* Chips de cada classe — exclui "Outros Exames" e "Outros" */}
          {classes.filter(c => c !== 'Outros Exames' && c !== 'Outros').map(c => {
            const ativa = classeAtiva === c;
            return (
              <button key={c} onClick={() => setClasseAtiva(ativa ? null : c)}
                style={{
                  padding: '4px 12px', borderRadius: 20, fontSize: 12, fontWeight: ativa ? 700 : 500,
                  border: `1.5px solid ${ativa ? PP.sage : PP.borderLight}`,
                  background: ativa ? PP.sagePale : PP.white,
                  color: ativa ? PP.sageDark : PP.textMid,
                  cursor: 'pointer', fontFamily: 'inherit', transition: 'all 0.12s',
                }}>
                {c}
              </button>
            );
          })}
        </div>
      )}

      {/* Estado: sem filtro aplicado */}
      {!temFiltro && (
        <div style={{
          display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
          padding: '28px 16px', gap: 8,
          background: PP.bg, borderRadius: 12, border: `1.5px dashed ${PP.borderLight}`,
        }}>
          <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke={PP.textMuted} strokeWidth="1.5"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
          <span style={{fontSize: 13, color: PP.textMuted, textAlign: 'center'}}>
            Use os filtros acima para buscar exames por nome ou classe.
          </span>
          <span style={{fontSize: 11, color: PP.textMuted}}>
            {exames.length} exame{exames.length !== 1 ? 's' : ''} disponíve{exames.length !== 1 ? 'is' : 'l'}
          </span>
        </div>
      )}

      {/* Estado: filtro aplicado, sem resultados */}
      {temFiltro && sorted.length === 0 && (
        <div style={{padding: '20px', textAlign: 'center', color: PP.textMuted, fontSize: 13, background: PP.bg, borderRadius: 12, border: `1.5px dashed ${PP.borderLight}`}}>
          Nenhum exame encontrado para esse filtro.
        </div>
      )}

      {/* Grid de mini-cards */}
      {sorted.length > 0 && (
        <div>
          <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10}}>
            <span style={{fontSize: 12, color: PP.textMuted}}>
              {sorted.length} exame{sorted.length !== 1 ? 's' : ''} encontrado{sorted.length !== 1 ? 's' : ''}
              {classeAtiva && classeAtiva !== 'todos' ? ` em "${classeAtiva}"` : ''}
            </span>
            {(buscaNorm.length > 0 || (classeAtiva && classeAtiva !== 'todos')) && (
              <button onClick={() => { setBuscaNome(''); setClasseAtiva(null); }}
                style={{fontSize: 11, color: PP.textMuted, background: 'none', border: 'none', cursor: 'pointer', textDecoration: 'underline', padding: 0, fontFamily: 'inherit'}}>
                Limpar filtros
              </button>
            )}
          </div>
          <div style={{display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(124px, 1fr))', gap: 8}}>
            {sorted.map(exame => {
              const exKey = normNomePP(exame.nome);
              const ultimo = exame.pontos[exame.pontos.length - 1];
              const anterior = exame.pontos.length > 1 ? exame.pontos[exame.pontos.length - 2] : null;
              const isAlert = ultimo?.status === 'alto' || ultimo?.status === 'baixo';
              const isSel = selecionados.includes(exKey);
              let trendArrow = null;
              if (anterior) {
                if (ultimo.valor > anterior.valor) trendArrow = { symbol: '↑', color: PP.red };
                else if (ultimo.valor < anterior.valor) trendArrow = { symbol: '↓', color: PP.sage };
                else trendArrow = { symbol: '→', color: PP.textMuted };
              }
              const cardBg = isSel ? PP.sagePale : (isAlert ? PP.redLight : PP.white);
              const cardBorder = isSel ? PP.sage : (isAlert ? '#F5C5C5' : PP.borderLight);

              return (
                <button key={exKey} onClick={() => toggleSel(exKey)}
                  style={{
                    display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 8,
                    position: 'relative', padding: '10px 12px', borderRadius: 12,
                    border: `1.5px solid ${cardBorder}`, background: cardBg,
                    cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left',
                    transition: 'all 0.15s', boxShadow: '0 1px 4px rgba(30,51,38,0.04)',
                  }}
                  onMouseEnter={e => { if (!isSel) e.currentTarget.style.borderColor = PP.sage; }}
                  onMouseLeave={e => { if (!isSel) e.currentTarget.style.borderColor = cardBorder; }}
                >
                  <span style={{
                    width: 16, height: 16, borderRadius: 5, flexShrink: 0,
                    border: `1.5px solid ${isSel ? PP.sage : PP.border}`,
                    background: isSel ? PP.sage : PP.white,
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                  }}>
                    {isSel && <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="3.5"><polyline points="20 6 9 17 4 12"/></svg>}
                  </span>
                  <span style={{fontSize: 12, fontWeight: 600, color: isAlert ? '#7A2E2E' : PP.textMid, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', lineHeight: 1.3, flex: 1, minWidth: 0}} title={exame.nome}>
                    {exame.nome}
                  </span>
                  {trendArrow && <span style={{fontSize: 14, fontWeight: 800, color: trendArrow.color, flexShrink: 0}}>{trendArrow.symbol}</span>}
                  {isAlert && !isSel && <span style={{position: 'absolute', top: 8, right: 8, width: 7, height: 7, borderRadius: '50%', background: PP.red, boxShadow: `0 0 0 3px ${PP.redLight}`}}/>}
                </button>
              );
            })}
          </div>

          {/* Barra de ação: ver análise dos selecionados */}
          {selecionados.length > 0 && (
            <div style={{display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, marginTop: 12, padding: '10px 14px', background: PP.sagePale, borderRadius: 12, border: `1.5px solid ${PP.sage}`}}>
              <span style={{fontSize: 12, fontWeight: 600, color: PP.sageDark}}>
                {selecionados.length} exame{selecionados.length !== 1 ? 's' : ''} selecionado{selecionados.length !== 1 ? 's' : ''}
              </span>
              <div style={{display: 'flex', gap: 8}}>
                <button onClick={() => setSelecionados([])}
                  style={{fontSize: 12, fontWeight: 600, color: PP.textMid, background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit'}}>
                  Limpar
                </button>
                <button onClick={() => {
                    const escolhidos = exames.filter(e => selecionados.includes(normNomePP(e.nome)));
                    onVerAnalise(escolhidos);
                  }}
                  style={{fontSize: 12, fontWeight: 700, color: PP.white, background: PP.sageDark, border: 'none', borderRadius: 9, padding: '8px 16px', cursor: 'pointer', fontFamily: 'inherit', boxShadow: '0 4px 12px rgba(44,74,53,0.22)'}}>
                  Ver análise ({selecionados.length})
                </button>
              </div>
            </div>
          )}
        </div>
      )}
    </div>
  );
};

// ── Fluxograma de Exames (tabela numérica: exames × datas) ────────────────────
// Monta grupos (por categoria/bloco) → exames → valores por data.
// Datas em colunas, do mais recente (esq.) ao mais antigo (dir.).
const construirFluxograma = (submissions) => {
  // As colunas são as DATAS DE REALIZAÇÃO dos exames (data_coleta), não a data de
  // anexo (submitted_at). Coletadas direto dos blocos/exames; submitted_at é fallback.
  const datasSet = new Set();

  // grupoMap: nomeGrupo → { ordem, exames: { [nomeNorm]: { nome, unidade, valores: { [data]: {valorStr, status} } } } }
  const grupoMap = {};
  const grupoOrder = [];

  const garanteGrupo = (grupo) => {
    if (!grupoMap[grupo]) { grupoMap[grupo] = {}; grupoOrder.push(grupo); }
  };
  const adiciona = (grupo, ex, data) => {
    if (!ex || !ex.nome || ex.valor == null || ex.valor === '') return;
    const num = parseFloat(String(ex.valor).replace(',', '.'));
    if (isNaN(num)) return;
    garanteGrupo(grupo);
    datasSet.add(data);
    const key = normNomePP(ex.nome);
    if (!grupoMap[grupo][key]) grupoMap[grupo][key] = { nome: ex.nome, unidade: ex.unidade || '', referencia: ex.referencia || '', valores: {} };
    grupoMap[grupo][key].valores[data] = { valorStr: ex.valor, status: ex.status };
  };

  for (const sub of submissions) {
    for (const block of sub.lab_blocks || []) {
      const grupoBloco = block.categoria || block.nome || 'Outros';
      const dataBlock = block.data_coleta || sub.submitted_at;
      for (const ex of block.exames || []) adiciona(categoriaOverridePP(ex && ex.nome) || grupoBloco, ex, dataBlock);
    }
    for (const ex of sub.demais_exames || []) {
      if (ex && ex.nome === '_meta') continue; // ignora item de metadados da IA
      adiciona(categoriaOverridePP(ex && ex.nome) || 'Outros Exames', ex, ex.data_coleta || sub.submitted_at);
    }
  }

  // ordem das colunas: mais recente (esq.) → mais antigo (dir.)
  const datasOrden = [...datasSet].sort((a, b) => new Date(b) - new Date(a));

  const grupos = grupoOrder
    .map(nome => ({ nome, exames: Object.values(grupoMap[nome]) }))
    .filter(g => g.exames.length > 0);

  return { datas: datasOrden, grupos };
};

// ── Template clínico padronizado (botão Copiar) ───────────────────────────────
// Mapa: nome normalizado do exame → abreviação usada no template.
// Cobre os sinônimos mais comuns em laudos brasileiros.
const ABREV_EXAMES = (() => {
  // [abreviacao, [sinônimos...]]
  const pares = [
    // Hemograma  (Hem = hemácias/eritrócitos; Hg = hemoglobina — campos distintos)
    ['Hem', ['hemacias', 'hemacia', 'eritrocitos', 'contagem de hemacias']],
    ['Hg', ['hemoglobina', 'hb', 'hgb']],
    ['Ht', ['hematocrito', 'ht', 'hto']],
    ['CHCM', ['chcm', 'concentracao de hemoglobina corpuscular media']],
    ['HCM', ['hcm', 'hemoglobina corpuscular media']],
    ['VCM', ['vcm', 'volume corpuscular medio']],
    ['RDW', ['rdw']],
    ['Plaq', ['plaquetas', 'plaqueta', 'contagem de plaquetas']],
    ['VPM', ['vpm', 'volume plaquetario medio']],
    // Cinética do ferro
    ['Ferritina', ['ferritina']],
    ['Ferro', ['ferro', 'ferro serico']],
    ['UIBIC', ['uibic', 'capacidade latente de ligacao do ferro']],
    ['TIBC', ['tibc', 'capacidade total de ligacao do ferro', 'ctlf']],
    ['IST', ['ist', 'indice de saturacao de transferrina', 'saturacao de transferrina']],
    // Leucograma
    ['Leuco', ['leucocitos', 'leucograma', 'global de leucocitos', 'contagem de leucocitos']],
    ['Neut', ['neutrofilos', 'neutrofilo', 'segmentados']],
    ['Eos', ['eosinofilos', 'eosinofilo']],
    ['Bas', ['basofilos', 'basofilo']],
    ['Linf', ['linfocitos', 'linfocito']],
    ['Mo', ['monocitos', 'monocito']],
    // Inflamatórios
    ['PCR', ['pcr', 'proteina c reativa', 'proteina c-reativa']],
    ['VSH', ['vsh', 'vhs', 'velocidade de hemossedimentacao']],
    // Função renal e eletrólitos
    ['Ur', ['ureia', 'ureia serica']],
    ['Cr', ['creatinina', 'creatinina serica']],
    ['eGFR', ['egfr', 'tfg estimada', 'taxa de filtracao glomerular estimada', 'rfg estimado', 'ckd epi', 'ckd-epi']],
    ['Na', ['sodio', 'na']],
    ['K', ['potassio', 'k']],
    ['Cl', ['cloro', 'cloreto']],
    ['Ca', ['calcio', 'calcio total', 'calcio serico']],
    ['Mg', ['magnesio', 'mg']],
    ['Fosf', ['fosforo', 'fosfato', 'fosf']],
    ['Ác. Úr.', ['acido urico', 'ac urico', 'uricemia']],
    ['RAC', ['rac', 'relacao albumina creatinina', 'relacao albumina creatinina urinaria', 'albumina creatinina', 'microalbuminuria', 'rel alb cr urin']],
    ['RPC', ['rpc', 'relacao proteina creatinina', 'relacao proteina creatinina urinaria', 'rel prot cr urin', 'proteina creatinina']],
    ['Prot Ur 24h', ['proteinuria de 24h', 'proteinuria 24h', 'proteinuria 24 horas', 'proteina urinaria 24h', 'prot ur 24h', 'proteinuria de 24 horas']],
    // Lipidograma
    ['CT', ['colesterol total', 'colesterol']],
    ['HDL', ['hdl', 'hdl colesterol', 'colesterol hdl']],
    ['Não-HDL', ['nao hdl', 'colesterol nao hdl', 'nao-hdl']],
    ['LDL', ['ldl', 'ldl colesterol', 'colesterol ldl']],
    ['VLDL', ['vldl', 'colesterol vldl']],
    ['TG', ['triglicerideos', 'triglicerides', 'tg']],
    // Muscular
    ['CPK', ['cpk', 'ck', 'creatinoquinase', 'creatina quinase']],
    // Glicemia
    ['GJ', ['glicemia de jejum', 'glicemia', 'glicose', 'glicose de jejum', 'glj']],
    ['HbA1c', ['hba1c', 'hemoglobina glicada', 'hemoglobina glicosilada', 'glicada']],
    ['Glicose Média Estimada', ['glicose media estimada', 'gme', 'media glicemica estimada']],
    ['TOTG', ['totg', 'teste de tolerancia a glicose', 'tolerancia a glicose', 'gtt', 'curva glicemica']],
    // Função hepática
    ['TGO', ['tgo', 'ast', 'aspartato aminotransferase']],
    ['TGP', ['tgp', 'alt', 'alanina aminotransferase']],
    ['BT', ['bilirrubina total', 'bt']],
    ['BD', ['bilirrubina direta', 'bd']],
    ['BI', ['bilirrubina indireta', 'bi']],
    ['INR', ['inr', 'rni', 'tap inr', 'tempo de protrombina']],
    ['ALB', ['albumina', 'albumina serica']],
    // Vitaminas
    ['Ác. Fól.', ['acido folico', 'ac folico', 'folato']],
    ['Vit B-12', ['vitamina b12', 'vit b12', 'b12', 'cobalamina']],
    ['25-OH VIT D', ['vitamina d', 'vit d', '25 oh vitamina d', '25 hidroxivitamina d', '25-oh-vitamina d', '25 oh vit d']],
    // Tireoide e hormônios
    ['TSH', ['tsh', 'hormonio tireoestimulante']],
    ['T4 L', ['t4 livre', 't4l', 'tiroxina livre', 't4 l']],
    ['T3', ['t3', 'triiodotironina', 't3 total']],
    ['PTH', ['pth', 'paratormonio', 'hormonio da paratireoide']],
  ];
  const map = {};
  for (const [abrev, sins] of pares) for (const s of sins) map[normNomePP(s)] = abrev;
  return map;
})();

// Linhas do template laboratorial (modelo médico). Cada linha é uma sequência de
// abreviações na ordem exata do modelo. Itens AUSENTES são suprimidos (não aparecem
// em branco). Linha sem nenhum item presente é omitida inteira.
const LINHAS_LAB = [
  ['Hem', 'Hg', 'Ht', 'CHCM', 'HCM', 'VCM', 'RDW', 'Plaq', 'VPM'],
  ['Ferritina', 'Ferro', 'UIBIC', 'TIBC', 'IST'],
  ['Leuco', 'Neut', 'Eos', 'Bas', 'Linf', 'Mo'],
  ['PCR', 'VSH'],
  ['Ur', 'Cr', 'eGFR', 'Na', 'K', 'Cl', 'Ca', 'Mg', 'Fosf', 'Ác. Úr.', 'RAC', 'RPC', 'Prot Ur 24h'],
  ['GJ', 'HbA1c', 'Glicose Média Estimada', 'TOTG'],
  ['CT', 'HDL', 'Não-HDL', 'LDL', 'VLDL', 'TG'],
  ['CPK'],
  ['TGO', 'TGP', 'BT', 'BD', 'BI', 'INR', 'ALB'],
  ['Ác. Fól.', 'Vit B-12', '25-OH VIT D'],
  ['TSH', 'T4 L', 'PTH'],
];

// Sumário de urina (SU): campos textuais com rótulo próprio dentro de uma única linha.
const SU_CAMPOS = [
  ['Dens', ['densidade', 'densidade urinaria']],
  ['pH', ['ph', 'ph urinario']],
  ['Cor', ['cor', 'coloracao']],
  ['Prot', ['proteinas', 'proteina urinaria', 'proteinuria']],
  ['Corpos. Cetônicos', ['corpos cetonicos', 'cetonuria', 'cetona']],
  ['Pig. Biliares', ['pigmentos biliares', 'bilirrubina urinaria']],
  ['Nitrito', ['nitrito', 'nitritos']],
  ['Filamentos de Muco', ['filamentos de muco', 'muco']],
  ['Hemácias', ['hemacias urinarias', 'hematuria', 'hemacias na urina']],
  ['Células Epiteliais de Descamação', ['celulas epiteliais', 'celulas epiteliais de descamacao', 'celulas de descamacao']],
];

// Rastreios: nome normalizado → rótulo do template
const RASTREIOS = [
  ['MMG', ['mamografia', 'mmg']],
  ['USG de mamas', ['ultrassom de mamas', 'usg de mamas', 'usg mamas', 'ultrassonografia mamaria', 'ecografia mamaria']],
  ['COLONO', ['colonoscopia', 'colono']],
  ['DMO', ['dmo', 'densitometria', 'densitometria ossea']],
  ['PSOF', ['psof', 'pesquisa de sangue oculto', 'sangue oculto nas fezes', 'sangue oculto']],
  ['PSA', ['psa', 'antigeno prostatico especifico', 'antigeno prostatico']],
  ['Papanicolau', ['papanicolau', 'colpocitologia', 'citologia oncotica', 'preventivo']],
];

// Classificação dos exames DESCRITIVOS (origem 'demais') em seções do relatório.
// Cada exame que não for laboratório/rastreio cai em uma destas, pela 1ª que casar.
// A ordem importa (cardiológico antes de imagem genérica, etc.).
const SECOES_DESCRITIVAS = [
  ['Exames cardiológicos', [
    'ecocardiograma', 'eco transtoracico', 'ecodopplercardiograma', 'eletrocardiograma', 'ecg',
    'holter', 'mapa 24', 'm.a.p.a', 'mapa de 24', 'doppler de carotidas', 'doppler carotidas',
    'cateterismo', 'cintilografia miocardica', 'cintilografia cardiaca', 'ressonancia cardiaca',
    'ergometrico', 'teste ergometrico', 'teste de esforco', 'angiotomografia de coronarias', 'escore de calcio',
  ]],
  ['Exames de Rastreios', [
    // rastreios já tratados à parte; aqui só p/ não cair em "imagem" por engano
  ]],
  ['Outros Exames', [
    'endoscopia', 'eda', 'colonoscopia', 'histopatologico', 'anatomopatologico', 'biopsia', 'core biopsy', 'punção', 'puncao',
    'polissonografia', 'espirometria', 'prova de funcao pulmonar', 'liquor', 'liquido cefalorraquidiano',
  ]],
  ['Exames de Imagem', [
    'ultrassonografia', 'ultrassom', 'usg', 'us de', 'us ', 'ecografia',
    'ressonancia', 'rm de', 'rm ', 'rnm',
    'tomografia', 'tc de', 'tc ', 'angiotomografia', 'angiografia', 'angiorressonancia',
    'raio-x', 'raio x', 'rx de', 'rx ', 'radiografia', 'pet', 'pet-ct', 'pet ct', 'cintilografia', 'densitometria',
  ]],
  ['Pareceres, relatórios médicos ou de outros profissionais, resumos de alta hospitalar', [
    'parecer', 'relatorio medico', 'relatorio', 'resumo de alta', 'alta hospitalar', 'sumario de alta',
    'avaliacao de', 'laudo de',
  ]],
];

// Classifica um exame descritivo numa das seções acima pelo nome. Default: "Outros Exames".
const classificarSecao = (nome) => {
  const n = normNomePP(nome);
  for (const [secao, palavras] of SECOES_DESCRITIVAS) {
    if (palavras.some(p => n.includes(normNomePP(p)))) return secao;
  }
  return 'Outros Exames';
};

// Constrói, para cada data, um lookup: nomeNorm → { valorStr, nome }.
// Lê direto das submissions (inclui exames com VALOR DE TEXTO, como rastreios
// "BIRADS 2", "negativo" — que a tabela numérica descarta).
const lookupPorData = (submissions) => {
  const porData = {};
  const add = (ex, data, origem) => {
    if (!ex || !ex.nome || ex.nome === '_meta' || ex.valor == null || ex.valor === '') return;
    if (!porData[data]) porData[data] = {};
    const key = normNomePP(ex.nome);
    if (!porData[data][key]) porData[data][key] = { valorStr: ex.valor, nome: ex.nome, origem, status: ex.status || '' };
  };
  for (const sub of submissions) {
    for (const block of sub.lab_blocks || []) {
      // Cada laudo tem sua própria data de coleta; usa o submitted_at só como fallback.
      const dataBlock = block.data_coleta || sub.submitted_at;
      for (const ex of block.exames || []) add(ex, dataBlock, 'lab');
    }
    // Cada exame descritivo (US, RM, TC, laudo...) tem sua própria data de
    // realização (data_coleta). Usa o submitted_at só como fallback (dados antigos).
    for (const ex of sub.demais_exames || []) add(ex, ex.data_coleta || sub.submitted_at, 'demais');
  }
  return porData;
};

const fmtDataBR = (iso) => {
  if (!iso) return '';
  // Data pura "YYYY-MM-DD" é interpretada como UTC por new Date(); em fuso negativo
  // (Recife, UTC-3) isso recua 1 dia ao formatar. Trata como data LOCAL nesse caso.
  const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(iso).trim());
  const d = m ? new Date(+m[1], +m[2] - 1, +m[3]) : new Date(iso);
  return d.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit', year: 'numeric' });
};

// Lista todos os exames selecionáveis (numéricos e de texto), 1 por nome,
// usando o nome mais recente. Marca quais NÃO aparecem na tabela numérica
// (exames de texto) para a lista extra de checkboxes.
const listarExamesSelecionaveis = (submissions, grupos) => {
  const porData = lookupPorData(submissions);
  const datas = Object.keys(porData).sort((a, b) => new Date(b) - new Date(a));
  // chaves que aparecem na tabela numérica
  const chavesTabela = new Set();
  for (const g of grupos) for (const ex of g.exames) chavesTabela.add(normNomePP(ex.nome));

  const vistos = new Map(); // key → { key, nome, naTabela }
  for (const data of datas) {
    for (const [key, item] of Object.entries(porData[data])) {
      if (!vistos.has(key)) vistos.set(key, { key, nome: item.nome, naTabela: chavesTabela.has(key) });
    }
  }
  const todos = [...vistos.values()];
  return {
    todasChaves: todos.map(e => e.key),
    soTexto: todos.filter(e => !e.naTabela), // não aparecem na tabela → lista extra
  };
};

// Seta de status ao lado do valor, como no modelo médico (↑ alto / ↓ baixo).
const setaStatus = (st) => st === 'alto' ? ' ↑' : st === 'baixo' ? ' ↓' : '';

// selecionados: Set de chaves normalizadas a INCLUIR. Se null, inclui todos.
// REGRA DO MODELO: exame ausente ou desmarcado é SUPRIMIDO (não aparece em branco).
// Linha/seção sem nenhum item presente é omitida inteira.
const montarTextoClinico = (submissions, patientName, selecionados = null) => {
  const porData = lookupPorData(submissions);
  // datas: todas as datas com pelo menos um exame, mais recente → mais antigo
  const datas = Object.keys(porData).sort((a, b) => new Date(b) - new Date(a));

  const incluido = (keyNorm) => selecionados === null || selecionados.has(keyNorm);

  // item de um exame (por abreviação) numa data: procura usando todos os sinônimos.
  const sinsPorAbrev = {};
  for (const [keyNorm, abrev] of Object.entries(ABREV_EXAMES)) {
    (sinsPorAbrev[abrev] = sinsPorAbrev[abrev] || []).push(keyNorm);
  }
  const itemAbrev = (data, abrev) => {
    const sins = sinsPorAbrev[abrev] || [];
    const k = sins.find(k => porData[data]?.[k]);
    if (!k || !incluido(k)) return null;   // ausente ou desmarcado → suprimido
    return porData[data][k];
  };

  // Item por lista de sinônimos crus (para SU e rastreios), numa data.
  const itemPorSins = (data, sins) => {
    const k = sins.map(s => normNomePP(s)).find(s => porData[data]?.[s]);
    if (!k || !incluido(k)) return null;
    return porData[data][k];
  };

  // nomes reconhecidos como laboratório (para separar os descritivos)
  const reconhecidos = new Set(Object.keys(ABREV_EXAMES));
  const suKeys = new Set();
  for (const [, sins] of SU_CAMPOS) for (const s of sins) suKeys.add(normNomePP(s));
  const rastreioKeys = new Set();
  for (const [, sins] of RASTREIOS) for (const s of sins) rastreioKeys.add(normNomePP(s));

  let out = '';

  // ── Laboratório — um bloco por data, mais recente primeiro. ──
  const blocosLab = [];
  for (const data of datas) {
    const linhas = [];
    // Linhas numéricas do template (itens ausentes suprimidos; linha vazia omitida).
    for (const abrevs of LINHAS_LAB) {
      const partes = [];
      for (const a of abrevs) {
        const it = itemAbrev(data, a);
        if (it) partes.push(`${a}: ${it.valorStr}${setaStatus(it.status)}`);
      }
      if (partes.length) linhas.push(partes.join(' / '));
    }
    // Sumário de urina (SU): só os campos presentes.
    const suPartes = [];
    for (const [rotulo, sins] of SU_CAMPOS) {
      const it = itemPorSins(data, sins);
      if (it) suPartes.push(`${rotulo}: ${it.valorStr}`);
    }
    if (suPartes.length) linhas.push('SU ' + suPartes.join(' / '));
    // Outros laboratoriais (origem 'lab') não reconhecidos e que não são SU.
    const outros = [];
    for (const [keyNorm, item] of Object.entries(porData[data] || {})) {
      if (item.origem !== 'lab') continue;
      if (reconhecidos.has(keyNorm) || suKeys.has(keyNorm)) continue;
      if (!incluido(keyNorm)) continue;
      outros.push(`${item.nome}: ${item.valorStr}${setaStatus(item.status)}`);
    }
    if (outros.length) linhas.push('Outros Exames Laboratoriais: ' + outros.join(' / '));

    if (linhas.length) blocosLab.push(`[${fmtDataBR(data)}]\n` + linhas.join('\n'));
  }
  if (blocosLab.length) {
    out += 'Laboratório\n' + blocosLab.join('\n\n') + '\n\n';
  }

  // ── Seções descritivas (origem 'demais'): agrupadas por seção e por data. ──
  // Coleta todos os descritivos não-rastreio, classificando por seção.
  const porSecao = {};        // secao → [{ data, nome, valorStr }]
  const rastreiosAchados = []; // [{ rotulo, data, valorStr }] (1 por rótulo, + recente)
  const rastreioFeito = new Set();

  for (const data of datas) {
    for (const [keyNorm, item] of Object.entries(porData[data] || {})) {
      if (item.origem !== 'demais') continue;
      if (!incluido(keyNorm)) continue;
      // Rastreio? (consolida 1 por rótulo, data mais recente)
      const rast = RASTREIOS.find(([, sins]) => sins.map(s => normNomePP(s)).includes(keyNorm));
      if (rast) {
        const rotulo = rast[0];
        if (!rastreioFeito.has(rotulo)) {
          rastreioFeito.add(rotulo);
          rastreiosAchados.push({ rotulo, data, valorStr: item.valorStr });
        }
        continue;
      }
      const secao = classificarSecao(item.nome);
      (porSecao[secao] = porSecao[secao] || []).push({ data, nome: item.nome, valorStr: item.valorStr });
    }
  }

  // Formata uma seção descritiva agrupando os itens por data (mais recente
  // primeiro), com cabeçalho [DATA] por grupo — igual ao bloco Laboratório.
  // itens: [{ data, rotulo, valorStr }]. Itens sem data caem em "[sem data]".
  const blocoDescritivo = (titulo, itens) => {
    if (!itens.length) return;
    const porData = {};
    for (const it of itens) {
      const d = it.data || '';
      (porData[d] = porData[d] || []).push(it);
    }
    const datasOrd = Object.keys(porData).sort((a, b) => {
      if (!a) return 1; if (!b) return -1;       // sem data por último
      return new Date(b) - new Date(a);
    });
    out += titulo + ':\n';
    out += datasOrd.map(d => {
      const cab = d ? `[${fmtDataBR(d)}]` : '[sem data]';
      const linhas = porData[d].map(it => `- ${it.rotulo}: ${it.valorStr}`).join('\n');
      return cab + '\n' + linhas;
    }).join('\n\n') + '\n\n';
  };

  const mapItens = (arr) => arr.map(e => ({ data: e.data, rotulo: e.nome, valorStr: e.valorStr }));

  blocoDescritivo('Exames de Imagem', mapItens(porSecao['Exames de Imagem'] || []));
  blocoDescritivo('Exames cardiológicos', mapItens(porSecao['Exames cardiológicos'] || []));
  blocoDescritivo('Exames de Rastreios',
    rastreiosAchados.map(r => ({ data: r.data, rotulo: r.rotulo, valorStr: r.valorStr })));
  blocoDescritivo('Outros Exames', mapItens(porSecao['Outros Exames'] || []));
  blocoDescritivo('Pareceres, relatórios médicos ou de outros profissionais, resumos de alta hospitalar',
    mapItens(porSecao['Pareceres, relatórios médicos ou de outros profissionais, resumos de alta hospitalar'] || []));

  return out.trimEnd() + '\n';
};

const FluxogramaExames = ({ submissions, patientName }) => {
  const { datas, grupos } = useMemoP(() => construirFluxograma(submissions), [submissions]);
  const { todasChaves, soTexto } = useMemoP(
    () => listarExamesSelecionaveis(submissions, grupos),
    [submissions, grupos]
  );
  const tabelaRef = useRefPP(null);
  const [copiado, setCopiado] = useStatePP(false);
  const [aviso, setAviso] = useStatePP(''); // toast de feedback (erros pontuais)

  const mostrarAviso = (msg) => { setAviso(msg); setTimeout(() => setAviso(''), 3500); };

  // Seleção de exames a copiar — todos marcados por padrão.
  const [selecionados, setSelecionados] = useStatePP(() => new Set(todasChaves));
  // Re-sincroniza quando a lista de exames mudar (novo envio, troca de paciente).
  useEffectPP(() => { setSelecionados(new Set(todasChaves)); }, [todasChaves.join('|')]);

  const toggleExame = (key) => setSelecionados(prev => {
    const next = new Set(prev);
    next.has(key) ? next.delete(key) : next.add(key);
    return next;
  });
  const todosMarcados = todasChaves.length > 0 && todasChaves.every(k => selecionados.has(k));
  const marcarTodos = () => setSelecionados(todosMarcados ? new Set() : new Set(todasChaves));

  const corStatus = (st) => st === 'alto' ? PP.red : st === 'baixo' ? PP.blue : PP.text;
  const fmtData = (iso) => {
    // Data pura "YYYY-MM-DD" (data_coleta) seria lida como UTC por new Date() e
    // recuaria 1 dia em fuso negativo (Recife, UTC-3); trata como data LOCAL.
    const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(iso).trim());
    const d = m ? new Date(+m[1], +m[2] - 1, +m[3]) : new Date(iso);
    // "05 de jun." → "05 jun" (compacto, sem preposição nem ponto)
    const curta = d.toLocaleDateString('pt-BR', { day: '2-digit', month: 'short' })
      .replace(/\bde\s+/g, '').replace(/\.$/, '').trim();
    return { curta, ano: String(d.getFullYear()) };
  };

  if (datas.length === 0 || grupos.length === 0) {
    return (
      <div style={{padding: '24px', color: PP.textMuted, fontSize: 13, textAlign: 'center'}}>
        Nenhum exame numérico disponível para montar o fluxograma.
      </div>
    );
  }

  // ── Copiar: gera texto clínico estruturado (template padronizado em Markdown) ──
  const montarHTMLeTSV = () => {
    const texto = montarTextoClinico(submissions, patientName, selecionados);
    // Mantém um HTML simples (mesmo conteúdo em <pre>) para colar com fonte mono em editores ricos.
    const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    const html = `<pre style="font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;white-space:pre-wrap">${esc(texto)}</pre>`;
    return { html, tsv: texto };
  };

  // Registro de auditoria de exportação (Art. 37 LGPD): grava QUE houve exportação,
  // nunca o conteúdo clínico — apenas tipo, paciente, contagens e timestamp.
  const registrarExportacao = (tipo) => {
    console.info('[auditoria] exportacao fluxograma', {
      tipo,                       // 'copiar' | 'pdf'
      paciente: patientName,
      datas: datas.length,
      exames: tipo === 'copiar' ? selecionados.size : todasChaves.length,
      em: new Date().toISOString(),
    });
  };

  const copiar = async () => {
    const { html, tsv } = montarHTMLeTSV();
    try {
      if (navigator.clipboard && window.ClipboardItem) {
        await navigator.clipboard.write([
          new ClipboardItem({
            'text/html': new Blob([html], { type: 'text/html' }),
            'text/plain': new Blob([tsv], { type: 'text/plain' }),
          }),
        ]);
      } else {
        await navigator.clipboard.writeText(tsv);
      }
      setCopiado(true);
      setTimeout(() => setCopiado(false), 2000);
      registrarExportacao('copiar');
    } catch (err) {
      console.error('[FluxogramaExames] falha ao copiar', err.message);
      // fallback simples
      try { await navigator.clipboard.writeText(tsv); setCopiado(true); setTimeout(() => setCopiado(false), 2000); }
      catch (_) { mostrarAviso('Não foi possível copiar. Tente exportar o PDF.'); }
    }
  };

  // ── Exportar PDF: abre janela só com a tabela e dispara impressão (Salvar como PDF) ──
  const exportarPDF = () => {
    if (!tabelaRef.current) return;
    const geradoEm = new Date().toLocaleDateString('pt-BR', { day: '2-digit', month: 'long', year: 'numeric' });
    // Clona a tabela e remove a coluna de seleção (checkboxes) — não vai pro PDF.
    const clone = tabelaRef.current.cloneNode(true);
    clone.querySelectorAll('.fx-sel').forEach(el => el.remove());
    const tabelaHTML = clone.outerHTML;
    const win = window.open('', '_blank');
    if (!win) { mostrarAviso('Permita pop-ups neste site para exportar o PDF.'); return; }
    win.document.write(`<!DOCTYPE html><html lang="pt-BR"><head><meta charset="UTF-8"><title>Fluxograma de Exames — ${patientName}</title>
      <style>
        @page { size: landscape; margin: 14mm; }
        body { font-family: -apple-system, 'Segoe UI', Roboto, sans-serif; color: #1A2B1E; margin: 0; }
        h1 { font-size: 18px; margin: 0 0 2px; }
        .meta { font-size: 11px; color: #8FAA96; margin: 0 0 16px; }
        table { border-collapse: collapse; width: 100%; font-size: 11px; }
        th, td { border: 1px solid #DDD8CE; padding: 5px 9px; text-align: center; white-space: nowrap; }
        thead th { background: #EAF2ED; color: #2C4A35; }
        td.exam { text-align: left; font-weight: 600; }
        tr.grp td { background: #F5F0E8; font-weight: 700; text-align: left; text-transform: uppercase; letter-spacing: .04em; font-size: 10px; }
        -webkit-print-color-adjust: exact; print-color-adjust: exact;
      </style></head><body>
      <h1>Fluxograma de Exames</h1>
      <p class="meta">${patientName} · Gerado em ${geradoEm}</p>
      ${tabelaHTML}
      <script>window.onload = function(){ window.print(); setTimeout(function(){ window.close(); }, 300); };<\/script>
      </body></html>`);
    win.document.close();
    registrarExportacao('pdf');
  };

  return (
    <div>
      {/* Toast de aviso (erros pontuais de exportação) */}
      {aviso && (
        <div role="status" aria-live="polite"
          style={{position: 'fixed', left: '50%', bottom: 24, transform: 'translateX(-50%)', zIndex: 400, display: 'flex', alignItems: 'center', gap: 9, maxWidth: 360, padding: '11px 16px', background: PP.text, color: PP.white, borderRadius: 12, boxShadow: '0 12px 32px rgba(30,51,38,0.32)', fontSize: 13, lineHeight: 1.4, animation: 'ppToastIn 0.25s cubic-bezier(0.34,1.3,0.64,1)'}}>
          <style>{`@keyframes ppToastIn{from{opacity:0;transform:translateX(-50%) translateY(12px)}to{opacity:1;transform:translateX(-50%) translateY(0)}}`}</style>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#F5C5C5" strokeWidth="2" style={{flexShrink: 0}}><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
          <span>{aviso}</span>
        </div>
      )}

      {/* Barra de ações */}
      <div style={{display: 'flex', justifyContent: 'flex-end', gap: 8, marginBottom: 12}}>
        <button onClick={copiar}
          style={{display: 'inline-flex', alignItems: 'center', gap: 6, padding: '7px 13px', borderRadius: 9, border: `1.5px solid ${copiado ? PP.sage : PP.border}`, background: copiado ? PP.sagePale : PP.white, color: copiado ? PP.sageDark : PP.textMid, fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', transition: 'all 0.15s'}}>
          {copiado ? (
            <><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><polyline points="20 6 9 17 4 12"/></svg>Copiado!</>
          ) : (
            <><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>Copiar</>
          )}
        </button>
        <button onClick={exportarPDF}
          style={{display: 'inline-flex', alignItems: 'center', gap: 6, padding: '7px 13px', borderRadius: 9, border: 'none', background: PP.sageDark, color: PP.white, fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit'}}>
          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
          Exportar PDF
        </button>
      </div>

      {/* Tabela com scroll horizontal */}
      <div style={{overflowX: 'auto', borderRadius: 12, border: `1.5px solid ${PP.borderLight}`}}>
        <table ref={tabelaRef} style={{borderCollapse: 'separate', borderSpacing: 0, width: '100%', fontSize: 12}}>
          <thead>
            <tr>
              <th className="fx-sel" style={{padding: '10px 8px', background: PP.sageDark, borderBottom: `1.5px solid #B8D9C4`, position: 'sticky', left: 0, zIndex: 4}} title={todosMarcados ? 'Desmarcar todos' : 'Marcar todos'}>
                <input type="checkbox" checked={todosMarcados} onChange={marcarTodos}
                  style={{width: 15, height: 15, accentColor: PP.sage, cursor: 'pointer', display: 'block'}}/>
              </th>
              <th style={{textAlign: 'left', padding: '10px 12px', background: PP.sageDark, color: PP.white, fontWeight: 700, fontSize: 11, position: 'sticky', left: 31, zIndex: 3, minWidth: 150, borderBottom: `1.5px solid #B8D9C4`}}>Exame</th>
              {datas.map((d, i) => {
                const f = fmtData(d);
                return (
                  <th key={i} style={{padding: '10px 12px', background: PP.sagePale, color: PP.sageDark, fontWeight: 700, fontSize: 11, textAlign: 'center', whiteSpace: 'nowrap', borderBottom: `1.5px solid #B8D9C4`}}>
                    {f.curta}<span style={{display: 'block', fontSize: 9, color: PP.textMuted, fontWeight: 400}}>{f.ano}</span>
                  </th>
                );
              })}
            </tr>
          </thead>
          <tbody>
            {grupos.map(g => (
              <React.Fragment key={g.nome}>
                <tr className="grp">
                  <td colSpan={datas.length + 2} style={{padding: '7px 12px', background: PP.sagePale, color: PP.sageDark, fontWeight: 700, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.04em', textAlign: 'left', position: 'sticky', left: 0}}>
                    {g.nome}
                  </td>
                </tr>
                {g.exames.map((ex, ri) => {
                  const key = normNomePP(ex.nome);
                  const marcado = selecionados.has(key);
                  return (
                  <tr key={ex.nome + ri} style={{opacity: marcado ? 1 : 0.45}}>
                    <td className="fx-sel" style={{padding: '8px', background: PP.white, position: 'sticky', left: 0, zIndex: 2, borderBottom: `1px solid ${PP.borderLight}`, textAlign: 'center'}}>
                      <input type="checkbox" checked={marcado} onChange={() => toggleExame(key)}
                        style={{width: 15, height: 15, accentColor: PP.sage, cursor: 'pointer', display: 'block', margin: '0 auto'}}/>
                    </td>
                    <td className="exam" style={{textAlign: 'left', fontWeight: 600, color: PP.text, background: PP.white, position: 'sticky', left: 31, zIndex: 1, padding: '8px 12px', borderRight: `1.5px solid ${PP.borderLight}`, borderBottom: `1px solid ${PP.borderLight}`, whiteSpace: 'nowrap'}}>
                      {ex.nome}{ex.unidade && <span style={{fontSize: 10, color: PP.textMuted, fontWeight: 400, marginLeft: 4}}>{ex.unidade}</span>}
                    </td>
                    {datas.map((d, ci) => {
                      const cel = ex.valores[d];
                      return (
                        <td key={ci} style={{padding: '8px 12px', textAlign: 'center', whiteSpace: 'nowrap', borderBottom: `1px solid ${PP.borderLight}`}}>
                          {cel ? <span style={{fontWeight: 700, color: corStatus(cel.status)}}>{cel.valorStr}</span> : null}
                        </td>
                      );
                    })}
                  </tr>
                  );
                })}
              </React.Fragment>
            ))}
          </tbody>
        </table>
      </div>

      {/* Lista extra: exames de texto (rastreios / demais) que não aparecem na tabela numérica */}
      {soTexto.length > 0 && (
        <div style={{marginTop: 12, padding: '12px 14px', background: PP.bg, borderRadius: 12, border: `1.5px solid ${PP.borderLight}`}}>
          <div style={{fontSize: 11, fontWeight: 700, color: PP.sageDark, textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: 8}}>
            Outros exames (resultado em texto)
          </div>
          <div style={{display: 'flex', flexWrap: 'wrap', gap: '8px 18px'}}>
            {soTexto.map(e => (
              <label key={e.key} style={{display: 'inline-flex', alignItems: 'center', gap: 7, fontSize: 12.5, color: PP.text, cursor: 'pointer'}}>
                <input type="checkbox" checked={selecionados.has(e.key)} onChange={() => toggleExame(e.key)}
                  style={{width: 15, height: 15, accentColor: PP.sage, cursor: 'pointer'}}/>
                {e.nome}
              </label>
            ))}
          </div>
        </div>
      )}

      <p style={{margin: '12px 2px 0', fontSize: 11, color: PP.textMuted, lineHeight: 1.6}}>
        Marque os exames que deseja copiar (todos marcados por padrão). ·{' '}
        <span style={{color: PP.red, fontWeight: 700}}>vermelho</span> = alto ·{' '}
        <span style={{color: PP.blue, fontWeight: 700}}>azul</span> = baixo · preto = normal · célula em branco = exame não realizado naquela data.
      </p>
    </div>
  );
};

// ── PatientProfile (componente principal) ─────────────────────────────────────
const PatientProfile = ({ envId, name, onBack, onOpenSubmission }) => {
  const [submissions, setSubmissions] = useStatePP([]);
  const [loading, setLoading] = useStatePP(true);
  const [erro, setErro] = useStatePP(null);
  const [examesSelecionados, setExamesSelecionados] = useStatePP([]);
  const [historicoAberto, setHistoricoAberto] = useStatePP(false);
  const [dashboardAberto, setDashboardAberto] = useStatePP(false);
  const [fluxogramaAberto, setFluxogramaAberto] = useStatePP(false);
  const [showSubmissionModal, setShowSubmissionModal] = useStatePP(false);

  useEffectPP(() => {
    setLoading(true);
    setErro(null);
    window.API.patients.history(envId, name)
      .then(data => { setSubmissions(data); setLoading(false); })
      .catch(err => { console.error('[PatientProfile]', err.message); setErro(err.message); setLoading(false); });
  }, [envId, name]);

  const recarregarSubmissions = useCallbackPP(() => {
    window.API.patients.history(envId, name)
      .then(data => setSubmissions(data))
      .catch(err => console.error('[PatientProfile] erro ao recarregar', err.message));
  }, [envId, name]);

  const handleDeleteSubmission = useCallbackPP((id) => {
    setSubmissions(prev => prev.filter(s => s.id !== id));
  }, []);

  const { exames: examesDisponiveis } = useMemoP(
    () => construirExamesDisponiveis(submissions),
    [submissions]
  );

  const handleVerAnalise = (lista) => setExamesSelecionados(lista);

  const handleAddExame = (exame) => {
    setExamesSelecionados(prev =>
      prev.some(e => e.nome === exame.nome) ? prev : [...prev, exame]
    );
  };

  const handleRemoveExame = (nomeExame) => {
    setExamesSelecionados(prev => prev.filter(e => e.nome !== nomeExame));
  };

  const handleCloseModal = useCallbackPP(() => setExamesSelecionados([]), []);

  const primeiro = submissions.length > 0 ? submissions[submissions.length - 1] : null;
  const ultimo   = submissions.length > 0 ? submissions[0] : null;
  const sub0     = primeiro || {};
  const dob      = fmtDataNascimento(sub0.patient_dob);
  const cpf      = sub0.patient_cpf;

  if (loading) {
    return (
      <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', gap: 10, color: PP.textMuted, fontSize: 13}}>
        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={PP.sage} strokeWidth="2.5" style={{animation: 'ppSpin 1s linear infinite'}}><path d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0"/></svg>
        Carregando histórico...
      </div>
    );
  }

  if (erro) {
    return (
      <div style={{display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', gap: 8, color: PP.red, fontSize: 13}}>
        <span>Não foi possível carregar o histórico.</span>
        <span style={{fontSize: 11, color: PP.textMuted}}>{erro}</span>
      </div>
    );
  }

  return (
    <div style={{maxWidth: 860, margin: '0 auto', padding: '24px 20px', display: 'flex', flexDirection: 'column', gap: 24}}>
      <style>{`
  @keyframes ppSpin { to { transform: rotate(360deg); } }
  @keyframes ppSlideDown { from { opacity: 0; transform: translateY(-12px); } to { opacity: 1; transform: translateY(0); } }
  .ppComment { border-radius:13px; padding:13px 15px; font-size:13px; line-height:1.6; display:flex; gap:10px; align-items:flex-start; }
  .ppComment.good { background:#EAF2ED; border:1.5px solid #B8D9C4; color:#2C4A35; }
  .ppComment.bad  { background:#FFF0F0; border:1.5px solid #F3C9C9; color:#7A2E2E; }
  .ppComment.neutral { background:#FBF5E6; border:1.5px solid #E8D5A0; color:#7A5B1E; }
`}</style>

      {/* Cabeçalho do paciente */}
      <div style={{background: PP.white, border: `1.5px solid ${PP.borderLight}`, borderRadius: 16, padding: '20px 22px', display: 'flex', alignItems: 'flex-start', gap: 16, boxShadow: '0 2px 8px rgba(30,51,38,0.05)'}}>
        {/* Avatar */}
        <div style={{width: 52, height: 52, borderRadius: '50%', background: PP.sageDark, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0}}>
          <span style={{fontSize: 20, fontWeight: 700, color: PP.white, fontFamily: 'Lora, serif'}}>
            {name.trim().split(/\s+/).slice(0, 2).map(w => w[0]).join('').toUpperCase()}
          </span>
        </div>

        <div style={{flex: 1, minWidth: 0}}>
          <div style={{display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', marginBottom: 6}}>
            <span style={{fontSize: 18, fontWeight: 700, color: PP.text, fontFamily: 'Lora, serif'}}>{name}</span>
            <span style={{fontSize: 11, color: PP.sageDark, background: PP.sagePale, borderRadius: 20, padding: '2px 10px', border: `1px solid #B8D9C4`, fontWeight: 600}}>
              {submissions.length} envio{submissions.length !== 1 ? 's' : ''}
            </span>
          </div>
          <div style={{display: 'flex', flexWrap: 'wrap', gap: '4px 16px', fontSize: 12, color: PP.textMid}}>
            {dob && <span>Nasc.: {dob}</span>}
            {cpf  && <span>CPF: {cpf}</span>}
            {primeiro && <span>Primeiro envio: {fmtDataCurta(primeiro.submitted_at)}</span>}
            {ultimo && ultimo !== primeiro && <span>Último envio: {fmtDataCurta(ultimo.submitted_at)}</span>}
          </div>
        </div>

        <button onClick={onBack}
          style={{padding: '6px 14px', borderRadius: 9, border: `1.5px solid ${PP.border}`, background: PP.white, color: PP.textMid, fontSize: 12, fontWeight: 600, cursor: 'pointer', flexShrink: 0, fontFamily: 'inherit', display: 'flex', alignItems: 'center', gap: 6}}
          onMouseEnter={e => { e.currentTarget.style.borderColor = PP.sage; e.currentTarget.style.color = PP.sage; }}
          onMouseLeave={e => { e.currentTarget.style.borderColor = PP.border; e.currentTarget.style.color = PP.textMid; }}>
          <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><polyline points="15 18 9 12 15 6"/></svg>
          Voltar
        </button>
      </div>

      {submissions.length === 0 ? (
        <div style={{textAlign: 'center', color: PP.textMuted, fontSize: 13, padding: '40px 0'}}>Nenhum envio encontrado para este paciente.</div>
      ) : (
        <>
          {/* Botões lado a lado: Histórico de Envios + Dashboard Exames + Fluxograma Exames */}
          <div style={{display: 'flex', flexDirection: 'column', gap: 0}}>
            {/* Linha de botões */}
            <div style={{display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 10}}>
              {/* Botão Histórico de Envios */}
              <button
                onClick={() => setHistoricoAberto(v => { const next = !v; if (next) { setDashboardAberto(false); setFluxogramaAberto(false); } return next; })}
                style={{
                  display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                  padding: '12px 16px',
                  background: historicoAberto ? PP.sagePale : PP.white,
                  border: `1.5px solid ${historicoAberto ? '#B8D9C4' : PP.borderLight}`,
                  borderRadius: historicoAberto ? '12px 12px 0 0' : 12,
                  cursor: 'pointer', fontFamily: 'inherit',
                  transition: 'all 0.15s',
                }}
                onMouseEnter={e => { e.currentTarget.style.borderColor = PP.sage; }}
                onMouseLeave={e => { e.currentTarget.style.borderColor = historicoAberto ? '#B8D9C4' : PP.borderLight; }}
              >
                <span style={{fontSize: 13, fontWeight: 700, color: PP.sageDark}}>Histórico de Envios</span>
                <div style={{display: 'flex', alignItems: 'center', gap: 6}}>
                  <span style={{fontSize: 11, color: PP.textMuted, background: PP.white, borderRadius: 20, padding: '1px 8px', border: `1px solid ${PP.border}`}}>
                    {submissions.length}
                  </span>
                  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={PP.textMuted} strokeWidth="2.5"
                    style={{transition: 'transform 0.2s', transform: historicoAberto ? 'rotate(180deg)' : 'rotate(0deg)', flexShrink: 0}}>
                    <polyline points="6 9 12 15 18 9"/>
                  </svg>
                </div>
              </button>

              {/* Botão Dashboard Exames */}
              <button
                onClick={() => setDashboardAberto(v => { const next = !v; if (next) { setHistoricoAberto(false); setFluxogramaAberto(false); } return next; })}
                style={{
                  display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                  padding: '12px 16px',
                  background: dashboardAberto ? PP.sagePale : PP.white,
                  border: `1.5px solid ${dashboardAberto ? '#B8D9C4' : PP.borderLight}`,
                  borderRadius: dashboardAberto ? '12px 12px 0 0' : 12,
                  cursor: 'pointer', fontFamily: 'inherit',
                  transition: 'all 0.15s',
                }}
                onMouseEnter={e => { e.currentTarget.style.borderColor = PP.sage; }}
                onMouseLeave={e => { e.currentTarget.style.borderColor = dashboardAberto ? '#B8D9C4' : PP.borderLight; }}
              >
                <span style={{fontSize: 13, fontWeight: 700, color: PP.sageDark}}>Dashboard Exames</span>
                <div style={{display: 'flex', alignItems: 'center', gap: 6}}>
                  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={PP.textMuted} strokeWidth="2.5"
                    style={{transition: 'transform 0.2s', transform: dashboardAberto ? 'rotate(180deg)' : 'rotate(0deg)', flexShrink: 0}}>
                    <polyline points="6 9 12 15 18 9"/>
                  </svg>
                </div>
              </button>

              {/* Botão Fluxograma Exames */}
              <button
                onClick={() => setFluxogramaAberto(v => { const next = !v; if (next) { setHistoricoAberto(false); setDashboardAberto(false); } return next; })}
                style={{
                  display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                  padding: '12px 16px',
                  background: fluxogramaAberto ? PP.sagePale : PP.white,
                  border: `1.5px solid ${fluxogramaAberto ? '#B8D9C4' : PP.borderLight}`,
                  borderRadius: fluxogramaAberto ? '12px 12px 0 0' : 12,
                  cursor: 'pointer', fontFamily: 'inherit',
                  transition: 'all 0.15s',
                }}
                onMouseEnter={e => { e.currentTarget.style.borderColor = PP.sage; }}
                onMouseLeave={e => { e.currentTarget.style.borderColor = fluxogramaAberto ? '#B8D9C4' : PP.borderLight; }}
              >
                <span style={{fontSize: 13, fontWeight: 700, color: PP.sageDark}}>Fluxograma Exames</span>
                <div style={{display: 'flex', alignItems: 'center', gap: 6}}>
                  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={PP.textMuted} strokeWidth="2.5"
                    style={{transition: 'transform 0.2s', transform: fluxogramaAberto ? 'rotate(180deg)' : 'rotate(0deg)', flexShrink: 0}}>
                    <polyline points="6 9 12 15 18 9"/>
                  </svg>
                </div>
              </button>
            </div>

            {/* Conteúdo: Histórico de Envios */}
            {historicoAberto && (
              <div style={{
                border: `1.5px solid #B8D9C4`, borderTop: 'none',
                borderRadius: '0 0 12px 12px',
                background: PP.white,
                overflow: 'hidden',
                animation: 'ppSlideDown 0.2s cubic-bezier(0.16,1,0.3,1)',
                marginTop: 0,
              }}>
                <div style={{display: 'flex', flexDirection: 'column', gap: 8, padding: '12px'}}>
                  <button
                    onClick={() => setShowSubmissionModal(true)}
                    style={{
                      display: 'flex', alignItems: 'center', gap: 6,
                      padding: '8px 14px', borderRadius: 10,
                      border: `1.5px dashed ${PP.sage}`,
                      background: 'none', color: PP.sageDark,
                      fontSize: 12, fontWeight: 600, cursor: 'pointer',
                      fontFamily: 'inherit', alignSelf: 'flex-start',
                      marginBottom: 4,
                    }}
                    onMouseEnter={e => { e.currentTarget.style.background = PP.sagePale; }}
                    onMouseLeave={e => { e.currentTarget.style.background = 'none'; }}>
                    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
                    Novo envio
                  </button>
                  {submissions.map(sub => (
                    <SubmissionItem key={sub.id} sub={sub} onOpen={onOpenSubmission} onDelete={handleDeleteSubmission}/>
                  ))}
                </div>
              </div>
            )}

            {/* Conteúdo: Dashboard Exames */}
            {dashboardAberto && (
              <div style={{
                border: `1.5px solid #B8D9C4`, borderTop: 'none',
                borderRadius: '0 0 12px 12px',
                background: PP.white,
                overflow: 'hidden',
                animation: 'ppSlideDown 0.2s cubic-bezier(0.16,1,0.3,1)',
                padding: '16px',
              }}>
                <ExamPills submissions={submissions} onVerAnalise={handleVerAnalise}/>
              </div>
            )}

            {/* Conteúdo: Fluxograma Exames */}
            {fluxogramaAberto && (
              <div style={{
                border: `1.5px solid #B8D9C4`, borderTop: 'none',
                borderRadius: '0 0 12px 12px',
                background: PP.white,
                overflow: 'hidden',
                animation: 'ppSlideDown 0.2s cubic-bezier(0.16,1,0.3,1)',
                padding: '16px',
              }}>
                <FluxogramaExames submissions={submissions} patientName={name}/>
              </div>
            )}
          </div>

          {examesSelecionados.length > 0 && (
            <ExamDashboardModal
              exames={examesSelecionados}
              examesDisponiveis={examesDisponiveis}
              onClose={handleCloseModal}
              onRemove={handleRemoveExame}
              onAdd={handleAddExame}
            />
          )}
        </>
      )}

      {showSubmissionModal && (
        <SubmissionModal
          modo="novo-envio"
          envId={envId}
          patient={{
            name,
            dob: sub0.patient_dob
              ? (sub0.patient_dob.toISOString ? sub0.patient_dob.toISOString().slice(0,10) : String(sub0.patient_dob).slice(0,10))
              : '',
            cpf: cpf || '',
          }}
          onClose={() => setShowSubmissionModal(false)}
          onSuccess={recarregarSubmissions}
        />
      )}
    </div>
  );
};

// ── SubmissionModal ────────────────────────────────────────────────────────────
// modo='novo-paciente': todos os campos editáveis + upload + consentimento
// modo='novo-envio':    dados pré-preenchidos (readonly) + upload + consentimento
const SubmissionModal = ({ modo, envId, patient, onClose, onSuccess }) => {
  const [info, setInfoSM]   = useStatePP(
    patient
      ? { name: patient.name || '', dob: patient.dob || '', cpf: patient.cpf || '' }
      : { name: '', dob: '', cpf: '' }
  );
  const [files, setFilesSM]       = useStatePP([]);
  const [consent, setConsentSM]   = useStatePP(false);
  const [sending, setSendingSM]   = useStatePP(false);
  const [sendError, setSendErrorSM] = useStatePP(null);
  const [showCamDicas, setShowCamDicas] = useStatePP(false);
  const [showPrivacy, setShowPrivacySM] = useStatePP(false);

  const pdfRefSM = useRefPP(null);
  const galRefSM = useRefPP(null);
  const camRefSM = useRefPP(null);

  useEffectPP(() => {
    const handler = (e) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', handler);
    return () => document.removeEventListener('keydown', handler);
  }, [onClose]);

  const readonly = modo === 'novo-envio';
  const MAX_FILE_BYTES = 20 * 1024 * 1024;

  const handleFilesSM = useCallbackPP(async (fileList) => {
    const newFiles = await Promise.all(Array.from(fileList).map(async (file) => {
      const compressed = await compressImage(file);
      const quality    = await analyzeImageQuality(compressed);
      const tooBig     = compressed.size > MAX_FILE_BYTES;
      return {
        id: Math.random().toString(36).slice(2),
        name: compressed.name,
        size: compressed.size,
        isImage: compressed.type.startsWith('image/'),
        preview: compressed.type.startsWith('image/') ? URL.createObjectURL(compressed) : null,
        warnings: tooBig
          ? [...quality.warnings, 'Arquivo acima de 20 MB — remova-o para poder enviar.']
          : quality.warnings,
        wasCompressed: compressed !== file,
        tooBig,
        file: compressed,
      };
    }));
    setFilesSM(prev => [...prev, ...newFiles]);
  }, []);

  const removeFileSM = (id) => setFilesSM(prev => prev.filter(f => f.id !== id));

  const hasTooBig = files.some(f => f.tooBig);
  const nameOk    = info.name.trim().length > 0;
  const canSend   = files.length > 0 && consent && !sending && !hasTooBig && nameOk;

  const doSubmitSM = useCallbackPP(async () => {
    if (!canSend || !window.API) return;
    setSendingSM(true); setSendErrorSM(null);
    try {
      let dob;
      if (info.dob && info.dob.length === 10) {
        if (info.dob.includes('-')) {
          dob = info.dob;
        } else {
          const [d, m, y] = info.dob.split('/');
          dob = `${y}-${m}-${d}`;
        }
      }
      await window.API.patient.submit({
        env_id:       envId || '',
        patient_name: info.name || 'Paciente',
        patient_dob:  dob,
        patient_cpf:  info.cpf || undefined,
        files,
      });
      onSuccess();
      onClose();
    } catch (err) {
      const msgs = {
        'network':      'Sem conexão estável. Seus arquivos continuam selecionados — tente novamente.',
        'invalid-link': 'Ambiente inválido ou sem permissão.',
        'too-large':    'Algum arquivo é grande demais (máximo 20 MB). Remova-o e tente novamente.',
        'server':       'Tivemos um problema ao processar o envio. Tente novamente em instantes.',
      };
      setSendErrorSM(msgs[err && err.kind] || 'Não foi possível enviar. Tente novamente.');
    } finally {
      setSendingSM(false);
    }
  }, [canSend, envId, info, files]);

  const inputSM = (readonlyField) => ({
    width: '100%', padding: '10px 13px', borderRadius: 10,
    border: `1.5px solid ${PP.border}`, fontSize: 16, color: PP.text,
    background: readonlyField ? PP.bg : PP.white,
    boxSizing: 'border-box', outline: 'none', fontFamily: 'inherit',
    cursor: readonlyField ? 'default' : 'text',
    opacity: readonlyField ? 0.8 : 1,
  });

  const titulo = modo === 'novo-paciente'
    ? 'Novo paciente'
    : `Novo envio — ${info.name}`;

  const PrivacyModalSM = () => (
    <div style={{position:'fixed',inset:0,background:'rgba(0,0,0,0.6)',zIndex:200,display:'flex',alignItems:'center',justifyContent:'center',padding:16}}
      onClick={e => { if (e.target === e.currentTarget) setShowPrivacySM(false); }}>
      <div style={{background:PP.white,borderRadius:20,padding:'28px 24px',maxWidth:480,width:'100%',maxHeight:'80vh',overflowY:'auto',boxShadow:'0 20px 60px rgba(0,0,0,0.2)'}}>
        <h3 style={{fontSize:17,fontWeight:700,color:PP.text,margin:'0 0 16px',fontFamily:'Lora, serif'}}>Política de Privacidade</h3>
        {[
          ['Quem somos', 'Este formulário é operado pelo profissional de saúde responsável por este ambiente. Ele é o controlador dos dados coletados.'],
          ['O que coletamos', 'Coletamos nome, data de nascimento, CPF (opcional) e os documentos de exames enviados.'],
          ['Para que usamos', 'Os dados são usados exclusivamente para análise clínica pelo profissional responsável.'],
          ['Por quanto tempo', 'Seus dados são armazenados por até 5 anos ou pelo prazo mínimo exigido pelo CFM, após o que são excluídos.'],
          ['Seus direitos (LGPD)', 'Você pode solicitar acesso, correção ou exclusão dos seus dados a qualquer momento.'],
          ['Compartilhamento', 'Seus dados não são vendidos ou compartilhados com terceiros para fins comerciais.'],
          ['Segurança', 'Os dados são transmitidos via HTTPS e armazenados em servidores seguros com controle de acesso.'],
        ].map(([title, text]) => (
          <div key={title} style={{marginBottom:16}}>
            <div style={{fontSize:12,fontWeight:700,color:PP.textMid,textTransform:'uppercase',letterSpacing:'0.06em',marginBottom:4}}>{title}</div>
            <div style={{fontSize:13,color:PP.text,lineHeight:1.65}}>{text}</div>
          </div>
        ))}
        <button onClick={() => setShowPrivacySM(false)}
          style={{width:'100%',padding:'13px',borderRadius:12,border:'none',background:PP.sageDark,color:PP.white,fontSize:14,fontWeight:700,cursor:'pointer',fontFamily:'inherit',marginTop:8}}>
          Fechar
        </button>
      </div>
    </div>
  );

  return (
    <div onClick={onClose}
      style={{position:'fixed',inset:0,background:'rgba(30,51,38,0.45)',backdropFilter:'blur(2px)',display:'flex',alignItems:'flex-start',justifyContent:'center',zIndex:100,padding:'16px',paddingTop:'env(safe-area-inset-top, 16px)',overflowY:'auto'}}>

      {/* CameraGuideModal removido — setTimeout quebra o gesto do usuario no mobile */}
      {showPrivacy && <PrivacyModalSM />}

      <div onClick={e => e.stopPropagation()}
        style={{background:PP.white,borderRadius:20,boxShadow:'0 24px 64px rgba(30,51,38,0.22)',width:'100%',maxWidth:480,overflowY:'auto',display:'flex',flexDirection:'column',border:`1px solid ${PP.borderLight}`,marginTop:'auto',marginBottom:'auto',flexShrink:0}}>

        <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',padding:'18px 20px 14px',borderBottom:`1px solid ${PP.borderLight}`,flexShrink:0}}>
          <div style={{display:'flex',alignItems:'center',gap:10}}>
            <div style={{width:36,height:36,borderRadius:10,background:PP.sagePale,display:'flex',alignItems:'center',justifyContent:'center',border:`1.5px solid #B8D9C4`}}>
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={PP.sage} strokeWidth="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
            </div>
            <span style={{fontSize:15,fontWeight:700,color:PP.text,fontFamily:'Lora, serif'}}>{titulo}</span>
          </div>
          <button onClick={onClose}
            style={{width:30,height:30,borderRadius:'50%',border:'none',background:PP.creamDark,color:PP.textMuted,cursor:'pointer',display:'flex',alignItems:'center',justifyContent:'center',fontSize:18,lineHeight:1,padding:0}}>
            ×
          </button>
        </div>

        <div style={{padding:'16px 20px',display:'flex',flexDirection:'column',gap:14}}>

          <div>
            <div style={{fontSize:11,fontWeight:700,color:PP.textMid,textTransform:'uppercase',letterSpacing:'0.08em',marginBottom:10}}>
              Dados do paciente
              {readonly && <span style={{marginLeft:8,fontWeight:400,textTransform:'none',fontSize:10,color:PP.textMuted}}>(preenchido automaticamente)</span>}
            </div>
            <div style={{display:'flex',flexDirection:'column',gap:10}}>
              <div>
                <label style={{display:'block',fontSize:11,fontWeight:600,color:PP.textMid,marginBottom:5}}>Nome completo {!readonly && '*'}</label>
                <input
                  style={inputSM(readonly)}
                  type="text"
                  placeholder="Maria da Silva"
                  value={info.name}
                  readOnly={readonly}
                  onChange={e => !readonly && setInfoSM(i => ({...i, name: e.target.value}))}
                />
              </div>
              <div className="modal-grid-2">
                <div>
                  <label style={{display:'block',fontSize:11,fontWeight:600,color:PP.textMid,marginBottom:5}}>Nascimento</label>
                  <input
                    style={inputSM(readonly)}
                    type={readonly ? 'text' : 'date'}
                    value={info.dob}
                    readOnly={readonly}
                    onChange={e => !readonly && setInfoSM(i => ({...i, dob: e.target.value}))}
                  />
                </div>
                <div>
                  <label style={{display:'block',fontSize:11,fontWeight:600,color:PP.textMid,marginBottom:5}}>CPF</label>
                  <input
                    style={inputSM(readonly)}
                    type="text"
                    placeholder="000.000.000-00"
                    value={info.cpf}
                    readOnly={readonly}
                    inputMode="numeric"
                    maxLength={14}
                    onChange={e => {
                      if (readonly) return;
                      let v = e.target.value.replace(/\D/g, '').slice(0, 11);
                      if (v.length > 9) v = v.slice(0,3) + '.' + v.slice(3,6) + '.' + v.slice(6,9) + '-' + v.slice(9);
                      else if (v.length > 6) v = v.slice(0,3) + '.' + v.slice(3,6) + '.' + v.slice(6);
                      else if (v.length > 3) v = v.slice(0,3) + '.' + v.slice(3);
                      setInfoSM(i => ({...i, cpf: v}));
                    }}
                  />
                </div>
              </div>
            </div>
          </div>

          <div>
            <div style={{fontSize:11,fontWeight:700,color:PP.textMid,textTransform:'uppercase',letterSpacing:'0.08em',marginBottom:6}}>Anexar documentos</div>
            <p style={{fontSize:11,color:PP.textMuted,margin:'0 0 12px'}}>PDF, JPG ou PNG — máximo 20 MB por arquivo</p>
            <div style={{display:'grid',gridTemplateColumns:'1fr 1fr 1fr',gap:8}}>
              <button onClick={() => pdfRefSM.current.click()}
                style={{display:'flex',flexDirection:'column',alignItems:'center',gap:8,padding:'16px 8px',borderRadius:12,border:`1.5px solid ${PP.borderLight}`,background:PP.bg,cursor:'pointer',transition:'all 0.15s',fontFamily:'inherit'}}
                onMouseEnter={e=>{e.currentTarget.style.borderColor=PP.sage;e.currentTarget.style.background=PP.sagePale;}}
                onMouseLeave={e=>{e.currentTarget.style.borderColor=PP.borderLight;e.currentTarget.style.background=PP.bg;}}>
                <div style={{width:40,height:40,borderRadius:10,background:PP.redLight,display:'flex',alignItems:'center',justifyContent:'center'}}>
                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={PP.red} strokeWidth="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
                </div>
                <span style={{fontSize:10,fontWeight:600,color:PP.textMid,textAlign:'center',lineHeight:1.3}}>Arquivo PDF</span>
              </button>

              <button onClick={() => galRefSM.current.click()}
                style={{display:'flex',flexDirection:'column',alignItems:'center',gap:8,padding:'16px 8px',borderRadius:12,border:`1.5px solid ${PP.borderLight}`,background:PP.bg,cursor:'pointer',transition:'all 0.15s',fontFamily:'inherit'}}
                onMouseEnter={e=>{e.currentTarget.style.borderColor=PP.sage;e.currentTarget.style.background=PP.sagePale;}}
                onMouseLeave={e=>{e.currentTarget.style.borderColor=PP.borderLight;e.currentTarget.style.background=PP.bg;}}>
                <div style={{width:40,height:40,borderRadius:10,background:PP.sagePale,display:'flex',alignItems:'center',justifyContent:'center'}}>
                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={PP.sage} strokeWidth="1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
                </div>
                <span style={{fontSize:10,fontWeight:600,color:PP.textMid,textAlign:'center',lineHeight:1.3}}>Foto da galeria</span>
              </button>

              {/* Tirar foto: aciona camera diretamente no gesto do usuario */}
              <label style={{display:'flex',flexDirection:'column',alignItems:'center',gap:8,padding:'16px 8px',borderRadius:12,border:`1.5px solid ${PP.borderLight}`,background:PP.bg,cursor:'pointer',transition:'all 0.15s',fontFamily:'inherit'}}
                onMouseEnter={e=>{e.currentTarget.style.borderColor=PP.sage;e.currentTarget.style.background=PP.sagePale;}}
                onMouseLeave={e=>{e.currentTarget.style.borderColor=PP.borderLight;e.currentTarget.style.background=PP.bg;}}>
                <input ref={camRefSM} type="file" accept="image/*" capture="environment" style={{display:'none'}} onChange={e=>{handleFilesSM(e.target.files);e.target.value='';}}/>
                <div style={{width:40,height:40,borderRadius:10,background:PP.amberLight,display:'flex',alignItems:'center',justifyContent:'center'}}>
                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={PP.amber} strokeWidth="1.5"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
                </div>
                <span style={{fontSize:10,fontWeight:600,color:PP.textMid,textAlign:'center',lineHeight:1.3}}>Tirar foto</span>
              </label>
            </div>

            {/* Dicas de foto — expansível, sem bloquear a câmera */}
            <button onClick={() => setShowCamDicas(v => !v)}
              style={{display:'flex',alignItems:'center',gap:6,background:'none',border:'none',cursor:'pointer',padding:'2px 0',fontFamily:'inherit'}}>
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={PP.textMuted} strokeWidth="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
              <span style={{fontSize:11,color:PP.textMuted,textDecoration:'underline',textDecorationStyle:'dotted'}}>
                {showCamDicas ? 'Ocultar dicas de foto' : 'Ver dicas para fotos melhores'}
              </span>
            </button>
            {showCamDicas && (
              <div style={{background:'#FBF5E6',border:'1px solid #E8D5A0',borderRadius:10,padding:'10px 14px',display:'flex',flexDirection:'column',gap:6}}>
                {[
                  {ico:'💡', txt:'Fotografe em local bem iluminado — luz natural é ideal'},
                  {ico:'📐', txt:'Segure o celular paralelo ao papel, sem inclinar'},
                  {ico:'🔍', txt:'Enquadre todo o documento — margens incluídas'},
                  {ico:'🤚', txt:'Apoie o cotovelo para evitar tremido'},
                ].map(d => (
                  <div key={d.txt} style={{display:'flex',alignItems:'flex-start',gap:8,fontSize:12,color:'#6B4F10',lineHeight:1.5}}>
                    <span>{d.ico}</span><span>{d.txt}</span>
                  </div>
                ))}
              </div>
            )}

            <input ref={pdfRefSM} type="file" accept=".pdf,application/pdf" multiple style={{display:'none'}} onChange={e=>{handleFilesSM(e.target.files);e.target.value='';}}/>
            <input ref={galRefSM} type="file" accept="image/*" multiple style={{display:'none'}} onChange={e=>{handleFilesSM(e.target.files);e.target.value='';}}/>
          </div>

          {files.length > 0 && (
            <div style={{display:'flex',flexDirection:'column',gap:6}}>
              {files.map(f => (
                <div key={f.id}>
                  <div style={{display:'flex',alignItems:'center',gap:10,padding:'9px 11px',borderRadius:10,background:PP.bg,border:`1px solid ${PP.borderLight}`}}>
                    <div style={{width:36,height:36,borderRadius:8,background:f.isImage?PP.sagePale:PP.redLight,display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0,overflow:'hidden'}}>
                      {f.preview
                        ? <img src={f.preview} style={{width:'100%',height:'100%',objectFit:'cover'}} alt=""/>
                        : <span style={{fontSize:9,fontWeight:800,color:PP.red}}>PDF</span>}
                    </div>
                    <div style={{flex:1,minWidth:0}}>
                      <div style={{fontSize:12,fontWeight:600,color:PP.text,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{f.name}</div>
                      <div style={{fontSize:10,color:PP.textMuted}}>{(f.size/1024/1024).toFixed(1)} MB</div>
                    </div>
                    <button onClick={() => removeFileSM(f.id)}
                      style={{width:24,height:24,borderRadius:'50%',border:'none',background:PP.borderLight,color:PP.textMuted,cursor:'pointer',display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0}}
                      onMouseEnter={e=>{e.currentTarget.style.background='#FFE0E0';e.currentTarget.style.color=PP.red;}}
                      onMouseLeave={e=>{e.currentTarget.style.background=PP.borderLight;e.currentTarget.style.color=PP.textMuted;}}>
                      <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
                    </button>
                  </div>
                  {f.wasCompressed && (
                    <div style={{display:'flex',alignItems:'center',gap:6,marginTop:4,padding:'6px 10px',borderRadius:7,background:'#F0F7F2',border:`1px solid #C2DBC9`,fontSize:11,color:PP.sageDark}}>
                      ✓ Foto otimizada automaticamente para envio — qualidade preservada.
                    </div>
                  )}
                  {f.warnings && f.warnings.map((w, i) => (
                    <div key={i} style={{display:'flex',alignItems:'flex-start',gap:6,marginTop:4,padding:'6px 10px',borderRadius:7,background:PP.amberLight,border:`1px solid #E8D5A0`,fontSize:11,color:'#6B4F10',lineHeight:1.5}}>
                      ⚠️ {w}
                    </div>
                  ))}
                </div>
              ))}
            </div>
          )}

          <div style={{background:PP.white,borderRadius:12,border:`1.5px solid ${consent ? PP.sage : PP.borderLight}`,padding:'14px 16px',transition:'border-color 0.2s'}}>
            <label style={{display:'flex',alignItems:'flex-start',gap:10,cursor:'pointer'}}>
              <input type="checkbox" checked={consent} onChange={e => setConsentSM(e.target.checked)}
                style={{marginTop:2,flexShrink:0,width:16,height:16,accentColor:PP.sageDark,cursor:'pointer'}}/>
              <div>
                <span style={{fontSize:12,color:PP.text,lineHeight:1.65,display:'block'}}>
                  Autorizo o envio e armazenamento dos dados e documentos de saúde para análise
                  pelo profissional responsável. Os dados serão retidos por até <strong>5 anos</strong> e podem ser excluídos a qualquer momento mediante solicitação.
                </span>
                <button onClick={e => { e.preventDefault(); setShowPrivacySM(true); }}
                  style={{marginTop:4,fontSize:11,color:PP.sage,background:'none',border:'none',cursor:'pointer',padding:0,fontFamily:'inherit',textDecoration:'underline'}}>
                  Ver política de privacidade completa
                </button>
              </div>
            </label>
          </div>

          {sendError && (
            <div style={{padding:'10px 13px',borderRadius:9,background:PP.redLight,border:`1px solid #F5C5C5`,fontSize:12,color:PP.red,lineHeight:1.55}}>
              {sendError}
            </div>
          )}

          <button
            disabled={!canSend}
            onClick={doSubmitSM}
            style={{
              width:'100%', padding:'14px', borderRadius:12, border:'none',
              background: canSend ? PP.sageDark : PP.borderLight,
              color: canSend ? PP.white : PP.textMuted,
              fontSize:14, fontWeight:700,
              cursor: canSend ? 'pointer' : 'not-allowed',
              boxShadow: canSend ? '0 4px 16px rgba(44,74,53,0.25)' : 'none',
              transition:'all 0.2s', fontFamily:'inherit',
            }}>
            {sending
              ? 'Enviando...'
              : files.length > 0
                ? `Enviar ${files.length} arquivo${files.length > 1 ? 's' : ''}`
                : 'Enviar exames'}
          </button>

          {!nameOk && modo === 'novo-paciente' && (
            <p style={{textAlign:'center',fontSize:11,color:PP.textMuted,margin:'-8px 0 0'}}>Preencha o nome do paciente para continuar</p>
          )}
          {!consent && files.length > 0 && nameOk && (
            <p style={{textAlign:'center',fontSize:11,color:PP.textMuted,margin:'-8px 0 0'}}>Marque o consentimento acima para continuar</p>
          )}

        </div>
      </div>
    </div>
  );
};

Object.assign(window, { PatientProfile, ExamPills, ExamDashboardModal, SubmissionModal, FluxogramaExames });
