// Órbita — Main App Router
//
// Três modos de layout:
//   BARE    — sem chrome de app: login, cadastro, verificação, onboarding, portal público
//   SHELL   — app da astróloga: barra lateral persistente + topo contextual da tela
//   FOCADO  — tarefa única, sem barra lateral: nova sessão, processando, editor
//
// A barra lateral lista SÓ destinos reais (Painel, Consulentes, Analytics, Portal,
// Config). Telas de fluxo e de estado não entram no menu — são alcançadas pela
// navegação natural do app ou pelo atalho de protótipo (canto inferior esquerdo).

// ── Ícones da barra lateral (stroke = currentColor) ────────
const RailIcon = ({ paths }) => (
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
    strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}>
    {paths.map((d, i) => <path key={i} d={d} />)}
  </svg>
);

const ICONS = {
  painel:      ['M3 13h8V3H3z', 'M13 21h8V3h-8z', 'M3 21h8v-6H3z'],
  consulentes: ['M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2', 'M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z', 'M22 21v-2a4 4 0 0 0-3-3.87', 'M17 3.13a4 4 0 0 1 0 7.75'],
  analytics:   ['M18 20V10', 'M12 20V4', 'M6 20v-6'],
  portal:      ['M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z', 'M3 12h18', 'M12 3c3 3 3 15 0 18', 'M12 3c-3 3-3 15 0 18'],
  config:      ['M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z', 'M19.4 13a1.7 1.7 0 0 0 .3 1.9l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-2.9 1.2V21a2 2 0 0 1-4 0v-.2A1.7 1.7 0 0 0 8 19.5l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1A1.7 1.7 0 0 0 4.5 12H4a2 2 0 0 1 0-4h.2A1.7 1.7 0 0 0 5.5 5l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1A1.7 1.7 0 0 0 11 2.5V2a2 2 0 0 1 4 0v.2a1.7 1.7 0 0 0 2.9 1.2l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.9z'],
};

// Menu principal — só destinos reais do produto.
const NAV_ITEMS = [
  { id: 'dashboard',   label: 'Painel',      icon: 'painel'      },
  { id: 'consulentes', label: 'Consulentes', icon: 'consulentes' },
  { id: 'analytics',   label: 'Analytics',   icon: 'analytics'   },
  { id: 'portal',      label: 'Portal',      icon: 'portal'      },
  { id: 'settings',    label: 'Config',      icon: 'config'      },
];

// Telas que ganham a barra lateral do app.
const SHELL_SCREENS = new Set(['dashboard', 'empty', 'consulentes', 'analytics', 'portal', 'settings']);
// Telas de tarefa única — sem barra lateral, header próprio com "← Painel".
const FOCUSED_SCREENS = new Set(['upload', 'processing', 'editor']);
// Qualquer outra = BARE (tela cheia, sem chrome).

// Qual item do menu fica ativo em cada tela.
const NAV_FOR_SCREEN = {
  dashboard: 'dashboard', empty: 'dashboard',
  consulentes: 'consulentes', analytics: 'analytics',
  portal: 'portal', settings: 'settings',
};

// Atalho de protótipo — todas as telas e estados, pra revisão. Fora do menu.
const PROTO_SCREENS = [
  { group: 'Entrada', items: [
    { id: 'landing',      label: 'Home (LP)' },
    { id: 'login',        label: 'Login' },
    { id: 'signup',       label: 'Cadastro' },
    { id: 'verify-email', label: 'Verificação de e-mail' },
    { id: 'onboarding',   label: 'Onboarding' },
  ]},
  { group: 'App', items: [
    { id: 'dashboard',   label: 'Painel' },
    { id: 'empty',       label: 'Painel · vazio' },
    { id: 'consulentes', label: 'Consulentes' },
    { id: 'analytics',   label: 'Analytics' },
    { id: 'settings',    label: 'Config' },
  ]},
  { group: 'Criar sessão', items: [
    { id: 'upload',           label: 'Nova sessão' },
    { id: 'upload-quota',     label: 'Nova sessão · cota do plano esgotada' },
    { id: 'upload-trial',     label: 'Nova sessão · trial esgotado' },
    { id: 'processing',       label: 'Processando' },
    { id: 'processing-error', label: 'Processando · falha' },
    { id: 'editor',           label: 'Editor' },
    { id: 'editor-error',     label: 'Editor · erro ao publicar' },
  ]},
  { group: 'Estados', items: [
    { id: 'consulentes-empty',  label: 'Consulentes · vazio' },
    { id: 'consulentes-error',  label: 'Consulentes · erro' },
    { id: 'analytics-empty',    label: 'Analytics · vazio' },
    { id: 'settings-billing',   label: 'Config · pagamento recusado' },
  ]},
  { group: 'Portal', items: [
    { id: 'portal',        label: 'Portal · prévia (astróloga)' },
    { id: 'portal-public', label: 'Portal · consulente' },
  ]},
];

// Paleta fixa da barra: fundo escuro nos dois temas (contraste tipo produto real).
const RAIL_FG = '#F5F1E8';
const RAIL_FG_DIM = 'rgba(245,241,232,0.45)';
const RAIL_HL = '#F4A261';

const railItemBase = {
  display: 'flex', alignItems: 'center', gap: 12, width: '100%',
  fontFamily: 'Inter, sans-serif', textAlign: 'left', cursor: 'pointer',
  transition: 'all 0.15s', borderRadius: 12,
};

// ── Barra lateral ─────────────────────────────────────────
// mode: 'docked' (coluna fixa ≥900px) | 'drawer' (gaveta <900px)
const Sidebar = ({ activeNav, onNavigate, mode = 'docked', onClose }) => {
  const isDrawer = mode === 'drawer';

  const nav = (
    <nav className={isDrawer ? 'orbita-rail-drawer' : 'orbita-rail'} style={{
      width: 236, flexShrink: 0,
      ...(isDrawer
        ? { position: 'fixed', top: 0, left: 0, bottom: 0, height: '100%', zIndex: 320, boxShadow: '8px 0 40px rgba(0,0,0,0.4)' }
        : { alignSelf: 'flex-start', position: 'sticky', top: 0, height: '100vh', zIndex: 200 }),
      background: isDrawer ? '#100D0A' : 'rgba(16,13,10,0.97)',
      borderRight: '1px solid rgba(245,241,232,0.09)',
      padding: '22px 14px', display: 'flex', flexDirection: 'column',
      overflowY: 'auto',
    }}>
      {!isDrawer && <style>{`
        @media (max-width: 899px){ .orbita-rail{ display:none !important; } }
        @media (min-width: 900px){ .orbita-mobile-nav-btn{ display:none !important; } }
      `}</style>}

      {/* Marca */}
      <div style={{
        display: 'flex', alignItems: 'center', gap: 10, justifyContent: 'space-between',
        padding: '2px 6px 18px', marginBottom: 8,
        borderBottom: '1px solid rgba(245,241,232,0.09)',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          <OrbitasMark size={26} color={ORBITAS_GOLD} />
          <span style={{ fontFamily: '"Instrument Serif", serif', fontSize: 21, letterSpacing: '-0.01em', color: RAIL_FG }}>
            Órbitas
          </span>
        </div>
        {isDrawer && (
          <button onClick={onClose} aria-label="Fechar menu" style={{
            background: 'transparent', border: 'none', color: RAIL_FG_DIM, cursor: 'pointer', fontSize: 20, lineHeight: 1, padding: 4,
          }}>×</button>
        )}
      </div>

      {NAV_ITEMS.map(item => {
        const isActive = activeNav === item.id;
        return (
          <button
            key={item.id}
            onClick={() => { onNavigate(item.id); onClose && onClose(); }}
            style={{
              ...railItemBase,
              padding: '11px 12px', marginBottom: 2,
              fontSize: 12.5, fontWeight: isActive ? 600 : 500,
              letterSpacing: '0.06em', textTransform: 'uppercase',
              background: isActive ? 'rgba(244,162,97,0.13)' : 'transparent',
              border: `1.5px solid ${isActive ? 'rgba(244,162,97,0.55)' : 'transparent'}`,
              color: isActive ? RAIL_HL : RAIL_FG,
              opacity: isActive ? 1 : 0.68,
            }}
            onMouseEnter={e => {
              if (isActive) return;
              e.currentTarget.style.opacity = '1';
              e.currentTarget.style.background = 'rgba(245,241,232,0.06)';
            }}
            onMouseLeave={e => {
              if (isActive) return;
              e.currentTarget.style.opacity = '0.68';
              e.currentTarget.style.background = 'transparent';
            }}
          >
            <RailIcon paths={ICONS[item.icon]} />
            {item.label}
          </button>
        );
      })}

      {/* paddingBottom deixa espaço pro atalho de protótipo (?proto=1) no canto inferior esquerdo */}
      <div style={{ marginTop: 'auto', paddingTop: 16, paddingBottom: 58, borderTop: '1px solid rgba(245,241,232,0.09)' }}>
        <button
          onClick={() => { onNavigate('upload'); onClose && onClose(); }}
          style={{
            ...railItemBase, justifyContent: 'center', gap: 8,
            padding: '10px 12px', fontSize: 12, fontWeight: 600,
            background: 'rgba(244,162,97,0.12)', border: '1px solid rgba(244,162,97,0.4)',
            color: RAIL_HL,
          }}
        >
          <svg width="14" height="14" 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>
          Nova sessão
        </button>
      </div>
    </nav>
  );

  if (!isDrawer) return nav;
  return (
    <div style={{ position: 'fixed', inset: 0, zIndex: 310, background: 'rgba(0,0,0,0.5)', backdropFilter: 'blur(2px)' }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()}>{nav}</div>
    </div>
  );
};

// Botão de menu flutuante — só aparece <900px, abre a gaveta.
const MobileNavButton = ({ onOpen }) => (
  <button className="orbita-mobile-nav-btn" onClick={onOpen} aria-label="Abrir menu" style={{
    position: 'fixed', top: 12, left: 12, zIndex: 250,
    width: 40, height: 40, borderRadius: 12,
    background: 'rgba(16,13,10,0.92)', backdropFilter: 'blur(8px)',
    border: '1px solid rgba(245,241,232,0.18)', color: '#F5F1E8',
    display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
  }}>
    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
      <line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/>
    </svg>
  </button>
);

// ── Atalho de protótipo (canto inferior esquerdo, recolhido) ──
const ProtoLauncher = ({ screen, onJump }) => {
  const [open, setOpen] = React.useState(false);
  return (
    <div style={{ position: 'fixed', left: 14, bottom: 14, zIndex: 4000, fontFamily: 'Inter, sans-serif' }}>
      {open && (
        <div style={{
          position: 'absolute', bottom: 'calc(100% + 8px)', left: 0,
          width: 244, maxHeight: '70vh', overflowY: 'auto',
          background: '#171310', border: '1px solid rgba(245,241,232,0.14)',
          borderRadius: 14, padding: '10px 8px', boxShadow: '0 16px 48px rgba(0,0,0,0.55)',
        }}>
          <div style={{ fontSize: 9, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'rgba(245,241,232,0.4)', padding: '4px 8px 8px' }}>
            Telas do protótipo
          </div>
          {PROTO_SCREENS.map(sec => (
            <div key={sec.group} style={{ marginBottom: 6 }}>
              <div style={{ fontSize: 9, color: 'rgba(245,241,232,0.32)', padding: '4px 8px 2px', textTransform: 'uppercase', letterSpacing: '0.1em' }}>{sec.group}</div>
              {sec.items.map(it => {
                const active = screen === it.id;
                return (
                  <button key={it.id} onClick={() => { onJump(it.id); setOpen(false); }} style={{
                    display: 'block', width: '100%', textAlign: 'left',
                    padding: '6px 8px', borderRadius: 8, border: 'none', cursor: 'pointer',
                    background: active ? 'rgba(244,162,97,0.16)' : 'transparent',
                    color: active ? '#F4A261' : 'rgba(245,241,232,0.7)',
                    fontSize: 12, fontFamily: 'Inter, sans-serif',
                  }}
                  onMouseEnter={e => { if (!active) e.currentTarget.style.background = 'rgba(245,241,232,0.06)'; }}
                  onMouseLeave={e => { if (!active) e.currentTarget.style.background = 'transparent'; }}
                  >{it.label}</button>
                );
              })}
            </div>
          ))}
        </div>
      )}
      <button onClick={() => setOpen(o => !o)} style={{
        display: 'flex', alignItems: 'center', gap: 7,
        padding: '7px 12px', borderRadius: 999, cursor: 'pointer',
        background: 'rgba(16,13,10,0.9)', backdropFilter: 'blur(8px)',
        border: '1px solid rgba(245,241,232,0.16)', color: 'rgba(245,241,232,0.6)',
        fontSize: 11, fontFamily: 'Inter, sans-serif', letterSpacing: '0.04em',
      }}>
        <span style={{ fontSize: 12 }}>◑</span>
        protótipo
        <span style={{ transform: open ? 'rotate(180deg)' : 'none', transition: 'transform 0.15s', fontSize: 9 }}>▲</span>
      </button>
    </div>
  );
};

// Trial de demonstração pro estado "upload-trial" do atalho de protótipo —
// simula uma conta nova que já bateu o limite (3 sessões OU 3 dias, o que vier primeiro).
const DEMO_EXHAUSTED_TRIAL = { active: true, started_at: new Date(Date.now() - 3 * 86400000).toISOString(), sessions_used: 3 };

// Rota pública do portal (/portal/:slug, via rewrite em next.config.ts) — não
// passa por login. Detectada no boot, antes de qualquer checagem de sessão.
const PUBLIC_PORTAL_MATCH = typeof window !== 'undefined' ? window.location.pathname.match(/^\/portal\/([^/]+)/) : null;
// O atalho de telas é ferramenta de revisão, não parte do produto: só aparece
// com ?proto=1 na URL. Sem isso, quem entra vê só o app.
const PROTO_NAV = typeof window !== 'undefined' && new URLSearchParams(window.location.search).get('proto') === '1';

const AppInner = () => {
  const [screen, setScreen] = React.useState(PUBLIC_PORTAL_MATCH ? 'portal-public' : 'landing');
  const [currentSession, setCurrentSession] = React.useState(null);
  const [drawerOpen, setDrawerOpen] = React.useState(false);
  const [booting, setBooting] = React.useState(!PUBLIC_PORTAL_MATCH);
  const [authUser, setAuthUser] = React.useState(null);
  const [openingSession, setOpeningSession] = React.useState(false);
  // Cadastro com verificação: e-mail pra onde o link foi, e o aviso de volta do link.
  const [pendingEmail, setPendingEmail] = React.useState(null);
  const [emailConfirmado] = React.useState(
    () => typeof window !== 'undefined' && new URLSearchParams(window.location.search).get('confirmado') === '1',
  );

  // Upload → motor real (Bloco 3): payload pendente + estado da extração em curso.
  const [pendingUpload, setPendingUpload] = React.useState(null);
  const [extractStatus, setExtractStatus] = React.useState('idle'); // idle | running | done | error
  const [extractError, setExtractError] = React.useState(null);
  const [extractResult, setExtractResult] = React.useState(null);
  const [extractRetryable, setExtractRetryable] = React.useState(false);
  // O que foi enviado na tentativa que falhou — volta preenchido em Nova sessão.
  const [uploadDraft, setUploadDraft] = React.useState(null);

  // Estados de variação/erro. O atalho de protótipo entra por telas dedicadas
  // (ex: 'consulentes-empty') e a gente traduz aqui pro par tela+estado.
  const consulentesVariant = 'cards';
  const consulentesState =
    screen === 'consulentes-empty' ? 'empty' :
    screen === 'consulentes-error' ? 'error' : 'data';
  const analyticsVariant = 'cockpit';
  const analyticsState = screen === 'analytics-empty' ? 'empty' : 'data';
  const emptyVariant = 'guided';
  const quotaExceeded = screen === 'upload-quota';
  const trialStatus = screen === 'upload-trial' ? getTrialStatus(DEMO_EXHAUSTED_TRIAL) : null;
  const processingError = screen === 'processing-error';
  const billingError = screen === 'settings-billing';
  const publishError = screen === 'editor-error';

  // Tela "canônica" (o estado 'consulentes-error' renderiza a tela 'consulentes').
  const canonical =
    screen.startsWith('consulentes') ? 'consulentes' :
    screen.startsWith('analytics') ? 'analytics' :
    screen === 'settings-billing' ? 'settings' :
    screen === 'processing-error' ? 'processing' :
    screen === 'editor-error' ? 'editor' :
    screen === 'upload-quota' ? 'upload' :
    screen === 'upload-trial' ? 'upload' :
    screen;

  const { theme } = useTheme();

  const go = (s, session = null) => {
    if (session !== null) setCurrentSession(session);
    // Entrar em 'upload' começa um formulário limpo. A exceção é voltar de uma
    // falha no processamento: `backToUploadWithDraft` repõe o texto logo depois.
    if (s === 'upload') {
      setPendingUpload(null);
      setExtractStatus('idle');
      setExtractError(null);
      setExtractResult(null);
      setUploadDraft(null);
    }
    setScreen(s);
    setDrawerOpen(false);
    window.scrollTo(0, 0);
  };

  // Falha no motor: volta pra Nova sessão com o que foi enviado, sem obrigar a
  // colar (ou transcrever) de novo.
  const backToUploadWithDraft = () => {
    const draft = pendingUpload;
    go('upload');
    setUploadDraft(draft);
  };

  // "Tentar de novo" com o mesmo payload: objeto novo re-dispara o efeito de extração.
  const retryExtract = () => {
    if (!pendingUpload) return;
    setExtractStatus('idle');
    setExtractError(null);
    setPendingUpload({ ...pendingUpload });
  };

  // Abrir uma sessão a partir de Dashboard/Consulentes: essas telas só têm a
  // linha enxuta da lista (sem `leitura`/`transcript`), então busca o registro
  // completo antes de navegar — sem isso o Editor cairia em "modo revisão"
  // (mock) por um instante, achando que não tinha sessão real (ver EditorScreen).
  const openSession = async (row) => {
    setOpeningSession(true);
    try {
      const res = await fetch(`/api/leituras/${row.id}`);
      const data = await res.json().catch(() => ({}));
      if (!res.ok || !data.leitura) throw new Error(data.error || 'Não foi possível abrir a sessão.');
      go('editor', data.leitura);
    } catch (err) {
      window.alert(err.message || 'Falha de rede ao abrir a sessão.');
    } finally {
      setOpeningSession(false);
    }
  };

  // "Ver portal" a partir de Dashboard/Consulentes — mesma lógica: precisa do
  // registro completo (a leitura em si) pra prévia renderizar algo de verdade.
  const openPortalPreview = async (row) => {
    setOpeningSession(true);
    try {
      const res = await fetch(`/api/leituras/${row.id}`);
      const data = await res.json().catch(() => ({}));
      if (!res.ok || !data.leitura) throw new Error(data.error || 'Não foi possível abrir o portal.');
      go('portal', data.leitura);
    } catch (err) {
      window.alert(err.message || 'Falha de rede ao abrir o portal.');
    } finally {
      setOpeningSession(false);
    }
  };

  // Publicar de verdade: POST /api/leituras/[id]/publish grava status='published'
  // + gera o public_slug (idempotente — publicar de novo mantém o mesmo slug).
  // Não navega sozinho: devolve o registro pro PublishModal mostrar o link real
  // e a astróloga copiar antes de sair da tela (ver PublishModal, orbita-editor.jsx).
  const publishSession = async () => {
    if (!currentSession?.id) {
      throw new Error('Esta sessão ainda não foi salva — não é possível publicar.');
    }
    const res = await fetch(`/api/leituras/${currentSession.id}/publish`, { method: 'POST' });
    const data = await res.json().catch(() => ({}));
    if (!res.ok || !data.leitura) throw new Error(data.error || 'Não foi possível publicar.');
    setCurrentSession(data.leitura);
    return data.leitura;
  };

  // Logout de verdade — revoga a sessão no Supabase e limpa o cookie httpOnly.
  React.useEffect(() => {
    window.__ORBITA_LOGOUT__ = async () => {
      try { await fetch('/api/auth/logout', { method: 'POST' }); } catch {}
      setAuthUser(null);
      setCurrentSession(null);
      setPendingUpload(null);
      setExtractStatus('idle');
      setExtractResult(null);
      setUploadDraft(null);
      go('login');
    };
  }, []);

  // Bootstrap: pergunta ao servidor se já existe sessão válida (cookie httpOnly)
  // antes de decidir entre tela de login e o app — evita piscar uma ou outra.
  // Pulado por completo no portal público: a consulente nunca faz login.
  React.useEffect(() => {
    if (PUBLIC_PORTAL_MATCH) return;
    let cancelled = false;
    fetch('/api/auth/session')
      .then(res => res.json())
      .then(data => {
        if (cancelled) return;
        if (data?.user) { setAuthUser(data.user); setScreen('dashboard'); }
        // Volta do link de confirmação: cai no login, não na landing.
        else if (emailConfirmado) setScreen('login');
      })
      .catch(() => {})
      .finally(() => { if (!cancelled) setBooting(false); });
    return () => { cancelled = true; };
  }, []);

  // Upload → /api/extract: dispara quando entra em 'processing' com um payload
  // pendente. `pendingUpload` é limpo ao sair da tela pra não re-disparar.
  React.useEffect(() => {
    if (canonical !== 'processing' || !pendingUpload || extractStatus !== 'idle') return;
    let cancelled = false;
    setExtractStatus('running');
    setExtractError(null);
    fetch('/api/extract', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(pendingUpload),
    })
      .then(async res => {
        const data = await res.json().catch(() => ({}));
        if (cancelled) return;
        if (!res.ok || data.error) {
          setExtractError(data.error || `Erro ${res.status} ao processar a sessão.`);
          // A API diz quando repetir com o mesmo texto tem chance (instabilidade,
          // formato inválido); limite de uso, chave ou texto curto não têm.
          setExtractRetryable(data.retryable === true);
          setExtractStatus('error');
          return;
        }
        setExtractResult({
          id: data.id ?? null,
          save_error: data.save_error ?? null,
          leitura: data.leitura,
          usage: data.usage,
          transcript: pendingUpload.transcript,
          consulente: pendingUpload.consulente,
          consulente_id: pendingUpload.consulente_id ?? null,
          year_start: pendingUpload.year_start ?? null,
        });
        setExtractStatus('done');
      })
      .catch(err => {
        if (cancelled) return;
        setExtractError(err?.message || 'Falha de rede ao processar a sessão.');
        setExtractRetryable(true);
        setExtractStatus('error');
      });
    return () => { cancelled = true; };
    // extractStatus fica de fora do dep array de propósito: setExtractStatus('running')
    // acima dispararia um re-run imediato que cancelaria o próprio fetch em voo
    // (a limpeza do run anterior marcaria `cancelled = true` antes do .then rodar).
  }, [canonical, pendingUpload]);

  // Enquanto decide entre login e app (checagem de sessão em voo), não renderiza
  // nenhum dos dois — evita piscar a tela errada por uma fração de segundo.
  if (booting) {
    return <div style={{ minHeight: '100vh', background: theme.bg }}><ThemeSync /></div>;
  }

  const renderScreen = () => {
    switch (canonical) {
      case 'landing':
        return <LandingScreen onEnter={() => go('login')} onSignup={() => go('signup')} />;
      case 'login':
        return (
          <LoginScreen
            aviso={emailConfirmado ? 'E-mail confirmado. Entre com a senha que você criou.' : null}
            onLogin={(user) => { setAuthUser(user); go('dashboard'); }}
            onGoSignup={() => go('signup')}
          />
        );
      case 'signup':
        // O cadastro cria a conta não confirmada e manda o link por e-mail (ver
        // app/api/auth/signup/route.ts). Sem sessão até a confirmação: daqui vai
        // pra tela de "confirme seu e-mail", não pro onboarding.
        return (
          <SignupScreen
            onComplete={(r) => {
              if (r?.pendente) { setPendingEmail(r.email); go('verify-email'); return; }
              setAuthUser(r); go('onboarding');
            }}
            onBack={() => go('login')}
          />
        );
      case 'verify-email':
        return <EmailVerifyScreen email={pendingEmail} onVerified={() => go('login')} />;
      case 'onboarding':
        return (
          <OnboardingScreen
            onComplete={() => go('empty')}
            onGoUpload={() => go('upload')}
            onGoEditor={() => go('editor', window.ORBITA_DATA.sessions[0])}
          />
        );
      case 'dashboard':
        return (
          <DashboardScreen
            onNewSession={() => go('upload')}
            onOpenSession={openSession}
            onOpenPortal={openPortalPreview}
            onOpenSettings={() => go('settings')}
            onOpenAnalytics={() => go('analytics')}
            onOpenConsulentes={() => go('consulentes')}
          />
        );
      case 'empty':
        return (
          <EmptyDashboardScreen
            variant={emptyVariant}
            onNewSession={() => go('upload')}
            onAddConsulente={() => go('consulentes')}
            onOpenSettings={() => go('settings')}
            onOpenDemo={() => go('editor', window.ORBITA_DATA.sessions[0])}
          />
        );
      case 'consulentes':
        return (
          <ConsulentesScreen
            state={consulentesState}
            variant={consulentesVariant}
            onOpenSession={openSession}
            onNewSession={() => go('upload')}
            onOpenSettings={() => go('settings')}
          />
        );
      case 'analytics':
        return (
          <AnalyticsScreen
            state={analyticsState}
            variant={analyticsVariant}
            onNewSession={() => go('upload')}
            onOpenSettings={() => go('settings')}
          />
        );
      case 'upload':
        return (
          <NewSessionScreen
            initialDraft={uploadDraft}
            onBack={() => go('dashboard')}
            onSubmit={(payload) => { setPendingUpload(payload); go('processing'); }}
            quotaExceeded={quotaExceeded}
            trialStatus={trialStatus}
            onUpgrade={() => go('settings')}
          />
        );
      case 'processing':
        return (
          <ProcessingScreen
            status={forcedProcessingError ? 'error' : extractStatus}
            errorMessage={forcedProcessingError ? 'Tivemos uma instabilidade simulada pra revisão deste estado.' : extractError}
            leitura={extractResult?.leitura}
            onReady={() => go('editor', extractResult)}
            onRetry={!forcedProcessingError && extractRetryable ? retryExtract : null}
            onBackToUpload={forcedProcessingError ? () => go('upload') : backToUploadWithDraft}
          />
        );
      case 'editor':
        return (
          <EditorScreen
            session={currentSession}
            onBack={() => go('dashboard')}
            onPreview={() => go('portal', currentSession)}
            onPublish={publishSession}
            onGoToPortal={() => go('portal', currentSession)}
            publishError={publishError}
          />
        );
      case 'portal':
        return <PortalScreen onBack={() => go('dashboard')} previewMode session={currentSession} />;
      case 'portal-public':
        return <PortalScreen onBack={() => go('login')} publicEntry slug={PUBLIC_PORTAL_MATCH?.[1]} />;
      case 'settings':
        return <SettingsScreen billingError={billingError} />;
      default:
        return <LoginScreen onLogin={() => go('dashboard')} onGoSignup={() => go('signup')} />;
    }
  };

  const isShell = SHELL_SCREENS.has(canonical);
  const activeNav = NAV_FOR_SCREEN[canonical] || null;

  return (
    <div style={{ display: 'flex', minHeight: '100vh', background: theme.bg, fontFamily: 'Inter, sans-serif' }}>
      <ThemeSync />

      {isShell && (
        <>
          <Sidebar activeNav={activeNav} onNavigate={go} mode="docked" />
          <MobileNavButton onOpen={() => setDrawerOpen(true)} />
          {drawerOpen && (
            <Sidebar activeNav={activeNav} onNavigate={go} mode="drawer" onClose={() => setDrawerOpen(false)} />
          )}
        </>
      )}

      <div style={{ flex: 1, minWidth: 0 }}>
        {renderScreen()}
      </div>

      {openingSession && (
        <div style={{
          position: 'fixed', inset: 0, zIndex: 3500, background: 'rgba(16,13,10,0.35)',
          backdropFilter: 'blur(2px)', display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>
          <div style={{
            padding: '10px 18px', borderRadius: 999, background: '#171310',
            border: '1px solid rgba(245,241,232,0.14)', color: 'rgba(245,241,232,0.7)',
            fontSize: 12, fontFamily: 'Inter, sans-serif',
          }}>Abrindo…</div>
        </div>
      )}

      {/* Só com ?proto=1, e nunca no link público do consulente — ele não pode ver as telas mock de outras contas (P0-4). */}
      {PROTO_NAV && !PUBLIC_PORTAL_MATCH && screen !== 'portal-public' && <ProtoLauncher screen={screen} onJump={go} />}
    </div>
  );
};

const App = () => (
  <ThemeProvider>
    <AppInner />
  </ThemeProvider>
);

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