// ============================================================
// App shell — status bar + sidebar/editor/PDF-preview.
// Plus toolbar actions to start a new plan, load the sample,
// open the saved-plans history, or export to PDF.
// ============================================================

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "showPreview": true,
  "density": "comfortable",
  "accent": "forest",
  "initialChapter": "carta"
}/*EDITMODE-END*/;

// B-Life accent palettes.
const ACCENTS = {
  forest:  { brand: '#04261E', brand2: '#024036', brand3: '#0B3D31', tint: '#E6EDEA', leaf: '#74B0A8' },
  sage:    { brand: '#2B5A4F', brand2: '#1F4339', brand3: '#3D6E62', tint: '#DCEBEA', leaf: '#74B0A8' },
  copper:  { brand: '#3D2418', brand2: '#2D1A11', brand3: '#5A3826', tint: '#F0E4DA', leaf: '#B68267' },
  ink:     { brand: '#1A1A18', brand2: '#0E0E0C', brand3: '#3A3A36', tint: '#ECE7E1', leaf: '#878582' },
};

const PDF_SRC = 'Plano%20de%20A%C3%A7%C3%A3o%20-%20PDF.html';

// ============================================================
// Proteção do plano exportado (.blife)
// ------------------------------------------------------------
// O arquivo do "Salvar plano" guarda dados sensíveis do paciente.
// Em PRODUÇÃO a cifragem é feita no servidor (Vercel), igual ao
// login: a senha vive só nas Environment Variables
// (process.env.BLIFE_PLAN_SECRET — pode ser a mesma do login) e
// NUNCA chega ao front. O navegador só troca o texto do plano por
// HTTPS, autenticado pela sessão — quem está logado importa sem
// digitar nada.
//
// Configure antes deste script carregar:
//     window.BLIFE_PLAN_API = '/api/plan-crypto';
// Sem essa variável (preview/protótipo) não há backend para cifrar,
// então o plano é salvo como .json legível; a proteção real entra
// assim que o endpoint estiver publicado na Vercel.
//
// Contrato do endpoint (implementado em api/plan-crypto.js):
//   POST /api/plan-crypto   (exige sessão; 401/403 se não logado)
//     { action:'encrypt', plan:<texto JSON> }  → 200 { data:<envelope texto> }
//     { action:'decrypt', data:<envelope texto> } → 200 { plan:<texto JSON> }
//   A senha fica em process.env.BLIFE_PLAN_SECRET (Environment Variable).
// ============================================================
const PLAN_API = (typeof window !== 'undefined' && window.BLIFE_PLAN_API) || '';
const PLAN_ENVELOPE_TAG = 'blifePlan';

function isEncryptedPlan(obj) {
  return !!(obj && typeof obj === 'object' && obj[PLAN_ENVELOPE_TAG] && obj.enc && obj.data);
}

// Cifra no servidor — a senha nunca sai das Environment Variables.
async function serverEncryptPlan(plainText) {
  const res = await fetch(PLAN_API, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    credentials: 'include', body: JSON.stringify({ action: 'encrypt', plan: plainText }),
  });
  if (res.status === 401 || res.status === 403) throw new Error('Sessão expirada — entre novamente para proteger o arquivo.');
  if (!res.ok) throw new Error('Falha ao proteger o arquivo no servidor (' + res.status + ').');
  const d = await res.json().catch(() => ({}));
  if (!d.data) throw new Error('Resposta inválida do servidor ao proteger o arquivo.');
  return d.data;
}

// Decifra no servidor (exige sessão válida).
async function serverDecryptPlan(envelope) {
  const data = typeof envelope === 'string' ? envelope : JSON.stringify(envelope);
  const res = await fetch(PLAN_API, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    credentials: 'include', body: JSON.stringify({ action: 'decrypt', data }),
  });
  if (res.status === 401 || res.status === 403) throw new Error('Entre no sistema para abrir este arquivo protegido.');
  if (!res.ok) throw new Error('Falha ao abrir o arquivo protegido (' + res.status + ').');
  const d = await res.json().catch(() => ({}));
  if (typeof d.plan !== 'string') throw new Error('Resposta inválida do servidor ao abrir o arquivo.');
  return d.plan;
}

// Decide como salvar conforme o ambiente (servidor configurado ou não).
async function protectPlanForExport(json) {
  if (PLAN_API) return { payload: await serverEncryptPlan(json), ext: 'blife', isProtected: true };
  return { payload: json, ext: 'json', isProtected: false };
}

// Recebe o conteúdo do arquivo já parseado; devolve o objeto do plano.
async function openImportedPlan(obj) {
  if (isEncryptedPlan(obj)) {
    if (!PLAN_API) throw new Error('Arquivo protegido (.blife) só abre no sistema publicado (com servidor).');
    return JSON.parse(await serverDecryptPlan(obj));
  }
  return obj;
}

function App() {
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [active, setActive] = React.useState(tweaks.initialChapter || 'carta');
  const [authed, setAuthed] = React.useState(() => {
    // Só bloqueia quando há validador no backend (senha vive só na Vercel).
    if (typeof blifeAuthRequired === 'function' && blifeAuthRequired()) {
      return typeof blifeIsAuthed === 'function' ? blifeIsAuthed() : false;
    }
    return true;
  });

  React.useEffect(() => {
    const a = ACCENTS[tweaks.accent] || ACCENTS.forest;
    const r = document.documentElement.style;
    r.setProperty('--brand', a.brand);
    r.setProperty('--brand-2', a.brand2);
    r.setProperty('--brand-3', a.brand3);
    r.setProperty('--brand-tint', a.tint);
    r.setProperty('--brand-leaf', a.leaf);
  }, [tweaks.accent]);

  React.useEffect(() => {
    document.body.dataset.density = tweaks.density;
  }, [tweaks.density]);

  if (!authed) {
    return <AccessGate onUnlock={() => setAuthed(true)}/>;
  }

  return (
    <PlanProvider>
      <AppInner
        active={active}
        setActive={setActive}
        tweaks={tweaks}
        setTweak={setTweak}
      />
    </PlanProvider>
  );
}

function AppInner({ active, setActive, tweaks, setTweak }) {
  const { plan, startNew, startFromSample, startFromTemplate, openPlan, deletePlan, loadExternalPlan, setLang } = usePlan();
  const { patient, doctor } = usePatient();
  const lang = plan.lang || 'pt-BR';
  const [showHistory, setShowHistory] = React.useState(false);
  const [showNovo, setShowNovo] = React.useState(false);
  const [toast, setToast] = React.useState(null);

  // Listen for blifeToast() calls from anywhere in the tree.
  React.useEffect(() => {
    const onToast = (e) => setToast({ id: Date.now(), text: e.detail });
    window.addEventListener('blife-toast', onToast);
    return () => window.removeEventListener('blife-toast', onToast);
  }, []);

  // Trigger PDF download — opens the print page in a fresh tab.
  const exportPdf = React.useCallback((e) => {
    if (e) e.preventDefault();
    const w = window.open(PDF_SRC + '#print', '_blank', 'noopener');
    if (!w) return;
    setTimeout(() => {
      try { w.postMessage({ type: 'pdf-print' }, '*'); } catch (err) {}
    }, 1200);
  }, []);

  // Save the whole plan as a .json file named after the patient, so it can
  // be handed off to another professional (e.g. the nutritionist) to finish.
  const exportPlan = React.useCallback(async () => {
    const safe = (s) => (s || '').replace(/[\\/:*?"<>|]+/g, ' ').trim();
    const name = safe(patient.name) || 'paciente';
    const datePart = patient.date ? ' - ' + safe(patient.date) : '';
    try {
      const json = JSON.stringify(plan, null, 2);
      const { payload, ext, isProtected } = await protectPlanForExport(json);
      const blob = new Blob([payload], { type: 'application/octet-stream' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url; a.download = `Plano de Acao - ${name}${datePart}.${ext}`;
      document.body.appendChild(a); a.click(); a.remove();
      setTimeout(() => URL.revokeObjectURL(url), 1000);
      blifeToast(isProtected
        ? 'Plano salvo (protegido) como arquivo .blife'
        : 'Plano salvo como .json · a proteção entra no sistema publicado');
    } catch (err) {
      blifeToast(err && err.message ? err.message : 'Não foi possível salvar o arquivo');
    }
  }, [plan, patient]);

  // Read a handed-off .json and load it as a fresh plan in the editor.
  const importPlanFromFile = React.useCallback((file, after) => {
    const reader = new FileReader();
    reader.onload = async () => {
      try {
        const raw = JSON.parse(reader.result);
        // .blife (cifrado) → decifra no servidor com a sessão atual (sem pedir senha).
        // .json (texto)   → carrega direto.
        const obj = await openImportedPlan(raw);
        if (!obj || typeof obj !== 'object' || (!obj.chapters && !obj.patient)) throw new Error('Arquivo inválido — selecione um plano (.blife ou .json) exportado aqui.');
        loadExternalPlan(obj);
        setActive('carta');
        blifeToast('Plano importado · ' + (((obj.patient && obj.patient.name) || 'sem nome')));
        if (after) after();
      } catch (e) {
        blifeToast(e && e.message ? e.message : 'Não foi possível abrir o arquivo.');
      }
    };
    reader.readAsText(file);
  }, [loadExternalPlan, setActive]);

  // Confirm-then-action helpers. Editors keep state in PlanContext, so
  // setPlan-style ops reset everything in one swap.
  const onNew = () => setShowNovo(true);
  const onLoadSample = () => {
    if (window.confirm('Carregar o exemplo preenchido (paciente fictício)? Ele substitui o plano atual no editor (o atual fica no histórico).')) {
      startFromSample();
      setActive('carta');
    }
  };
  const onLoadTemplate = () => {
    if (window.confirm('Carregar o modelo de exemplo (paciente Fabrizio Santos) com todas as seções preenchidas? Ele substitui o plano atual no editor (o atual fica no histórico).')) {
      startFromTemplate();
      setActive('carta');
    }
  };
  const onOpenFromHistory = (id) => {
    openPlan(id);
    setShowHistory(false);
  };
  const onDeleteFromHistory = (id, name) => {
    if (window.confirm(`Excluir o plano de "${name || 'paciente sem nome'}"? Esta ação não pode ser desfeita.`)) {
      deletePlan(id);
    }
  };

  // Editor switcher. `key={plan.id}` forces a remount when the plan changes,
  // so RichText (uncontrolled internally) picks up the new initial value.
  const editor = (() => {
    switch (active) {
      case 'carta': return <CartaEditor/>;
      case 'resultado': return <ResultadoEditor/>;
      case 'objetivos': return <ObjetivosEditor/>;
      case 'biomarcadores': return <BiomarcadoresEditor/>;
      case 'recmedicas': return <RecMedicasEditor/>;
      case 'medicamentos': return <MedicamentosEditor/>;
      case 'suplementacao': return <SuplementacaoEditor/>;
      case 'estilo': return <EstiloEditor/>;
      case 'recomendacoes': return <RecomendacoesEditor/>;
      case 'proximos': return <ProximosEditor/>;
      case 'observacoes': return <ObservacoesEditor/>;
      default: return null;
    }
  })();

  return (
    <div className="app slim">
      <div className="main">
        {/* Status bar */}
        <div className="statusbar">
          <div className="patient">
            <Avatar name={patient.name || '?'} color="linear-gradient(135deg,#C0BCAE,#04261E)" size={36}/>
            <div>
              <div className="patient-name">{patient.name || 'Novo paciente'}</div>
              <div className="patient-meta">
                {doctor.name ? <>Médico responsável · <strong style={{ color: 'var(--ink-2)' }}>{doctor.name}</strong></> : 'Defina o médico responsável no capítulo Paciente'}
              </div>
            </div>
          </div>
          <div className="divider"/>
          <div className="crumbs">
            <span>Plano de ação</span>
            <Ico.ChevronRight/>
            <strong>{patient.date || 'sem data'}</strong>
          </div>
          <div className="actions">
            <LangSelect lang={lang} onChange={setLang}/>
            <button className="btn" onClick={() => setShowHistory(true)} title="Ver planos salvos neste navegador">
              <Ico.File/> {tr('tb.historico', lang)}
            </button>
            <button className="btn" onClick={onLoadTemplate} title="Carregar o modelo de exemplo (Fabrizio Santos) com todas as seções preenchidas">
              <Ico.Template/> {tr('tb.modelo', lang)}
            </button>
            <button className="btn" onClick={onNew} title="Iniciar um novo plano: importar um salvo ou começar do zero">
              <Ico.Plus/> {tr('tb.novo', lang)}
            </button>
            <button className="btn" onClick={exportPlan} title="Baixar o plano atual como arquivo protegido .blife (dados do paciente cifrados; abre logado no sistema)">
              <Ico.Save/> {tr('tb.salvarPlano', lang)}
            </button>
            <button className="btn primary" onClick={exportPdf} title="Abrir o PDF final em nova aba (pronto para imprimir/salvar)">
              <Ico.Download/> {tr('tb.exportarPdf', lang)}
            </button>
          </div>
        </div>

        {/* Body. key={plan.id} forces editor remount on plan swap (RichText is uncontrolled) */}
        <div className={'body' + (tweaks.showPreview ? ' with-preview' : '')}>
          <Sidebar active={active} onSelect={setActive}/>
          <div className="editor" key={plan.id}>
            <div className="editor-inner">{editor}</div>
          </div>
          {tweaks.showPreview && <PreviewPanel active={active} plan={plan}/>}
        </div>
      </div>

      <Toast toast={toast} onClose={() => setToast(null)}/>

      {showHistory && (
        <HistoryModal
          currentPlanId={plan.id}
          onOpen={onOpenFromHistory}
          onDelete={onDeleteFromHistory}
          onLoadTemplate={() => { startFromTemplate(); setActive('carta'); setShowHistory(false); }}
          onLoadSample={() => { startFromSample(); setActive('carta'); setShowHistory(false); }}
          onNew={() => { startNew(); setActive('carta'); setShowHistory(false); }}
          onClose={() => setShowHistory(false)}
        />
      )}

      {showNovo && (
        <NovoPlanoModal
          onClose={() => setShowNovo(false)}
          onBlank={() => { startNew(); setActive('carta'); setShowNovo(false); }}
          onImport={(file) => importPlanFromFile(file, () => setShowNovo(false))}
          onTemplate={() => { startFromTemplate(); setActive('carta'); setShowNovo(false); }}
        />
      )}

      {/* Tweaks */}
      <TweaksPanel title="Tweaks · Editor">
        <TweakSection label="Layout">
          <TweakToggle label="Preview do PDF" value={tweaks.showPreview} onChange={v => setTweak('showPreview', v)}/>
          <TweakRadio label="Densidade" value={tweaks.density}
            options={[{value:'comfortable',label:'Conforto'},{value:'compact',label:'Compacto'}]}
            onChange={v => setTweak('density', v)}/>
        </TweakSection>
        <TweakSection label="Cor de marca">
          <TweakColor label="Accent" value={[ACCENTS[tweaks.accent].brand, ACCENTS[tweaks.accent].brand3, ACCENTS[tweaks.accent].tint]}
            options={Object.values(ACCENTS).map(a => [a.brand, a.brand3, a.tint])}
            onChange={v => {
              const found = Object.entries(ACCENTS).find(([_,a]) => a.brand === v[0]);
              if (found) setTweak('accent', found[0]);
            }}/>
        </TweakSection>
        <TweakSection label="Navegação rápida">
          <TweakSelect label="Capítulo" value={active}
            options={STEPS.map(s => ({value: s.id, label: s.label}))}
            onChange={setActive}/>
        </TweakSection>
      </TweaksPanel>
    </div>
  );
}

// ─── LangSelect ──────────────────────────────────────────────
function LangSelect({ lang, onChange }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', h);
    return () => document.removeEventListener('mousedown', h);
  }, []);
  const cur = LANGS.find(l => l.code === lang) || LANGS[0];
  return (
    <div className="lang-select" ref={ref}>
      <button className="btn lang-btn" onClick={() => setOpen(o => !o)} title={tr('tb.idioma', lang)}>
        <span className="lang-flag">{cur.flag}</span>
        <span className="lang-code">{cur.code.toUpperCase().replace('-', ' ')}</span>
        <Ico.Chevron/>
      </button>
      {open && (
        <div className="lang-menu">
          {LANGS.map(l => (
            <button key={l.code} className={'lang-opt' + (l.code === lang ? ' active' : '')} onClick={() => { onChange(l.code); setOpen(false); }}>
              <span className="lang-flag">{l.flag}</span>
              <span className="lang-opt-label">{l.label}</span>
              {l.code === lang && <Ico.Check/>}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

// ─── NovoPlanoModal ──────────────────────────────────────────
function NovoPlanoModal({ onClose, onBlank, onImport, onTemplate }) {
  const fileRef = React.useRef(null);
  React.useEffect(() => {
    const k = (e) => e.key === 'Escape' && onClose();
    window.addEventListener('keydown', k);
    return () => window.removeEventListener('keydown', k);
  }, [onClose]);
  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{ width: 560 }}>
        <div className="modal-head">
          <div>
            <div className="muted tiny" style={{ letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 600 }}>
              Novo plano de ação
            </div>
            <div style={{ fontSize: 18, fontWeight: 600, marginTop: 2 }}>
              Como você quer começar?
            </div>
          </div>
          <button className="btn ghost sm" onClick={onClose}><Ico.X/></button>
        </div>

        <div className="modal-body">
          <div className="empty-ctas">
            <div className="empty-cta" onClick={() => fileRef.current && fileRef.current.click()}>
              <Ico.Upload/>
              <div className="ttl">Importar plano salvo</div>
              <div className="desc">Continue um plano (.blife) enviado por outro profissional.</div>
            </div>
            <div className="empty-cta" onClick={onBlank}>
              <Ico.Plus/>
              <div className="ttl">Começar do zero</div>
              <div className="desc">Um plano em branco para um novo paciente.</div>
            </div>
          </div>
          <input
            ref={fileRef}
            type="file"
            accept=".blife,.json,application/json"
            style={{ display: 'none' }}
            onChange={e => { const f = e.target.files && e.target.files[0]; if (f) onImport(f); e.target.value = ''; }}
          />
          <div className="muted tiny" style={{ marginTop: 14, textAlign: 'center' }}>
            O plano atual continua salvo no histórico deste navegador.
          </div>
        </div>

        <div className="modal-foot">
          <button className="btn ghost" onClick={onClose}>Cancelar</button>
          <div style={{ flex: 1 }}/>
          <button className="btn" onClick={onTemplate} title="Modelo de exemplo (Fabrizio Santos) com todas as seções preenchidas">
            <Ico.Template/> Carregar modelo
          </button>
        </div>
      </div>
    </div>
  );
}

// ─── HistoryModal ────────────────────────────────────────────
function HistoryModal({ currentPlanId, onOpen, onDelete, onLoadTemplate, onLoadSample, onNew, onClose }) {
  const [entries, setEntries] = React.useState(() => listSavedPlans());

  // Refresh on focus so it picks up edits made just before opening.
  React.useEffect(() => { setEntries(listSavedPlans()); }, [currentPlanId]);

  // ESC to close
  React.useEffect(() => {
    const k = (e) => e.key === 'Escape' && onClose();
    window.addEventListener('keydown', k);
    return () => window.removeEventListener('keydown', k);
  }, [onClose]);

  const refresh = () => setEntries(listSavedPlans());

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{ width: 640 }}>
        <div className="modal-head">
          <div>
            <div className="muted tiny" style={{ letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 600 }}>
              Histórico
            </div>
            <div style={{ fontSize: 18, fontWeight: 600, marginTop: 2 }}>
              Planos salvos neste navegador
            </div>
          </div>
          <button className="btn ghost sm" onClick={onClose}><Ico.X/></button>
        </div>

        <div className="modal-body">
          {entries.length === 0 ? (
            <div className="muted tiny" style={{ textAlign: 'center', padding: 28 }}>
              Nenhum plano salvo ainda.<br/>
              Comece em branco ou carregue o modelo abaixo.
            </div>
          ) : (
            <div className="history-list">
              {entries.map((e) => (
                <div
                  key={e.id}
                  className={'history-row' + (e.id === currentPlanId ? ' current' : '')}
                >
                  <div className="history-avatar">
                    {(e.patientName || '?').split(' ').filter(Boolean).slice(0,2).map(s => s[0]).join('').toUpperCase() || '?'}
                  </div>
                  <div className="history-body">
                    <div className="history-name">{e.patientName}</div>
                    <div className="history-meta">
                      {e.doctorName && <>{e.doctorName} · </>}
                      {e.planDate && <>{e.planDate} · </>}
                      <span className="muted">salvo {formatAgo(e.updatedAt)}</span>
                    </div>
                  </div>
                  {e.id === currentPlanId && (
                    <span className="history-current-pill">Em edição</span>
                  )}
                  <div className="history-actions">
                    {e.id !== currentPlanId && (
                      <button className="btn xs" onClick={() => onOpen(e.id)}>Abrir</button>
                    )}
                    <button
                      className="btn ghost xs"
                      onClick={() => { onDelete(e.id, e.patientName); refresh(); }}
                      title="Excluir"
                    ><Ico.Trash/></button>
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>

        <div className="modal-foot">
          <button className="btn ghost" onClick={onClose}>Cancelar</button>
          <div style={{ flex: 1 }}/>
          <button className="btn" onClick={onNew}>
            <Ico.Plus/> Novo em branco
          </button>
          <button className="btn ghost" onClick={onLoadSample} title="Carregar um exemplo preenchido com paciente fictício">
            <Ico.Eye/> Exemplo
          </button>
          <button className="btn primary" onClick={onLoadTemplate} title="Modelo de exemplo (Fabrizio Santos) com todas as seções preenchidas">
            <Ico.Template/> Carregar modelo
          </button>
        </div>
      </div>
    </div>
  );
}

// Small relative-time helper for the history rows.
function formatAgo(ts) {
  if (!ts) return 'há instantes';
  const s = Math.floor((Date.now() - ts) / 1000);
  if (s < 60) return 'há instantes';
  const m = Math.floor(s / 60);
  if (m < 60) return `há ${m} min`;
  const h = Math.floor(m / 60);
  if (h < 24) return `há ${h}h`;
  const d = Math.floor(h / 24);
  if (d < 7) return `há ${d} dia${d === 1 ? '' : 's'}`;
  const date = new Date(ts);
  return date.toLocaleDateString('pt-BR', { day: '2-digit', month: 'short', year: 'numeric' });
}

// ─── Toast ───────────────────────────────────────────────────
function Toast({ toast, onClose }) {
  React.useEffect(() => {
    if (!toast) return;
    const t = setTimeout(onClose, 2400);
    return () => clearTimeout(t);
  }, [toast, onClose]);
  if (!toast) return null;
  return (
    <div className="blife-toast" key={toast.id} role="status" aria-live="polite">
      <span className="blife-toast__icon"><Ico.Check/></span>
      <span>{toast.text}</span>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App/>);
