// app.jsx — auth-gated SPA root.
//
//   AuthGate → (unauthenticated) LoginScreen
//            → (role: client)    ClientRoot  → IntakeApp ⇄ ReportApp
//            → (role: admin)     AdminConsole → manage clients / edit+version
//                                               assessments / review intake / preview
//
// Data flows through the backend API (window.API). Report views still read the
// window.* globals — hydrated from /api/assessment via window.hydrateAssessment.
// Wrapped in an IIFE so local hook names don't collide with other Babel scripts.

(function () {
  'use strict';
  const { useState, useEffect, useRef } = React;
  const API = window.API;

  // ════════════════════════ REPORT SHELL (the 4-view deliverable) ════════════════════════
  function ReportApp({ onExit }) {
    const [view, setView] = useState('summary');
    const [dataVersion, setDataVersion] = useState(0);
    const [sidebarCollapsed, setSidebarCollapsed] = useState(false);

    const sections = Object.assign(
      { summary: true, data: true, workflows: true, roadmap: true },
      window.REPORT_CONFIG && window.REPORT_CONFIG.sections ? window.REPORT_CONFIG.sections : {}
    );

    useEffect(() => {
      window.__nav = setView;
      window.__refreshApp = () => setDataVersion(v => v + 1);
    }, []);
    useEffect(() => {
      const order = ['summary', 'data', 'workflows', 'roadmap'];
      if (!sections[view]) { const first = order.find(k => sections[k]); if (first) setView(first); }
    }, [dataVersion]);
    useEffect(() => { document.querySelector('.main')?.scrollTo({ top: 0, behavior: 'instant' }); }, [view]);

    return (
      <React.Fragment>
        {onExit && (
          <button onClick={onExit} title="Back" style={{ position: 'fixed', left: 14, bottom: 14, zIndex: 90, display: 'inline-flex', alignItems: 'center', gap: 6, background: '#0a0a0a', color: '#fff', border: 'none', borderRadius: 999, padding: '9px 16px', fontFamily: "'Outfit',sans-serif", fontSize: 13, fontWeight: 600, cursor: 'pointer', boxShadow: '0 6px 24px rgba(0,0,0,0.25)' }}>← Back</button>
        )}
        <div className={"app" + (sidebarCollapsed ? ' sidebar-collapsed' : '')}>
          <Sidebar active={view} onNav={setView} collapsed={sidebarCollapsed} onToggle={() => setSidebarCollapsed(c => !c)} sections={sections} />
          <div className="main">
            <Topbar active={view} />
            {sections.summary && view === 'summary' && <SummaryView key={"s-" + dataVersion} />}
            {sections.data && view === 'data' && <DataView key={"d-" + dataVersion} />}
            {sections.workflows && view === 'workflows' && <WorkflowsView key={"w-" + dataVersion} />}
            {sections.roadmap && view === 'roadmap' && <RoadmapView key={"r-" + dataVersion} />}
          </div>
        </div>
        {window.PrintDocument && <window.PrintDocument key={"p-" + dataVersion} />}
      </React.Fragment>
    );
  }

  // Admin "view as client" of the intake. Renders the real client IntakeApp with
  // the client's live intake data, but neutralizes every write path so clicking
  // around can't mutate the client's answers/questions/uploads (admin-session
  // calls would 400 anyway — these no-ops keep it clean and side-effect-free).
  function IntakePreview({ client, initial, onOpenReport, onExit }) {
    useEffect(() => {
      const API = window.API;
      const real = { intake: API.intake, questions: API.questions, uploads: API.uploads };
      const ok = () => Promise.resolve({ ok: true });
      API.intake = {
        get: real.intake.get,
        save: () => Promise.resolve({ ok: true, updatedAt: null }),
        submitStage1: ok, submitStage2: ok,
      };
      // Reflect sends locally so the control visibly works in preview, while the
      // read-only banner makes clear nothing is actually persisted.
      let previewQ = (initial.questions || []).slice();
      API.questions = {
        add: (text, section) => { const q = { id: 'preview-' + Date.now() + '-' + previewQ.length, text, section, sent: true, createdAt: 'just now (preview — not saved)' }; previewQ = previewQ.concat([q]); return Promise.resolve({ question: q }); },
        send: () => Promise.resolve({ questions: previewQ }),
        remove: ok,
      };
      API.uploads = Object.assign({}, real.uploads, { upload: () => Promise.resolve({ uploads: [] }), remove: ok });
      return () => { API.intake = real.intake; API.questions = real.questions; API.uploads = real.uploads; };
    }, []);

    return (
      <React.Fragment>
        <div style={{ position: 'fixed', top: 10, left: '50%', transform: 'translateX(-50%)', zIndex: 95, display: 'inline-flex', alignItems: 'center', gap: 8, background: '#3a2f12', color: '#f2d999', border: '1px solid rgba(224,168,58,0.45)', borderRadius: 999, padding: '7px 16px', fontFamily: "'Outfit',sans-serif", fontSize: 12, fontWeight: 600, boxShadow: '0 6px 20px rgba(0,0,0,0.2)', pointerEvents: 'none', whiteSpace: 'nowrap' }}>
          <IconEye size={13} />Admin preview · read-only. Changes here aren’t saved.
        </div>
        <button onClick={onExit} title="Exit preview" style={{ position: 'fixed', right: 14, bottom: 14, zIndex: 95, display: 'inline-flex', alignItems: 'center', gap: 8, background: '#3a2f12', color: '#f2d999', border: '1px solid rgba(224,168,58,0.45)', borderRadius: 999, padding: '8px 16px', fontFamily: "'Outfit',sans-serif", fontSize: 12, fontWeight: 600, cursor: 'pointer', boxShadow: '0 6px 20px rgba(0,0,0,0.2)' }}>
          <IconEye size={13} />Exit Preview
        </button>
        <window.IntakeApp client={client} initial={initial} onOpenReport={onOpenReport} onSignOut={onExit} />
      </React.Fragment>
    );
  }

  // ════════════════════════ LOGIN ════════════════════════
  function LoginScreen({ onLogin }) {
    const [email, setEmail] = useState('');
    const [pw, setPw] = useState('');
    const [err, setErr] = useState(null);
    const [busy, setBusy] = useState(false);
    const submit = () => {
      if (!email || !pw) { setErr('Email and password are required.'); return; }
      setBusy(true); setErr(null);
      API.login(email.trim(), pw)
        .then(p => onLogin(p))
        .catch(e => { setErr(e.message || 'Login failed.'); setBusy(false); });
    };
    return (
      <div className="linea-auth">
        <div className="linea-auth-card">
          <div style={{ textAlign: 'center', marginBottom: 40 }}>
            <div style={{ fontFamily: "'DM Serif Display',serif", fontSize: 30, color: '#0a0a0a', letterSpacing: '-0.01em' }}>AI Readiness Program</div>
          </div>
          <div className="linea-auth-box">
            <div style={{ fontSize: 14.5, fontWeight: 600, color: '#0a0a0a', marginBottom: 22 }}>Sign in to continue</div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 15 }}>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                <label style={{ fontSize: 11, fontWeight: 600, color: '#8b8d94', letterSpacing: '0.08em', textTransform: 'uppercase' }}>Email</label>
                <input type="email" value={email} onChange={e => setEmail(e.target.value)} placeholder="your@email.com" autoComplete="username" />
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                <label style={{ fontSize: 11, fontWeight: 600, color: '#8b8d94', letterSpacing: '0.08em', textTransform: 'uppercase' }}>Password</label>
                <input type="password" value={pw} onChange={e => setPw(e.target.value)} onKeyDown={e => e.key === 'Enter' && submit()} placeholder="••••••••" autoComplete="current-password" />
              </div>
            </div>
            {err && <div style={{ marginTop: 13, padding: '10px 13px', background: '#fef2f2', border: '1px solid rgba(185,28,28,0.18)', borderRadius: 6, fontSize: 13, color: '#991b1b' }}>{err}</div>}
            <button onClick={submit} disabled={busy} style={{ width: '100%', marginTop: 20, border: 'none', background: '#0a0a0a', color: '#fafafa', borderRadius: 8, height: 48, fontFamily: "'Outfit',sans-serif", fontSize: 14, fontWeight: 600, cursor: busy ? 'default' : 'pointer', opacity: busy ? 0.7 : 1 }}>{busy ? 'Signing in…' : 'Continue →'}</button>
          </div>
          <div style={{ textAlign: 'center', marginTop: 28 }}>
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 12, color: '#b8bac0', fontWeight: 500 }}>Powered by <LineaLogo size={12} color="#b8bac0" weight={600} /></span>
          </div>
        </div>
      </div>
    );
  }

  // Shown when the signed-in user still has must_reset set — they cannot use the
  // app until they replace the seed/temporary password.
  function ForcePasswordReset({ principal, onDone, onSignOut }) {
    const [cur, setCur] = useState('');
    const [pw, setPw] = useState('');
    const [confirm, setConfirm] = useState('');
    const [err, setErr] = useState(null);
    const [busy, setBusy] = useState(false);
    const inputStyle = { width: '100%', boxSizing: 'border-box' };
    const submit = () => {
      if (!cur || !pw) { setErr('Fill in all fields.'); return; }
      if (pw.length < 8) { setErr('New password must be at least 8 characters.'); return; }
      if (pw !== confirm) { setErr('New passwords do not match.'); return; }
      setBusy(true); setErr(null);
      API.changePassword(cur, pw)
        .then(() => onDone())
        .catch(e => { setErr(e.message || 'Could not update password.'); setBusy(false); });
    };
    return (
      <div className="linea-auth">
        <div className="linea-auth-card">
          <div style={{ textAlign: 'center', marginBottom: 40 }}>
            <div style={{ fontFamily: "'DM Serif Display',serif", fontSize: 30, color: '#0a0a0a', letterSpacing: '-0.01em' }}>Set a new password</div>
            <div style={{ fontSize: 13, color: '#8b8d94', marginTop: 8 }}>Required for {principal.user.email} before continuing.</div>
          </div>
          <div className="linea-auth-box">
            <div style={{ display: 'flex', flexDirection: 'column', gap: 15 }}>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                <label style={{ fontSize: 11, fontWeight: 600, color: '#8b8d94', letterSpacing: '0.08em', textTransform: 'uppercase' }}>Current password</label>
                <input type="password" value={cur} onChange={e => setCur(e.target.value)} placeholder="••••••••" autoComplete="current-password" style={inputStyle} />
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                <label style={{ fontSize: 11, fontWeight: 600, color: '#8b8d94', letterSpacing: '0.08em', textTransform: 'uppercase' }}>New password</label>
                <input type="password" value={pw} onChange={e => setPw(e.target.value)} placeholder="At least 8 characters" autoComplete="new-password" style={inputStyle} />
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                <label style={{ fontSize: 11, fontWeight: 600, color: '#8b8d94', letterSpacing: '0.08em', textTransform: 'uppercase' }}>Confirm new password</label>
                <input type="password" value={confirm} onChange={e => setConfirm(e.target.value)} onKeyDown={e => e.key === 'Enter' && submit()} placeholder="••••••••" autoComplete="new-password" style={inputStyle} />
              </div>
            </div>
            {err && <div style={{ marginTop: 13, padding: '10px 13px', background: '#fef2f2', border: '1px solid rgba(185,28,28,0.18)', borderRadius: 6, fontSize: 13, color: '#991b1b' }}>{err}</div>}
            <button onClick={submit} disabled={busy} style={{ width: '100%', marginTop: 20, border: 'none', background: '#0a0a0a', color: '#fafafa', borderRadius: 8, height: 48, fontFamily: "'Outfit',sans-serif", fontSize: 14, fontWeight: 600, cursor: busy ? 'default' : 'pointer', opacity: busy ? 0.7 : 1 }}>{busy ? 'Saving…' : 'Set password & continue →'}</button>
            <button onClick={onSignOut} style={{ width: '100%', marginTop: 10, border: 'none', background: 'none', color: '#8b8d94', fontFamily: "'Outfit',sans-serif", fontSize: 12.5, cursor: 'pointer' }}>Sign out</button>
          </div>
        </div>
      </div>
    );
  }

  function Loading({ label }) {
    return <div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: "'Outfit',sans-serif", color: '#8b8d94', fontSize: 14, background: '#fafafa' }}>{label || 'Loading…'}</div>;
  }

  // ════════════════════════ CLIENT ROOT ════════════════════════
  function ClientRoot({ principal, onSignOut }) {
    const [loading, setLoading] = useState(true);
    const [intake, setIntake] = useState(null);
    const [mode, setMode] = useState('intake'); // 'intake' | 'report'
    const [reportLoaded, setReportLoaded] = useState(false);
    const [pwOpen, setPwOpen] = useState(false);

    useEffect(() => {
      API.intake.get().then(d => { setIntake(d); setLoading(false); }).catch(() => setLoading(false));
    }, []);

    const openReport = () => {
      if (reportLoaded) { setMode('report'); return; }
      API.getAssessment().then(a => {
        if (a && a.delivered) { window.hydrateAssessment(a.payload, a.deep); setReportLoaded(true); setMode('report'); }
      });
    };

    if (loading || !intake) return <Loading />;
    if (mode === 'report') return <ReportApp onExit={() => setMode('intake')} />;
    return (
      <React.Fragment>
        <window.IntakeApp client={principal.client} initial={intake} onOpenReport={openReport} onSignOut={onSignOut} onChangePassword={() => setPwOpen(true)} />
        {pwOpen && <ClientPasswordModal email={principal.user.email} onClose={() => setPwOpen(false)} />}
      </React.Fragment>
    );
  }

  // Self-service password change for a signed-in client (reuses /auth/password).
  // Light "paper" theme to match the client intake/report shell.
  function ClientPasswordModal({ email, onClose }) {
    const [cur, setCur] = useState('');
    const [pw, setPw] = useState('');
    const [confirm, setConfirm] = useState('');
    const [err, setErr] = useState(null);
    const [done, setDone] = useState(false);
    const [busy, setBusy] = useState(false);
    const labelStyle = { fontSize: 11, fontWeight: 600, color: '#8b8d94', letterSpacing: '0.08em', textTransform: 'uppercase' };
    const inputStyle = { border: '1px solid rgba(10,10,10,0.14)', borderRadius: 6, padding: '0 13px', height: 44, fontFamily: "'Outfit',sans-serif", fontSize: 14, color: '#0a0a0a', width: '100%', boxSizing: 'border-box', background: '#fafafa' };
    const submit = () => {
      if (!cur || !pw) { setErr('Fill in all fields.'); return; }
      if (pw.length < 8) { setErr('New password must be at least 8 characters.'); return; }
      if (pw !== confirm) { setErr('New passwords do not match.'); return; }
      setBusy(true); setErr(null);
      API.changePassword(cur, pw)
        .then(() => { setDone(true); setTimeout(onClose, 1200); })
        .catch(e => { setErr(e.message || 'Could not update password.'); setBusy(false); });
    };
    return (
      <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(20,20,18,0.45)', zIndex: 95, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
        <div onClick={e => e.stopPropagation()} style={{ background: '#fff', borderRadius: 14, padding: '28px 28px 26px', width: '100%', maxWidth: 420, boxShadow: '0 20px 60px rgba(0,0,0,0.25)', fontFamily: "'Outfit',sans-serif" }}>
          <div style={{ fontFamily: "'DM Serif Display',serif", fontSize: 23, color: '#0a0a0a', letterSpacing: '-0.01em', marginBottom: 4 }}>Change password</div>
          <div style={{ fontSize: 13, color: '#8b8d94', marginBottom: 20 }}>{email}</div>
          {done ? (
            <div style={{ padding: '12px 14px', background: '#ecfdf3', border: '1px solid rgba(26,143,89,0.25)', borderRadius: 8, fontSize: 13.5, color: '#1a8f59', fontWeight: 600 }}>Password updated.</div>
          ) : (
            <React.Fragment>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                  <label style={labelStyle}>Current password</label>
                  <input type="password" value={cur} onChange={e => setCur(e.target.value)} placeholder="••••••••" autoComplete="current-password" style={inputStyle} />
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                  <label style={labelStyle}>New password</label>
                  <input type="password" value={pw} onChange={e => setPw(e.target.value)} placeholder="At least 8 characters" autoComplete="new-password" style={inputStyle} />
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                  <label style={labelStyle}>Confirm new password</label>
                  <input type="password" value={confirm} onChange={e => setConfirm(e.target.value)} onKeyDown={e => e.key === 'Enter' && submit()} placeholder="••••••••" autoComplete="new-password" style={inputStyle} />
                </div>
              </div>
              {err && <div style={{ marginTop: 13, padding: '10px 13px', background: '#fef2f2', border: '1px solid rgba(185,28,28,0.18)', borderRadius: 6, fontSize: 13, color: '#991b1b' }}>{err}</div>}
              <div style={{ display: 'flex', gap: 10, marginTop: 20 }}>
                <button onClick={onClose} style={{ flex: '0 0 auto', border: '1px solid rgba(10,10,10,0.15)', background: 'none', color: '#0a0a0a', borderRadius: 8, height: 44, padding: '0 18px', fontFamily: "'Outfit',sans-serif", fontSize: 13.5, fontWeight: 600, cursor: 'pointer' }}>Cancel</button>
                <button onClick={submit} disabled={busy} style={{ flex: 1, border: 'none', background: '#0a0a0a', color: '#fafafa', borderRadius: 8, height: 44, fontFamily: "'Outfit',sans-serif", fontSize: 13.5, fontWeight: 600, cursor: busy ? 'default' : 'pointer', opacity: busy ? 0.7 : 1 }}>{busy ? 'Saving…' : 'Update password'}</button>
              </div>
            </React.Fragment>
          )}
        </div>
      </div>
    );
  }

  // ════════════════════════ ADMIN CONSOLE ════════════════════════
  function AdminConsole({ principal, onSignOut }) {
    const [clients, setClients] = useState(null);
    const [selected, setSelected] = useState(null); // clientId
    const [creating, setCreating] = useState(false);
    const [pwOpen, setPwOpen] = useState(false);

    const reload = () => API.admin.clients().then(d => setClients(d.clients));
    useEffect(() => { reload(); }, []);

    if (!clients) return <Loading label="Loading clients…" />;

    if (selected != null) {
      const client = clients.find(c => c.id === selected);
      return <AdminClient client={client} onBack={() => { setSelected(null); reload(); }} onSignOut={onSignOut} />;
    }

    return (
      <div style={{ fontFamily: "'Outfit',sans-serif", minHeight: '100vh', background: '#111110', color: '#fafafa' }}>
        <div style={{ background: '#0a0a0a', borderBottom: '1px solid rgba(255,255,255,0.07)', height: 52, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 28px' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
            <LineaLogo size={15} color="#fafafa" weight={700} />
            <span style={{ width: 1, height: 16, background: 'rgba(255,255,255,0.12)' }} />
            <span style={{ fontSize: 12.5, color: '#8b8d94', fontWeight: 500 }}>Client Console</span>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
            <span style={{ fontSize: 12.5, color: '#8b8d94' }}>{principal.user.email}</span>
            <button onClick={() => setPwOpen(true)} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, background: 'none', border: '1px solid rgba(255,255,255,0.14)', color: '#d3d4d8', borderRadius: 6, padding: '6px 12px', fontSize: 12.5, cursor: 'pointer', fontFamily: "'Outfit',sans-serif" }}><IconKey size={13} />Change password</button>
            <button onClick={onSignOut} style={{ background: 'none', border: '1px solid rgba(255,255,255,0.14)', color: '#d3d4d8', borderRadius: 6, padding: '6px 12px', fontSize: 12.5, cursor: 'pointer', fontFamily: "'Outfit',sans-serif" }}>Sign Out</button>
          </div>
        </div>

        <div style={{ maxWidth: 880, margin: '0 auto', padding: '40px 24px 80px' }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', marginBottom: 24 }}>
            <div>
              <h1 style={{ fontFamily: "'DM Serif Display',serif", fontWeight: 400, fontSize: 30, margin: '0 0 6px' }}>Clients</h1>
              <p style={{ fontSize: 13.5, color: '#8b8d94', margin: 0 }}>Create and manage client engagements, assessments, and intake.</p>
            </div>
            <button onClick={() => setCreating(true)} style={{ display: 'inline-flex', alignItems: 'center', gap: 7, border: 'none', background: '#2e5dff', color: '#fff', borderRadius: 8, padding: '0 20px', height: 44, fontFamily: "'Outfit',sans-serif", fontSize: 14, fontWeight: 600, cursor: 'pointer' }}><IconPlus size={16} />New Client</button>
          </div>

          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {clients.map(c => (
              <button key={c.id} onClick={() => setSelected(c.id)} style={{ textAlign: 'left', background: '#1c1c1a', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 10, padding: '16px 20px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 16 }}>
                <div style={{ flex: 1 }}>
                  <div style={{ fontSize: 15.5, fontWeight: 600, color: '#fafafa' }}>{c.name}</div>
                  <div style={{ fontSize: 12.5, color: '#8b8d94', marginTop: 3 }}>{c.industry || '—'} · {c.loginEmail || 'no login set'}</div>
                </div>
                <div style={{ width: 156, flexShrink: 0 }}><StatusPill prefix="Report" status={c.assessmentStatus} map={REPORT_META} /></div>
                <div style={{ width: 196, flexShrink: 0 }}><StatusPill prefix="Intake" status={c.intakeStatus} map={INTAKE_META} /></div>
                <span style={{ color: '#5e6068' }}>›</span>
              </button>
            ))}
          </div>
        </div>

        {creating && <NewClientModal onClose={() => setCreating(false)} onCreated={(id) => { setCreating(false); reload().then(() => setSelected(id)); }} />}
        {pwOpen && <ChangePasswordModal onClose={() => setPwOpen(false)} />}
      </div>
    );
  }

  function Badge({ children, tone, icon }) {
    const map = { good: ['#13351f', '#5fd29a'], blue: ['#16234d', '#9db4ff'], muted: ['rgba(255,255,255,0.06)', '#9a9ca2'] };
    const [bg, fg] = map[tone] || map.muted;
    return <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 11, fontWeight: 600, padding: '4px 10px', borderRadius: 999, background: bg, color: fg, whiteSpace: 'nowrap' }}>{icon}{children}</span>;
  }

  // Status → Title Case label + tone + icon. Drives the pills on the client list
  // and the client admin home so both read consistently.
  const REPORT_META = {
    draft:     { label: 'Draft',     tone: 'muted', Icon: IconFile },
    delivered: { label: 'Delivered', tone: 'good',  Icon: IconRocket },
  };
  const INTAKE_META = {
    in_progress:      { label: 'In Progress',       tone: 'muted', Icon: IconClock },
    stage1_submitted: { label: 'Stage 1 Submitted', tone: 'blue',  Icon: IconCheck },
    stage2_unlocked:  { label: 'Stage 2 Unlocked',  tone: 'blue',  Icon: IconKey },
    stage2_submitted: { label: 'Stage 2 Submitted', tone: 'good',  Icon: IconCheck },
    delivered:        { label: 'Delivered',         tone: 'good',  Icon: IconRocket },
  };
  function StatusPill({ prefix, status, map }) {
    const m = map[status] || {
      label: status ? status.replace(/_/g, ' ').replace(/\b\w/g, (ch) => ch.toUpperCase()) : '—',
      tone: 'muted', Icon: IconDot,
    };
    const I = m.Icon;
    return <Badge tone={m.tone} icon={<I size={12} />}>{(prefix ? prefix + ': ' : '') + m.label}</Badge>;
  }

  // Self-service password change for the signed-in admin (reuses /auth/password).
  function ChangePasswordModal({ onClose }) {
    const [cur, setCur] = useState('');
    const [pw, setPw] = useState('');
    const [confirm, setConfirm] = useState('');
    const [err, setErr] = useState(null);
    const [done, setDone] = useState(false);
    const [busy, setBusy] = useState(false);
    const dark = { background: '#1c1c1a', border: '1px solid rgba(255,255,255,0.1)', borderRadius: 6, padding: '0 14px', height: 44, fontFamily: "'Outfit',sans-serif", fontSize: 14, color: '#fafafa', width: '100%', boxSizing: 'border-box' };
    const submit = () => {
      if (!cur || !pw) { setErr('Fill in all fields.'); return; }
      if (pw.length < 8) { setErr('New password must be at least 8 characters.'); return; }
      if (pw !== confirm) { setErr('New passwords do not match.'); return; }
      setBusy(true); setErr(null);
      API.changePassword(cur, pw)
        .then(() => { setDone(true); setTimeout(onClose, 1000); })
        .catch(e => { setErr(e.message || 'Could not update password.'); setBusy(false); });
    };
    return (
      <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.6)', zIndex: 90, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
        <div onClick={e => e.stopPropagation()} style={{ background: '#161614', border: '1px solid rgba(255,255,255,0.1)', borderRadius: 12, padding: 28, width: 420, maxWidth: '100%' }}>
          <h2 style={{ fontFamily: "'DM Serif Display',serif", fontWeight: 400, fontSize: 22, margin: '0 0 18px', color: '#fafafa' }}>Change password</h2>
          {done ? (
            <div style={{ display: 'inline-flex', alignItems: 'center', gap: 7, fontSize: 14, color: '#5fd29a' }}><IconCheck size={15} />Password updated.</div>
          ) : (
            <React.Fragment>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
                <input style={dark} type="password" placeholder="Current password" value={cur} onChange={e => setCur(e.target.value)} autoComplete="current-password" />
                <input style={dark} type="password" placeholder="New password (at least 8 characters)" value={pw} onChange={e => setPw(e.target.value)} autoComplete="new-password" />
                <input style={dark} type="password" placeholder="Confirm new password" value={confirm} onChange={e => setConfirm(e.target.value)} onKeyDown={e => e.key === 'Enter' && submit()} autoComplete="new-password" />
              </div>
              {err && <div style={{ marginTop: 12, color: '#ff8a7a', fontSize: 13 }}>{err}</div>}
              <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10, marginTop: 20 }}>
                <button onClick={onClose} style={{ background: 'none', border: '1px solid rgba(255,255,255,0.14)', color: '#d3d4d8', borderRadius: 8, padding: '0 16px', height: 42, cursor: 'pointer', fontFamily: "'Outfit',sans-serif", fontSize: 13.5 }}>Cancel</button>
                <button onClick={submit} disabled={busy} style={{ border: 'none', background: '#2e5dff', color: '#fff', borderRadius: 8, padding: '0 20px', height: 42, cursor: busy ? 'default' : 'pointer', fontFamily: "'Outfit',sans-serif", fontSize: 13.5, fontWeight: 600, opacity: busy ? 0.7 : 1 }}>{busy ? 'Saving…' : 'Update password'}</button>
              </div>
            </React.Fragment>
          )}
        </div>
      </div>
    );
  }

  function NewClientModal({ onClose, onCreated }) {
    const [f, setF] = useState({ name: '', industry: '', headcount: '', locations: '', short_name: '' });
    const [err, setErr] = useState(null);
    const set = k => e => setF(s => Object.assign({}, s, { [k]: e.target.value }));
    const dark = { background: '#1c1c1a', border: '1px solid rgba(255,255,255,0.1)', borderRadius: 6, padding: '0 14px', height: 44, fontFamily: "'Outfit',sans-serif", fontSize: 14, color: '#fafafa', width: '100%', boxSizing: 'border-box' };
    const create = () => {
      if (!f.name.trim()) { setErr('Client name is required.'); return; }
      API.admin.createClient(f).then(r => onCreated(r.client.id)).catch(e => setErr(e.message));
    };
    return (
      <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.6)', zIndex: 90, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
        <div onClick={e => e.stopPropagation()} style={{ background: '#161614', border: '1px solid rgba(255,255,255,0.1)', borderRadius: 12, padding: 28, width: 460, maxWidth: '100%' }}>
          <h2 style={{ fontFamily: "'DM Serif Display',serif", fontWeight: 400, fontSize: 22, margin: '0 0 18px', color: '#fafafa' }}>New Client</h2>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
            <input style={dark} placeholder="Company name *" value={f.name} onChange={set('name')} />
            <input style={dark} placeholder="Short name (e.g. Marlow)" value={f.short_name} onChange={set('short_name')} />
            <input style={dark} placeholder="Industry" value={f.industry} onChange={set('industry')} />
            <input style={dark} placeholder="Company size (e.g. 26–50)" value={f.headcount} onChange={set('headcount')} />
            <input style={dark} placeholder="Locations" value={f.locations} onChange={set('locations')} />
          </div>
          {err && <div style={{ marginTop: 12, color: '#ff8a7a', fontSize: 13 }}>{err}</div>}
          <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10, marginTop: 20 }}>
            <button onClick={onClose} style={{ background: 'none', border: '1px solid rgba(255,255,255,0.14)', color: '#d3d4d8', borderRadius: 8, padding: '0 16px', height: 42, cursor: 'pointer', fontFamily: "'Outfit',sans-serif", fontSize: 13.5 }}>Cancel</button>
            <button onClick={create} style={{ border: 'none', background: '#2e5dff', color: '#fff', borderRadius: 8, padding: '0 20px', height: 42, cursor: 'pointer', fontFamily: "'Outfit',sans-serif", fontSize: 13.5, fontWeight: 600 }}>Create Client</button>
          </div>
        </div>
      </div>
    );
  }

  // ── per-client admin workspace ──────────────────────────────────────────────
  function AdminClient({ client, onBack, onSignOut }) {
    const [tab, setTab] = useState('home');
    const [data, setData] = useState(null);     // { assessment, revision, revisions }
    const [intake, setIntake] = useState(null);
    const [preview, setPreview] = useState(null); // null | 'intake' | 'report'
    const [reportReturnsToIntake, setReportReturnsToIntake] = useState(false);
    const [msg, setMsg] = useState(null);
    const deepRef = useRef(null);

    const blankPayload = () => ({
      client: { name: client.name, shortName: client.short_name, industry: client.industry, headcount: client.headcount, locations: client.locations },
      readiness: { subscores: [{ key: 'data', label: 'Data', value: 50, note: '' }, { key: 'process', label: 'Process', value: 50, note: '' }, { key: 'team', label: 'Team', value: 50, note: '' }], heroLabel: '', recommendedPhase: '', annualWagePlaceholder: 60000, fteHoursPerYear: 2080, timelineMonths: 6 },
      findings: [], dataSources: [], workflows: [], roadmap: [], recommendedNext: {},
      reportConfig: { sections: { summary: true, data: true, workflows: true, roadmap: true }, dataReadiness: { enhanced: false, showLogos: true } },
    });
    const loadAssessment = () => API.admin.getAssessment(client.id).then(d => {
      deepRef.current = d.revision ? d.revision.deep : { dataObjects: {}, sops: {}, sopRevisions: {} };
      window.hydrateAssessment(d.revision ? d.revision.payload : blankPayload(), deepRef.current);
      // wire the AdminView save → new revision
      window.__persistAssessment = (payload) => API.admin.saveAssessment(client.id, payload, deepRef.current, 'Edited in console').then(r => { loadAssessment(); return r; });
      setData(d);
    });
    const loadIntake = () => API.admin.intake(client.id).then(setIntake);

    useEffect(() => { loadAssessment(); loadIntake(); return () => { delete window.__persistAssessment; }; }, [client.id]);

    const flash = (m) => { setMsg(m); setTimeout(() => setMsg(null), 2500); };
    const deliver = () => API.admin.deliver(client.id).then(() => { flash('Delivered.'); loadAssessment(); });
    const toDraft = () => API.admin.draft(client.id).then(() => { flash('Set to draft.'); loadAssessment(); });
    const restore = (v) => API.admin.restore(client.id, v).then(() => { flash('Restored v' + v + '.'); loadAssessment(); });

    const hydrateReport = () => { if (data && data.revision) window.hydrateAssessment(data.revision.payload, data.revision.deep); };
    const openReportFromIntake = () => { hydrateReport(); setReportReturnsToIntake(true); setPreview('report'); };

    if (preview === 'report') {
      // ensure window.* reflects this client before previewing
      hydrateReport();
      return <ReportApp onExit={() => { const back = reportReturnsToIntake; setReportReturnsToIntake(false); setPreview(back ? 'intake' : null); }} />;
    }
    if (preview === 'intake') {
      if (!intake || !window.IntakeApp) return <Loading label="Loading intake…" />;
      const initial = {
        status: intake.status,
        activeSection: intake.activeSection,
        answers: intake.answers || {},
        stage2Prefill: intake.stage2Prefill || null,
        reportDelivered: !!(data && data.assessment && data.assessment.status === 'delivered'),
        questions: intake.questions || [],
        uploads: intake.uploads || [],
      };
      return <IntakePreview client={client} initial={initial} onOpenReport={openReportFromIntake} onExit={() => setPreview(null)} />;
    }

    const openQuestions = intake ? intake.questions.filter(q => q.adminStatus !== 'addressed').length : 0;
    const TABS = [
      ['home', 'Home', IconHome],
      ['intake', 'Intake', IconClipboard, openQuestions],
      ['assessment', 'Assessment', IconSparkles],
      ['delivery', 'Delivery & Versions', IconLayers],
      ['access', 'Access', IconUsers],
      ['activity', 'Activity', IconClock],
    ];
    const tabBtn = (k, l, I, badge) => (
      <button key={k} onClick={() => setTab(k)} style={{ display: 'inline-flex', alignItems: 'center', gap: 7, background: 'none', border: 'none', borderBottom: '2px solid ' + (tab === k ? '#2e5dff' : 'transparent'), color: tab === k ? '#fafafa' : '#8b8d94', padding: '11px 2px', marginRight: 24, fontSize: 13.5, fontWeight: 600, cursor: 'pointer', fontFamily: "'Outfit',sans-serif" }}>
        <I size={15} />{l}
        {badge ? <span style={{ background: '#a06d10', color: '#fff', fontSize: 10.5, fontWeight: 700, borderRadius: 999, padding: '1px 6px', minWidth: 16, textAlign: 'center' }}>{badge}</span> : null}
      </button>
    );

    return (
      <div style={{ fontFamily: "'Outfit',sans-serif", minHeight: '100vh', background: tab === 'assessment' ? '#fafafa' : '#111110', color: tab === 'assessment' ? '#0a0a0a' : '#fafafa' }}>
        <div style={{ background: '#0a0a0a', color: '#fafafa', borderBottom: '1px solid rgba(255,255,255,0.07)', minHeight: 52, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 28px', flexWrap: 'wrap', gap: 8 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
            <button onClick={onBack} style={{ background: 'none', border: 'none', color: '#9db4ff', cursor: 'pointer', fontSize: 13, fontFamily: "'Outfit',sans-serif" }}>← Clients</button>
            <span style={{ fontFamily: "'DM Serif Display',serif", fontSize: 16 }}>{client.name}</span>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
            {msg && <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 12.5, color: '#5fd29a' }}><IconCheck size={13} />{msg}</span>}
            <span style={{ fontSize: 11, color: '#6b6d74', fontWeight: 600 }}>View as client:</span>
            <button onClick={() => setPreview('intake')} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, background: 'none', border: '1px solid rgba(255,255,255,0.16)', color: '#d3d4d8', borderRadius: 6, padding: '6px 12px', fontSize: 12.5, cursor: 'pointer', fontFamily: "'Outfit',sans-serif" }}><IconClipboard size={14} />Intake</button>
            <button onClick={() => { hydrateReport(); setReportReturnsToIntake(false); setPreview('report'); }} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, background: 'none', border: '1px solid rgba(255,255,255,0.16)', color: '#d3d4d8', borderRadius: 6, padding: '6px 12px', fontSize: 12.5, cursor: 'pointer', fontFamily: "'Outfit',sans-serif" }}><IconEye size={14} />Report</button>
          </div>
        </div>

        <div style={{ background: '#0a0a0a', borderBottom: '1px solid rgba(255,255,255,0.07)', padding: '0 28px' }}>
          {TABS.map(([k, l, I, badge]) => tabBtn(k, l, I, badge))}
        </div>

        {tab === 'home' && (
          <AdminHome client={client} data={data} intake={intake} setTab={setTab} onDeliver={deliver}
            onPreview={() => { hydrateReport(); setReportReturnsToIntake(false); setPreview('report'); }}
            onPreviewIntake={() => setPreview('intake')} />
        )}

        {tab === 'assessment' && (
          <div>
            <div style={{ background: '#f4f6fe', borderBottom: '1px solid rgba(46,93,255,0.18)', padding: '10px 28px', fontSize: 13, color: '#1c3db8' }}>
              Editing the live assessment. Each save in the Editor tab creates a new immutable revision. {data && data.revision ? 'Current: v' + data.revision.version : 'No revision yet — your first save creates v1.'}
            </div>
            {data && window.AdminView ? <window.AdminView key={'adm-' + client.id + '-' + (data.revision ? data.revision.version : 0)} /> : <Loading />}
          </div>
        )}

        {tab === 'delivery' && data && (
          <div style={{ maxWidth: 720, margin: '0 auto', padding: '32px 24px 80px' }}>
            <div style={{ background: '#1c1c1a', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 10, padding: 20, marginBottom: 22 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                <div>
                  <div style={{ fontSize: 14.5, fontWeight: 600 }}>Report Status: {data.assessment.status}</div>
                  <div style={{ fontSize: 12.5, color: '#8b8d94', marginTop: 3 }}>Delivered reports are visible to the client in Stage 3.</div>
                </div>
                {data.assessment.status === 'delivered'
                  ? <button onClick={toDraft} style={btnGhost}>Revert to Draft</button>
                  : <button onClick={deliver} style={iconBtn(btnBlue)}><IconRocket size={15} />Deliver to Client</button>}
              </div>
            </div>
            <div style={{ fontSize: 11, letterSpacing: '0.09em', textTransform: 'uppercase', color: '#5e6068', fontWeight: 600, marginBottom: 12 }}>Revision History</div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              {data.revisions.map(r => (
                <div key={r.id} style={{ background: '#1c1c1a', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 8, padding: '12px 16px', display: 'flex', alignItems: 'center', gap: 12 }}>
                  <span style={{ fontFamily: "'DM Serif Display',serif", fontSize: 18, color: '#9db4ff', minWidth: 42 }}>v{r.version}</span>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontSize: 13.5, color: '#d3d4d8' }}>{r.note || '—'}</div>
                    <div style={{ fontSize: 11.5, color: '#5e6068', marginTop: 2 }}>{r.created_at}{data.assessment.publishedRevisionId === r.id ? ' · published' : ''}</div>
                  </div>
                  {data.assessment.publishedRevisionId !== r.id && <button onClick={() => restore(r.version)} style={btnGhostSm}>Restore</button>}
                </div>
              ))}
            </div>
          </div>
        )}

        {tab === 'intake' && <AdminIntake client={client} intake={intake} reload={loadIntake} flash={flash} />}
        {tab === 'activity' && <AdminActivity intake={intake} />}
        {tab === 'access' && <AdminAccess client={client} flash={flash} />}
      </div>
    );
  }

  const btnBlue = { border: 'none', background: '#2e5dff', color: '#fff', borderRadius: 8, padding: '0 18px', height: 42, fontFamily: "'Outfit',sans-serif", fontSize: 13.5, fontWeight: 600, cursor: 'pointer' };
  // Merge a button base style with the flex bits needed to sit an icon beside a label.
  const iconBtn = (base) => Object.assign({ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 7 }, base);
  const btnGhost = { background: 'none', border: '1px solid rgba(255,255,255,0.16)', color: '#d3d4d8', borderRadius: 8, padding: '0 16px', height: 42, fontFamily: "'Outfit',sans-serif", fontSize: 13.5, cursor: 'pointer' };
  const btnGhostSm = { background: 'none', border: '1px solid rgba(255,255,255,0.16)', color: '#9db4ff', borderRadius: 6, padding: '6px 12px', fontSize: 12.5, cursor: 'pointer', fontFamily: "'Outfit',sans-serif" };
  const darkInput = { background: '#1c1c1a', border: '1px solid rgba(255,255,255,0.1)', borderRadius: 6, padding: '11px 14px', fontFamily: "'Outfit',sans-serif", fontSize: 14, color: '#fafafa', width: '100%', boxSizing: 'border-box' };

  function AdminIntake({ client, intake, reload, flash }) {
    const [prefill, setPrefill] = useState('');
    useEffect(() => { if (intake && intake.stage2Prefill) setPrefill(JSON.stringify(intake.stage2Prefill, null, 2)); }, [intake]);
    if (!intake) return <Loading label="Loading intake…" />;
    const unlock = () => {
      let parsed = null;
      if (prefill.trim()) { try { parsed = JSON.parse(prefill); } catch (e) { flash('Prefill is not valid JSON.'); return; } }
      API.admin.unlockStage2(client.id, parsed).then(() => { flash('Stage 2 unlocked.'); reload(); });
    };
    return (
      <div style={{ maxWidth: 760, margin: '0 auto', padding: '32px 24px 80px' }}>
        <div style={{ display: 'flex', gap: 10, marginBottom: 20, flexWrap: 'wrap' }}>
          <Badge tone="blue">Status: {intake.status.replace(/_/g, ' ')}</Badge>
          <Badge tone="muted">{intake.uploads.length} uploads</Badge>
          <Badge tone="muted">{intake.questions.length} questions</Badge>
          <Badge tone="muted">{intake.snapshots.length} snapshots</Badge>
        </div>

        <Section title="Client Responses">
          <ResponsesView answers={intake.answers} uploads={intake.uploads} clientId={client.id} />
        </Section>

        <Section title={'Questions for Linea' + (intake.questions.length ? ' (' + intake.questions.filter(q => q.adminStatus !== 'addressed').length + ' Open)' : '')}>
          <QuestionTriage clientId={client.id} questions={intake.questions} reload={reload} flash={flash} />
        </Section>

        <Section title="Unlock Stage 2 (Workflow Confirmation)">
          <p style={{ fontSize: 13, color: '#8b8d94', margin: '0 0 10px' }}>Paste the inferred workflows/systems JSON the client will confirm. Shape: <code>{'{ "workflows": [{ "unit": "Finance", "items": [{ "id": "wf1", "name": "...", "basis": "..." }] }], "systems": [{ "id":"sys1","name":"..." }] }'}</code></p>
          <textarea value={prefill} onChange={e => setPrefill(e.target.value)} rows={10} style={Object.assign({}, darkInput, { fontFamily: 'monospace', fontSize: 12.5, resize: 'vertical' })} placeholder='{ "workflows": [], "systems": [] }' />
          <div style={{ marginTop: 12 }}><button onClick={unlock} style={iconBtn(btnBlue)}><IconCheck size={15} />Unlock Stage 2 for Client</button></div>
        </Section>
      </div>
    );
  }

  // The Activity tab — intake-scoped internal tracking, separate from the review.
  function AdminActivity({ intake }) {
    if (!intake) return <Loading label="Loading activity…" />;
    return (
      <div style={{ maxWidth: 760, margin: '0 auto', padding: '32px 24px 80px' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 20, flexWrap: 'wrap' }}>
          <Badge tone="blue">Intake</Badge>
          <span style={{ fontSize: 13, color: '#8b8d94' }}>Coarse internal tracking: logins, section visits, and submissions. No granular engagement data.</span>
        </div>
        <Section title="Section Activity">
          <SectionActivity meta={intake.answers && intake.answers._sectionMeta} />
        </Section>
        <Section title="Activity Log">
          <ActivityLog events={intake.events} />
        </Section>
      </div>
    );
  }

  // Coarse engagement: when each section was last visited / last updated.
  function SectionActivity({ meta }) {
    const visits = (meta && meta.visits) || {};
    const updated = (meta && meta.updated) || {};
    const SECTIONS = (window.INTAKE_DATA && window.INTAKE_DATA.SECTIONS) || [];
    const rows = SECTIONS.map(s => ({ id: s.id, label: s.id + ' · ' + s.label })).concat([{ id: 'review', label: 'Review & Submit' }]);
    const fmt = (iso) => { if (!iso) return '—'; try { return new Date(iso).toLocaleString(undefined, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); } catch (e) { return iso; } };
    const active = rows.filter(r => visits[r.id] || updated[r.id]);
    if (!active.length) return <div style={{ fontSize: 13, color: '#6b6d74' }}>No section activity recorded yet.</div>;
    const head = { padding: '7px 12px', fontSize: 10.5, letterSpacing: '0.06em', textTransform: 'uppercase', color: '#6b6d74', fontWeight: 600, textAlign: 'left' };
    const cell = { padding: '9px 12px', fontSize: 12.5, color: '#9a9ca2', borderTop: '1px solid rgba(255,255,255,0.05)' };
    return (
      <div style={{ background: '#1c1c1a', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 8, overflow: 'hidden' }}>
        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead><tr><th style={head}>Section</th><th style={head}>Last visited</th><th style={head}>Last updated</th></tr></thead>
          <tbody>
            {active.map(r => (
              <tr key={r.id}>
                <td style={Object.assign({}, cell, { color: '#fafafa', fontWeight: 500 })}>{r.label}</td>
                <td style={cell}>{fmt(visits[r.id])}</td>
                <td style={cell}>{fmt(updated[r.id])}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    );
  }

  // Readable "Chrome · macOS" from a user-agent string (best-effort, no deps).
  function uaShort(ua) {
    if (!ua) return '';
    const browser = /Edg\//.test(ua) ? 'Edge' : /OPR\/|Opera/.test(ua) ? 'Opera' : /Chrome\//.test(ua) ? 'Chrome' : (/Safari\//.test(ua) && /Version\//.test(ua)) ? 'Safari' : /Firefox\//.test(ua) ? 'Firefox' : '';
    const os = /Windows/.test(ua) ? 'Windows' : /(iPhone|iPad|iPod)/.test(ua) ? 'iOS' : /(Mac OS X|Macintosh)/.test(ua) ? 'macOS' : /Android/.test(ua) ? 'Android' : /Linux/.test(ua) ? 'Linux' : '';
    return [browser, os].filter(Boolean).join(' · ');
  }

  // Internal activity log: logins/logouts, section visits, uploads, questions,
  // and stage submissions — each with the request IP and device. Coarse by design.
  function ActivityLog({ events }) {
    if (!events || !events.length) return <div style={{ fontSize: 13, color: '#6b6d74' }}>No activity recorded yet.</div>;
    const LABEL = { login: 'Signed in', logout: 'Signed out', visit: 'Visited', upload: 'Uploaded file', upload_remove: 'Removed file', question: 'Asked a question', stage1_submit: 'Submitted Stage 1', stage2_submit: 'Submitted Stage 2' };
    const secLabel = (id) => { const s = ((window.INTAKE_DATA && window.INTAKE_DATA.SECTIONS) || []).find(x => x.id === id); return s ? s.id + ' · ' + s.label : (id || ''); };
    const fmt = (ts) => { try { return new Date(String(ts).replace(' ', 'T') + 'Z').toLocaleString(undefined, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); } catch (e) { return ts; } };
    // events that simply name an identity (login/logout) don't need their detail echoed
    const noDetail = { login: 1, logout: 1, visit: 1 };
    const head = { padding: '7px 12px', fontSize: 10.5, letterSpacing: '0.06em', textTransform: 'uppercase', color: '#6b6d74', fontWeight: 600, textAlign: 'left' };
    const cell = { padding: '9px 12px', fontSize: 12.5, color: '#9a9ca2', borderTop: '1px solid rgba(255,255,255,0.05)', verticalAlign: 'top' };
    return (
      <div style={{ background: '#1c1c1a', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 8, overflow: 'hidden' }}>
        <div style={{ maxHeight: 380, overflow: 'auto' }}>
          <table style={{ width: '100%', borderCollapse: 'collapse' }}>
            <thead><tr><th style={head}>When</th><th style={head}>Event</th><th style={head}>IP &amp; device</th></tr></thead>
            <tbody>
              {events.map(e => {
                const device = uaShort(e.user_agent);
                return (
                  <tr key={e.id}>
                    <td style={Object.assign({}, cell, { whiteSpace: 'nowrap' })}>{fmt(e.created_at)}</td>
                    <td style={Object.assign({}, cell, { color: '#fafafa' })}>
                      {LABEL[e.type] || e.type}
                      {e.type === 'visit' && e.detail ? ' ' + secLabel(e.detail) : null}
                      {e.detail && !noDetail[e.type] ? <span style={{ color: '#8b8d94', fontWeight: 400 }}>{' · ' + e.detail}</span> : null}
                    </td>
                    <td style={cell}>
                      <span style={{ fontFamily: 'monospace', fontSize: 11.5 }}>{e.ip || '—'}</span>
                      {device ? <div style={{ fontSize: 11, color: '#6b6d74', marginTop: 2 }}>{device}</div> : null}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>
    );
  }

  // Readable, section-by-section rendering of the client's intake answers.
  function ResponsesView({ answers, uploads, clientId }) {
    const a = answers || {};
    const DOCS = (window.INTAKE_DATA && window.INTAKE_DATA.DOCS) || [];
    const uploadsByDoc = {};
    (uploads || []).forEach(u => { (uploadsByDoc[u.doc_id || '_'] = uploadsByDoc[u.doc_id || '_'] || []).push(u); });
    const ALT = { describe: 'Described', none: 'N/A', kickoff: 'Will bring to kickoff', upload: 'Uploaded', select: 'Added manually' };

    const Field = ({ label, value }) => (value == null || value === '') ? null : (
      <div style={{ marginBottom: 12 }}>
        <div style={{ fontSize: 11, color: '#6b6d74', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 3 }}>{label}</div>
        <div style={{ fontSize: 13.5, color: '#d3d4d8', whiteSpace: 'pre-wrap' }}>{value}</div>
      </div>
    );
    const Group = ({ title, children }) => (
      <div style={{ background: '#1c1c1a', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 8, padding: '16px 18px', marginBottom: 12 }}>
        <div style={{ fontSize: 13, fontWeight: 600, color: '#9db4ff', marginBottom: 12 }}>{title}</div>
        {children}
      </div>
    );
    const empty = (v) => !v || (Array.isArray(v) && !v.length);

    return (
      <div>
        {/* A — Documents */}
        <Group title="A · Documents">
          {DOCS.map(d => {
            const r = (a.docs && a.docs[d.id]) || {};
            const files = uploadsByDoc[d.id] || [];
            // Hybrid: a doc may combine several inputs. Show them all (reads the
            // new `modes` array, falling back to the legacy single `choice`).
            const modes = Array.isArray(r.modes) ? r.modes : (r.choice && r.choice !== 'upload' ? [r.choice] : []);
            const labels = [];
            if (files.length || r.choice === 'upload') labels.push('Uploaded');
            modes.forEach(m => labels.push(ALT[m] || m));
            const statusLabel = labels.length ? labels.join(' · ') : 'No answer';
            return (
              <div key={d.id} style={{ padding: '8px 0', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', gap: 10 }}>
                  <span style={{ fontSize: 13.5, color: '#d3d4d8' }}>{d.title}</span>
                  <span style={{ fontSize: 11.5, color: labels.length ? '#5fd29a' : '#6b6d74' }}>{statusLabel}</span>
                </div>
                {r.desc && <div style={{ fontSize: 12.5, color: '#9a9ca2', marginTop: 4 }}>{r.desc}</div>}
                {files.map(u => (
                  <a key={u.id} href={'/api/uploads/' + u.id + '?clientId=' + clientId} target="_blank" rel="noreferrer" style={{ display: 'inline-block', marginTop: 6, marginRight: 10, color: '#9db4ff', fontSize: 12.5 }}>↓ {u.original_name}</a>
                ))}
              </div>
            );
          })}
        </Group>

        {/* B — Organization */}
        <Group title="B · Your Organization">
          <Field label="What the company does" value={a.oneliner} />
          <Field label="Headcount" value={a.headcount} />
          {!empty(a.units) && (
            <div style={{ marginBottom: 12 }}>
              <div style={{ fontSize: 11, color: '#6b6d74', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 5 }}>Business units</div>
              {a.units.map((u, i) => (
                <div key={i} style={{ fontSize: 13, color: '#d3d4d8', padding: '4px 0' }}>
                  <strong>{u.name || '(unnamed)'}</strong>{u.head ? ' · ' + u.head + ' people' : ''}
                  {!empty((u.roles || []).filter(r => (r.title || '').trim())) && <span style={{ color: '#9a9ca2' }}>{' — ' + u.roles.filter(r => (r.title || '').trim()).map(r => r.title + (r.count ? ' (' + r.count + ')' : '')).join(', ')}</span>}
                  {u.delivers && <div style={{ fontSize: 12.5, color: '#9a9ca2' }}>{u.delivers}</div>}
                </div>
              ))}
            </div>
          )}
          <Field label="Top priorities this year" value={a.priorities} />
          <Field label="The one thing this program should do" value={a.oneThing} />
        </Group>

        {/* C — Data lives */}
        <Group title="C · Where Your Data Lives">
          {!empty(a.systems) && (
            <div style={{ marginBottom: 12 }}>
              <div style={{ fontSize: 11, color: '#6b6d74', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 5 }}>Systems</div>
              {a.systems.map((s, i) => (
                <div key={i} style={{ fontSize: 13, color: '#d3d4d8', padding: '4px 0' }}>
                  <strong>{s.name || '(unnamed)'}</strong>
                  {!empty(s.info) && <span style={{ color: '#9a9ca2' }}>{' — ' + s.info.join(', ')}</span>}
                  {!empty(s.units) && <div style={{ fontSize: 12, color: '#6b6d74' }}>Used by: {s.units.join(', ')}</div>}
                </div>
              ))}
            </div>
          )}
          <Field label="Lives in spreadsheets" value={a.hideSheets} />
          <Field label="Lives in email / chat" value={a.hideEmail} />
          <Field label="Lives only in someone's head" value={a.hideHead} />
        </Group>

        {/* D — Time goes */}
        <Group title="D · Where Time Goes">
          <Field label="Most repetitive task" value={a.repetitive} />
          <Field label="Where delays/errors cost most" value={a.costly} />
          <Field label="Estimated cost" value={a.costlyEst} />
          <Field label="Task to hand off tomorrow" value={a.handoff} />
          <Field label="Tried AI before?" value={a.aiExp} />
          <Field label="What happened" value={a.aiWhy} />
        </Group>
      </div>
    );
  }

  // Internal triage of client questions (admin only).
  function QuestionTriage({ clientId, questions, reload, flash }) {
    if (!questions || !questions.length) return <div style={{ fontSize: 13, color: '#6b6d74' }}>No questions submitted.</div>;
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {questions.map(q => <QuestionRow key={q.id} q={q} clientId={clientId} reload={reload} flash={flash} />)}
      </div>
    );
  }
  function QuestionRow({ q, clientId, reload, flash }) {
    const [note, setNote] = useState(q.adminNote || '');
    const addressed = q.adminStatus === 'addressed';
    const save = (status) => API.admin.setQuestionStatus(clientId, q.id, status, note)
      .then(() => { flash(status === 'addressed' ? 'Marked addressed.' : 'Saved.'); reload(); })
      .catch(e => flash(e.message));
    return (
      <div style={{ background: '#1c1c1a', border: '1px solid ' + (addressed ? 'rgba(95,210,154,0.25)' : 'rgba(157,180,255,0.25)'), borderRadius: 8, padding: '14px 16px' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6, flexWrap: 'wrap' }}>
          {q.section && <span style={{ fontSize: 10.5, textTransform: 'uppercase', letterSpacing: '0.05em', color: '#9db4ff', background: 'rgba(157,180,255,0.12)', borderRadius: 4, padding: '2px 7px' }}>{q.section}</span>}
          <Badge tone={addressed ? 'good' : 'blue'}>{addressed ? 'Addressed' : 'Open'}</Badge>
          {q.sent ? <span style={{ fontSize: 11, color: '#6b6d74' }}>sent by client</span> : <span style={{ fontSize: 11, color: '#6b6d74' }}>draft (not yet sent)</span>}
        </div>
        <div style={{ fontSize: 14, color: '#fafafa', marginBottom: 10 }}>{q.text}</div>
        <textarea value={note} onChange={e => setNote(e.target.value)} rows={2} placeholder="Internal note / answer (not shown to the client)…"
          style={{ background: '#111110', border: '1px solid rgba(255,255,255,0.1)', borderRadius: 6, padding: '9px 12px', fontFamily: "'Outfit',sans-serif", fontSize: 13, color: '#e8e9ec', width: '100%', boxSizing: 'border-box', resize: 'vertical' }} />
        <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
          <button onClick={() => save(q.adminStatus || 'open')} style={btnGhostSm}>Save Note</button>
          {addressed
            ? <button onClick={() => save('open')} style={btnGhostSm}>Reopen</button>
            : <button onClick={() => save('addressed')} style={iconBtn(Object.assign({}, btnGhostSm, { color: '#5fd29a', borderColor: 'rgba(95,210,154,0.4)' }))}><IconCheck size={13} />Mark Addressed</button>}
        </div>
      </div>
    );
  }

  // ── Home dashboard: the landing view when you open a client ──────────────────
  function AdminHome({ client, data, intake, setTab, onDeliver, onPreview, onPreviewIntake }) {
    const reportStatus = data && data.assessment ? data.assessment.status : 'none';
    const version = data && data.revision ? data.revision.version : null;
    const openQs = intake ? intake.questions.filter(q => q.adminStatus !== 'addressed') : [];
    const users = client.users || [];

    const cardStyle = { background: '#1c1c1a', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 12, padding: '20px 22px', display: 'flex', flexDirection: 'column' };
    const cardHead = (I, title) => (
      <div style={{ display: 'flex', alignItems: 'center', gap: 9, marginBottom: 14 }}>
        <span style={{ display: 'inline-flex', color: '#9db4ff' }}><I size={17} /></span>
        <span style={{ fontSize: 14.5, fontWeight: 600, color: '#fafafa' }}>{title}</span>
      </div>
    );
    const linkBtn = (label, I, onClick, accent) => (
      <button onClick={onClick} style={{ display: 'inline-flex', alignItems: 'center', gap: 7, background: accent ? '#2e5dff' : 'none', border: accent ? 'none' : '1px solid rgba(255,255,255,0.16)', color: accent ? '#fff' : '#d3d4d8', borderRadius: 7, padding: '8px 13px', fontSize: 12.5, fontWeight: 600, cursor: 'pointer', fontFamily: "'Outfit',sans-serif" }}>
        {I ? <I size={14} /> : null}{label}
      </button>
    );
    const stat = (n, label) => (
      <div style={{ flex: 1 }}>
        <div style={{ fontFamily: "'DM Serif Display',serif", fontSize: 26, color: '#fafafa', lineHeight: 1 }}>{n}</div>
        <div style={{ fontSize: 11.5, color: '#8b8d94', marginTop: 4 }}>{label}</div>
      </div>
    );

    return (
      <div style={{ maxWidth: 880, margin: '0 auto', padding: '32px 24px 80px' }}>
        <div style={{ marginBottom: 22 }}>
          <h1 style={{ fontFamily: "'DM Serif Display',serif", fontWeight: 400, fontSize: 26, margin: '0 0 5px' }}>Client Admin Home</h1>
          <p style={{ fontSize: 13.5, color: '#8b8d94', margin: 0 }}>Access, intake progress, and the report for {client.name}.</p>
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
          {/* Intake status */}
          <div style={cardStyle}>
            {cardHead(IconClipboard, 'Intake')}
            <div style={{ marginBottom: 14 }}>{intake ? <StatusPill status={intake.status} map={INTAKE_META} /> : <Badge tone="muted">Loading…</Badge>}</div>
            <div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>
              {stat(intake ? intake.uploads.length : 0, 'Uploads')}
              {stat(intake ? intake.questions.length : 0, 'Questions')}
              {stat(intake ? intake.snapshots.length : 0, 'Snapshots')}
            </div>
            <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 'auto' }}>
              {linkBtn('Review Intake', IconArrowRight, () => setTab('intake'))}
              {onPreviewIntake && linkBtn('Preview as Client', IconEye, onPreviewIntake)}
            </div>
          </div>

          {/* Open questions highlight */}
          <div style={Object.assign({}, cardStyle, openQs.length ? { borderColor: 'rgba(160,109,16,0.5)', background: '#221c12' } : {})}>
            {cardHead(openQs.length ? IconAlert : IconCheck, 'Questions for Linea')}
            {openQs.length ? (
              <React.Fragment>
                <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 10 }}>
                  <span style={{ fontFamily: "'DM Serif Display',serif", fontSize: 30, color: '#e0a83a', lineHeight: 1 }}>{openQs.length}</span>
                  <span style={{ fontSize: 13, color: '#caa86a' }}>open {openQs.length === 1 ? 'question' : 'questions'} to triage</span>
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginBottom: 14 }}>
                  {openQs.slice(0, 2).map(q => <div key={q.id} style={{ fontSize: 12.5, color: '#d3d4d8', borderLeft: '2px solid rgba(224,168,58,0.5)', paddingLeft: 9 }}>{q.text}</div>)}
                  {openQs.length > 2 && <div style={{ fontSize: 12, color: '#8b8d94' }}>+ {openQs.length - 2} more</div>}
                </div>
                <div style={{ marginTop: 'auto' }}>{linkBtn('Triage Questions', IconArrowRight, () => setTab('intake'), true)}</div>
              </React.Fragment>
            ) : (
              <div style={{ fontSize: 13, color: '#8b8d94', marginTop: 'auto' }}>No open questions. {intake && intake.questions.length ? 'All ' + intake.questions.length + ' addressed.' : 'None submitted yet.'}</div>
            )}
          </div>

          {/* Assessment + delivery */}
          <div style={cardStyle}>
            {cardHead(IconSparkles, 'Assessment')}
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16 }}>
              <Badge tone={reportStatus === 'delivered' ? 'good' : 'muted'}>{reportStatus}</Badge>
              {version != null && <span style={{ fontSize: 12.5, color: '#8b8d94' }}>Current: v{version}</span>}
            </div>
            <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 'auto' }}>
              {linkBtn('Open Editor', IconArrowRight, () => setTab('assessment'))}
              {reportStatus === 'delivered'
                ? linkBtn('Preview Report', IconEye, onPreview)
                : linkBtn('Deliver to Client', IconRocket, onDeliver, true)}
              {linkBtn('Delivery & Versions', IconLayers, () => setTab('delivery'))}
            </div>
          </div>

          {/* Access */}
          <div style={cardStyle}>
            {cardHead(IconUsers, 'Access')}
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 12 }}>
              <span style={{ fontFamily: "'DM Serif Display',serif", fontSize: 30, color: '#fafafa', lineHeight: 1 }}>{users.length}</span>
              <span style={{ fontSize: 13, color: '#8b8d94' }}>{users.length === 1 ? 'user can sign in' : 'users can sign in'}</span>
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 5, marginBottom: 14 }}>
              {users.slice(0, 3).map(u => <div key={u.id} style={{ display: 'flex', alignItems: 'center', gap: 7, fontSize: 12.5, color: '#d3d4d8' }}><IconMail size={13} />{u.email}</div>)}
              {!users.length && <div style={{ fontSize: 12.5, color: '#8b8d94' }}>No users yet — add one so the client can sign in.</div>}
              {users.length > 3 && <div style={{ fontSize: 12, color: '#8b8d94' }}>+ {users.length - 3} more</div>}
            </div>
            <div style={{ marginTop: 'auto' }}>{linkBtn('Manage Access', IconArrowRight, () => setTab('access'))}</div>
          </div>
        </div>
      </div>
    );
  }

  // ── Access: manage the people who can sign in as this client ─────────────────
  function AdminAccess({ client, flash }) {
    const [users, setUsers] = useState(null);
    const [add, setAdd] = useState({ email: '', password: '', name: '' });
    const load = () => API.admin.users(client.id).then(d => setUsers(d.users));
    useEffect(() => { load(); }, [client.id]);
    const setF = k => e => setAdd(s => Object.assign({}, s, { [k]: e.target.value }));
    const doAdd = () => {
      if (!add.email.trim() || !add.password.trim()) { flash('Email and password are required.'); return; }
      API.admin.addUser(client.id, add.email.trim(), add.password, add.name.trim() || null)
        .then(() => { setAdd({ email: '', password: '', name: '' }); flash('User added.'); load(); })
        .catch(e => flash(e.message));
    };
    return (
      <div style={{ maxWidth: 640, margin: '0 auto', padding: '32px 24px 80px' }}>
        <Section title="Who Can Sign In">
          {!users ? <Loading label="Loading users…" /> : !users.length
            ? <div style={{ fontSize: 13, color: '#6b6d74', padding: '8px 0' }}>No users yet. Add one below so the client can sign in.</div>
            : <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>{users.map(u => <UserRow key={u.id} client={client} u={u} reload={load} flash={flash} />)}</div>}
        </Section>

        <Section title="Add a User">
          <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
            <div>
              <label style={accessLabel}>Email</label>
              <input value={add.email} onChange={setF('email')} style={Object.assign({}, darkInput, { marginTop: 6 })} placeholder="person@company.com" />
            </div>
            <div>
              <label style={accessLabel}>Name (optional)</label>
              <input value={add.name} onChange={setF('name')} style={Object.assign({}, darkInput, { marginTop: 6 })} placeholder="Jordan Lee" />
            </div>
            <div>
              <label style={accessLabel}>Password</label>
              <input type="text" value={add.password} onChange={setF('password')} style={Object.assign({}, darkInput, { marginTop: 6 })} placeholder="Simple, memorable, shareable" />
            </div>
            <div><button onClick={doAdd} style={iconBtn(btnBlue)}><IconPlus size={15} />Add User</button></div>
          </div>
        </Section>
      </div>
    );
  }

  function UserRow({ client, u, reload, flash }) {
    const [open, setOpen] = useState(false);
    const [pw, setPw] = useState('');
    const resetPw = () => {
      if (!pw.trim()) { flash('Enter a new password.'); return; }
      API.admin.updateUser(client.id, u.id, { password: pw }).then(() => { setPw(''); setOpen(false); flash('Password updated.'); reload(); }).catch(e => flash(e.message));
    };
    const remove = () => {
      if (!window.confirm('Remove ' + u.email + '? They will no longer be able to sign in.')) return;
      API.admin.removeUser(client.id, u.id).then(() => { flash('User removed.'); reload(); }).catch(e => flash(e.message));
    };
    return (
      <div style={{ background: '#1c1c1a', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 8, padding: '13px 16px' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <span style={{ display: 'inline-flex', color: '#9db4ff' }}><IconMail size={15} /></span>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 13.5, color: '#fafafa', fontWeight: 600 }}>{u.email}</div>
            <div style={{ fontSize: 11.5, color: '#6b6d74', marginTop: 2 }}>{u.name ? u.name + ' · ' : ''}{u.lastLoginAt ? 'last in ' + u.lastLoginAt : 'never signed in'}</div>
          </div>
          <button onClick={() => setOpen(o => !o)} style={btnGhostSm} title="Reset password"><IconKey size={13} /></button>
          <button onClick={remove} style={Object.assign({}, btnGhostSm, { color: '#ff8a7a', borderColor: 'rgba(255,138,122,0.35)' })} title="Remove user"><IconTrash size={13} /></button>
        </div>
        {open && (
          <div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
            <input type="text" value={pw} onChange={e => setPw(e.target.value)} onKeyDown={e => e.key === 'Enter' && resetPw()} style={Object.assign({}, darkInput, { height: 38 })} placeholder="New password" autoFocus />
            <button onClick={resetPw} style={iconBtn(Object.assign({}, btnBlue, { height: 38, padding: '0 14px' }))}><IconCheck size={14} />Save</button>
          </div>
        )}
      </div>
    );
  }
  const accessLabel = { fontSize: 11, color: '#8b8d94', textTransform: 'uppercase', letterSpacing: '0.06em', fontWeight: 600 };

  function Section({ title, children }) {
    return (
      <div style={{ marginBottom: 26 }}>
        <div style={{ fontSize: 11, letterSpacing: '0.09em', textTransform: 'uppercase', color: '#5e6068', fontWeight: 600, marginBottom: 10 }}>{title}</div>
        {children}
      </div>
    );
  }
  const preStyle = { background: '#1c1c1a', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 8, padding: 16, fontSize: 12, color: '#c8cad0', overflow: 'auto', maxHeight: 360, whiteSpace: 'pre-wrap' };

  // ════════════════════════ AUTH GATE (root) ════════════════════════
  function AuthGate() {
    const [state, setState] = useState({ loading: true, principal: null });
    useEffect(() => {
      API.me().then(p => setState({ loading: false, principal: p && p.authenticated ? p : null }))
        .catch(() => setState({ loading: false, principal: null }));
    }, []);
    const signOut = () => API.logout().finally(() => setState({ loading: false, principal: null }));
    const refreshMe = () => API.me().then(p => setState({ loading: false, principal: p && p.authenticated ? p : null }));

    if (state.loading) return <Loading />;
    if (!state.principal) return <LoginScreen onLogin={(p) => setState({ loading: false, principal: p })} />;
    if (state.principal.user.mustReset) return <ForcePasswordReset principal={state.principal} onDone={refreshMe} onSignOut={signOut} />;
    if (state.principal.user.role === 'admin') return <AdminConsole principal={state.principal} onSignOut={signOut} />;
    return <ClientRoot principal={state.principal} onSignOut={signOut} />;
  }

  window.ReportApp = ReportApp;
  window.initApp = function () {
    const rootEl = document.getElementById('root');
    rootEl.style.display = '';
    ReactDOM.createRoot(rootEl).render(<AuthGate />);
  };

  if (!window.__skipAutoRender) window.initApp();
})();
