// screens.jsx — Login, Upload (with idle/dragover/uploading/success), History, Admin.

// ── Login ───────────────────────────────────────────────────────────────────
function Login({ onAuthed }) {
  const [un, setUn] = React.useState('');
  const [pw, setPw] = React.useState('');
  const [err, setErr] = React.useState('');
  const unRef = React.useRef(null);
  React.useEffect(() => { unRef.current?.focus(); }, []);

  const submit = async (e) => {
    e.preventDefault();
    const r = await fetch('/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username: un.trim().toLowerCase(), passphrase: pw }),
    });
    if (r.ok) {
      onAuthed(await r.json());
    } else {
      const body = await r.json().catch(() => ({}));
      setErr(body.error || "Those details don't match. Have another go.");
    }
  };

  return (
    <div className="ef-login">
      <div className="ef-login-screen" aria-hidden="true">
        <div className="ef-login-screen-glow" />
      </div>
      <form className="ef-login-card" onSubmit={submit}>
        <Wordmark subtitle="my View" />
        <div className="ef-login-body">
          <h1 className="ef-login-title">Welcome.</h1>
          <p className="ef-login-lede">
            To log into Eidos&nbsp;Factum, please enter the passphrase you've been given.
          </p>
          <label className="ef-field">
            <span className="ef-field-lbl">Username</span>
            <input ref={unRef} type="text" value={un}
                   onChange={(e) => { setUn(e.target.value); setErr(''); }}
                   placeholder="e.g. drm"
                   autoComplete="username" spellCheck="false"
                   autoCapitalize="none" />
          </label>
          <label className="ef-field">
            <span className="ef-field-lbl">Passphrase</span>
            <input type="password" value={pw}
                   onChange={(e) => { setPw(e.target.value); setErr(''); }}
                   placeholder="• • • • • •"
                   autoComplete="current-password" spellCheck="false" />
          </label>
          {err && <div className="ef-field-err">{err}</div>}
          <button type="submit" className="ef-btn ef-btn--primary">
            Enter <Icon.arrowRight />
          </button>
        </div>
        <div className="ef-login-foot">
          <span>collabs.eidosfactum.org</span>
          <span><Icon.shield size={11} /> TLS via Caddy · Tailscale</span>
        </div>
      </form>
    </div>
  );
}

// ── Drop zone (the projection screen) ───────────────────────────────────────
// One element drives idle / dragover / uploading / success states via a
// data-state attribute. The visual difference is the screen's brightness +
// the inner content; surface chrome stays put.
function DropZone({ state, file, progress, scanPhase, readyAt, onPickFile,
                    onDragOver, onDragLeave, onDrop, onReset }) {
  const fileInputRef = React.useRef(null);
  const pickHandler = () => fileInputRef.current?.click();

  return (
    <section className="ef-drop-wrap" data-state={state}
             onDragOver={onDragOver}
             onDragLeave={onDragLeave}
             onDrop={onDrop}>
      <Eyebrow>File upload · ready</Eyebrow>
      <h2 className="ef-h-main">
        {state === 'success'   ? 'Received.' :
         state === 'uploading' ? 'Receiving your file' :
         state === 'dragover'  ? 'Release to receive' :
         state === 'rejected'  ? 'File stopped.' :
         state === 'error'     ? "That didn't go through." :
         state === 'quota'     ? 'Upload limit reached.' :
         'Upload your file here.'}
      </h2>
      <p className="ef-lede">
        {state === 'success' ? (
          <>It's in the library now. {readyAt && <>It'll be playable in about
            <strong> five minutes</strong> — by <strong>{readyAt}</strong>.</>}</>
        ) : state === 'uploading' ? (
          <>Sit tight. We're moving it across, scanning it, and tucking it into
            the library. You don't need to wait on this screen.</>
        ) : state === 'dragover' ? (
          <>Let it go. We'll do the rest.</>
        ) : state === 'rejected' ? (
          <>Something on that file tripped the scanner, so we stopped it from being
            published. Hunter's been notified — he'll take a look.
            You haven't done anything wrong.</>
        ) : state === 'error' ? (
          <>Connection dropped before we finished. Give it another go when you're ready.</>
        ) : state === 'quota' ? (
          <>You've used your share of the library. Ask Hunter and he'll clear the counter
            — your files are all still safe.</>
        ) : (
          <>Drag a file from your USB stick or desktop onto the screen below.
            One at a time is plenty.</>
        )}
      </p>

      <div className="ef-screen"
           data-state={state === 'rejected' || state === 'error' || state === 'quota' ? 'success' : state}>
        <div className="ef-screen-frame" aria-hidden="true">
          <span className="ef-corner ef-corner--tl" />
          <span className="ef-corner ef-corner--tr" />
          <span className="ef-corner ef-corner--bl" />
          <span className="ef-corner ef-corner--br" />
        </div>
        <div className="ef-screen-light" aria-hidden="true" />
        <div className="ef-screen-grain" aria-hidden="true" />

        <div className="ef-screen-inner">
          {(state === 'idle' || state === 'dragover') && (
            <IdleContent dragover={state === 'dragover'} onPick={pickHandler} />
          )}
          {state === 'uploading' && (
            <UploadingContent file={file} progress={progress} scanPhase={scanPhase} />
          )}
          {state === 'success' && (
            <SuccessContent file={file} readyAt={readyAt} onReset={onReset} />
          )}
          {state === 'rejected' && (
            <FailureContent heading="Your file has been stopped."
                            buttonLabel="Try something else" onReset={onReset} />
          )}
          {state === 'error' && (
            <FailureContent heading="That didn't go through."
                            buttonLabel="Try again" onReset={onReset} />
          )}
          {state === 'quota' && (
            <FailureContent heading="Upload limit reached."
                            buttonLabel="Got it" onReset={onReset} />
          )}
        </div>
      </div>

      <input ref={fileInputRef} type="file" accept="video/*,.mkv,.mov,.mp4,.avi"
             style={{ display: 'none' }}
             onChange={(e) => { if (e.target.files?.[0]) onPickFile(e.target.files[0]); }} />

      <div className="ef-meta-row">
        <span><Icon.shield size={13} /> Every file is scanned before it's received</span>
        <span className="ef-meta-dot">·</span>
        <span><Icon.film size={13} /> .mp4 · .mkv · .mov · .avi up to 40&nbsp;GB</span>
      </div>
    </section>
  );
}

function IdleContent({ dragover, onPick }) {
  return (
    <div className="ef-idle">
      <div className="ef-idle-glyph" aria-hidden="true">
        <Icon.upload size={28} />
      </div>
      <div className="ef-idle-head">
        {dragover ? 'Drop to receive' : 'Drop your file onto this screen'}
      </div>
      <div className="ef-idle-sub">
        {dragover ? '\u00a0' : 'or'}
      </div>
      {!dragover && (
        <button className="ef-btn ef-btn--ghost" onClick={onPick}>
          Choose a file from this device
        </button>
      )}
    </div>
  );
}

function UploadingContent({ file, progress, scanPhase }) {
  const mb = (file?.size || 0) / (1024 * 1024);
  const sentMb = (mb * progress) / 100;
  return (
    <div className="ef-up">
      <div className="ef-up-filerow">
        <span className="ef-up-fileicon" aria-hidden="true"><Icon.film size={18} /></span>
        <div className="ef-up-fileinfo">
          <div className="ef-up-filename">{file?.name || 'film.mkv'}</div>
          <div className="ef-up-filemeta">
            {mb.toFixed(0)}&nbsp;MB ·{' '}
            {scanPhase === 'scanning'
              ? <>scanning with ClamAV…</>
              : scanPhase === 'finalizing'
              ? <>moving into the library…</>
              : <>{Math.min(100, Math.round(progress))}% received · {sentMb.toFixed(0)}&nbsp;MB of {mb.toFixed(0)}&nbsp;MB</>}
          </div>
        </div>
      </div>
      <div className="ef-progress">
        <div className="ef-progress-fill" style={{ width: progress + '%' }} />
        <div className="ef-progress-sheen" />
      </div>
      <div className="ef-phases">
        <Phase label="Receiving" done={progress >= 100} active={progress < 100} />
        <PhaseDash />
        <Phase label="Scanning" done={scanPhase === 'finalizing'} active={scanPhase === 'scanning'} />
        <PhaseDash />
        <Phase label="Filing" done={false} active={scanPhase === 'finalizing'} />
      </div>
    </div>
  );
}

function Phase({ label, done, active }) {
  return (
    <span className={'ef-phase' + (done ? ' is-done' : '') + (active ? ' is-active' : '')}>
      <span className="ef-phase-bullet" aria-hidden="true">
        {done ? <Icon.check size={10} /> : <Icon.dot size={6} />}
      </span>
      {label}
    </span>
  );
}
const PhaseDash = () => <span className="ef-phase-dash" aria-hidden="true" />;

function SuccessContent({ file, readyAt, onReset }) {
  return (
    <div className="ef-success">
      <div className="ef-success-glyph" aria-hidden="true">
        <Icon.check size={26} />
      </div>
      <div className="ef-success-head">Your file has been received.</div>
      <div className="ef-success-file">
        <Icon.film size={14} /> <span>{file?.name || 'film.mkv'}</span>
      </div>
      <div className="ef-success-ready">
        <Icon.clock size={13} />
        <span>Ready to play at <strong>{readyAt}</strong></span>
      </div>
      <button className="ef-btn ef-btn--ghost ef-btn--sm" onClick={onReset}>
        Send another
      </button>
    </div>
  );
}

function FailureContent({ heading, buttonLabel, onReset }) {
  return (
    <div className="ef-success">
      <div className="ef-success-head">{heading}</div>
      <button className="ef-btn ef-btn--ghost ef-btn--sm" onClick={onReset}>
        {buttonLabel}
      </button>
    </div>
  );
}

// ── Recent uploads list ─────────────────────────────────────────────────────
function History({ items, user }) {
  const isAdmin = !!user?.isAdmin;
  // Admin sees the full library (with uploader visible); users see only what
  // they put in.
  const visible = isAdmin
    ? items
    : items.filter((it) => it.uploaderUsername === user?.username);

  if (visible.length === 0) {
    return (
      <section className="ef-history">
        <div className="ef-history-head">
          <Eyebrow>{isAdmin ? 'Library · everyone' : 'Recently received'}</Eyebrow>
        </div>
        <div className="ef-history-empty">
          <div className="ef-history-empty-glyph" aria-hidden="true">
            <Icon.film size={22} />
          </div>
          <div className="ef-history-empty-head">Nothing here yet.</div>
          <p className="ef-history-empty-lede">
            {isAdmin ? (
              <>No one has sent anything in yet. Uploads will land here as they're received.</>
            ) : user?.name ? (
              <>Files you upload, {user.name}, will appear here once they've been
                scanned and tucked into the library.</>
            ) : (
              <>Files you upload will appear here once they've been received.</>
            )}
          </p>
        </div>
      </section>
    );
  }
  return (
    <section className="ef-history">
      <div className="ef-history-head">
        <div>
          <Eyebrow>{isAdmin ? 'Library · everyone' : 'Recently received'}</Eyebrow>
          {isAdmin && (
            <div className="ef-history-sub">
              Showing every upload, tagged with who routed it.
            </div>
          )}
        </div>
        <span className="ef-history-count">
          {visible.length} {visible.length === 1 ? 'file' : 'files'}
        </span>
      </div>
      <ol className="ef-history-list">
        {visible.map((it) => (
          <li key={it.id} className="ef-history-row" data-admin={isAdmin ? '1' : '0'}>
            <span className="ef-history-strip" aria-hidden="true">
              {Array.from({ length: 6 }, (_, i) => <i key={i} />)}
            </span>
            <div className="ef-history-main">
              <div className="ef-history-title">{it.title}</div>
              <div className="ef-history-meta">
                {isAdmin && it.uploader && (
                  <>
                    <span className="ef-history-uploader">
                      <span className="ef-history-uploader-dot" aria-hidden="true" />
                      {it.uploader}
                    </span>
                    <span className="ef-history-dot">·</span>
                  </>
                )}
                <span>{it.runtime}</span>
                <span className="ef-history-dot">·</span>
                <span>{it.size}</span>
                <span className="ef-history-dot">·</span>
                <span>{it.format}</span>
              </div>
            </div>
            <div className="ef-history-when">{it.when}</div>
            <Pill tone={it.statusTone}>{it.status}</Pill>
          </li>
        ))}
      </ol>
    </section>
  );
}

// ── Admin ───────────────────────────────────────────────────────────────────

// Sample passphrase words — short, warm, low-friction to read aloud over the
// phone. Used by the enrolment generator.
const __PHRASE_WORDS = [
  'amber', 'quiet', 'paper', 'lamp', 'river', 'reel', 'warm', 'soft',
  'slow', 'grain', 'linen', 'dusk', 'copper', 'ember', 'still', 'open',
  'field', 'hum', 'sand', 'smoke', 'index', 'plate', 'fold', 'margin',
  'archive', 'page', 'hour', 'note',
];
function __genPhrase() {
  const pick = () => __PHRASE_WORDS[Math.floor(Math.random() * __PHRASE_WORDS.length)];
  let words = [pick(), pick(), pick()];
  // de-dupe — small word list, easy to hit collisions
  while (new Set(words).size < 3) words = [pick(), pick(), pick()];
  return words.join('-');
}

const __SAMPLE_PEOPLE = [
  { id: 'p-hunter', username: 'hunter', name: 'Hunter',          role: 'admin',    passphrase: 'admin',
    enrolled: '12 Mar 2026', lastSeen: 'just now',         status: 'Active' },
  { id: 'p-drm',    username: 'drm',    name: 'Dr. M.',          role: 'uploader', passphrase: 'cinema',
    enrolled: '14 Mar 2026', lastSeen: 'yesterday · 21:14', status: 'Active' },
  { id: 'p-annika', username: 'annika', name: 'Annika R.',       role: 'uploader', passphrase: 'paper-lamp-river',
    enrolled: '2 Apr 2026',  lastSeen: '12 May · 11:08',    status: 'Active' },
  { id: 'p-fellow', username: 'fellow', name: 'Visiting fellow', role: 'uploader', passphrase: 'quiet-amber-hold',
    enrolled: '20 May 2026', lastSeen: '—',                 status: 'Pending first sign-in' },
];

// Suggest a username from a freeform name. First word, lowercased, alphanum
// only; collapses to "fellow" as a fallback so the field never goes empty.
function __suggestUsername(name) {
  const cleaned = (name || '').trim().toLowerCase()
    .split(/\s+/)[0].replace(/[^a-z0-9]/g, '');
  return cleaned || 'fellow';
}

function Checkbox({ checked, indeterminate, onChange, label }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (ref.current) ref.current.indeterminate = !!indeterminate;
  }, [indeterminate]);
  return (
    <label className="ef-cb" onClick={(e) => e.stopPropagation()}
           aria-label={label || 'Select'}>
      <input ref={ref} type="checkbox" checked={checked} onChange={onChange} />
      <span className="ef-cb-box" aria-hidden="true">
        {checked && !indeterminate && <Icon.check size={10} />}
        {indeterminate && <span className="ef-cb-dash" />}
      </span>
    </label>
  );
}

function Admin({ user }) {
  const [section, setSection] = React.useState('activity');
  return (
    <section className="ef-admin">
      <div className="ef-admin-head">
        <div>
          <Eyebrow>Admin console · {user?.name || 'Hunter'}</Eyebrow>
          <h2 className="ef-h-main ef-h-main--sm">The room behind the screen.</h2>
        </div>
        <nav className="ef-admin-subnav" aria-label="Admin sections">
          <button className={'ef-admin-subnav-link' + (section === 'activity' ? ' is-on' : '')}
                  onClick={() => setSection('activity')}>Activity</button>
          <button className={'ef-admin-subnav-link' + (section === 'people' ? ' is-on' : '')}
                  onClick={() => setSection('people')}>People</button>
        </nav>
      </div>

      {section === 'activity' ? <AdminActivity /> : <AdminPeople currentUser={user} />}
    </section>
  );
}

function _fmtWhen(ts) {
  if (!ts) return '—';
  const dt = new Date(ts.replace(' ', 'T'));
  const now = new Date();
  const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
  const yesterday = new Date(+today - 864e5);
  const day = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate());
  const hh = String(dt.getHours()).padStart(2, '0');
  const mm = String(dt.getMinutes()).padStart(2, '0');
  const t = `${hh}:${mm}`;
  if (+day >= +today) return `Today · ${t}`;
  if (+day >= +yesterday) return `Yesterday · ${t}`;
  const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  return `${dt.getDate()} ${months[dt.getMonth()]} · ${t}`;
}

function adaptPerson(row) {
  return {
    username: row.username,
    name: row.name,
    role: row.role,
    status: row.status === 'active' ? 'Active' : 'Inactive',
    mediaFolder: row.media_folder || '',
    quotaBytes: row.quota_bytes ?? 10737418240,
    quotaUsed: row.quota_used ?? 0,
    lastSeen: _fmtWhen(row.last_seen_at),
  };
}

function InlineEdit({ value, onSave, type = 'text', placeholder = '' }) {
  const [editing, setEditing] = React.useState(false);
  const [draft, setDraft] = React.useState(String(value ?? ''));
  const ref = React.useRef(null);
  React.useEffect(() => { if (editing) ref.current?.select(); }, [editing]);
  const commit = () => {
    setEditing(false);
    const v = draft.trim();
    if (v !== String(value ?? '')) onSave(v);
  };
  if (editing) {
    return (
      <input ref={ref} className="ef-inline-edit" type={type} value={draft}
             onChange={e => setDraft(e.target.value)}
             onBlur={commit}
             onKeyDown={e => {
               if (e.key === 'Enter') commit();
               if (e.key === 'Escape') { setDraft(String(value ?? '')); setEditing(false); }
             }} />
    );
  }
  return (
    <button className="ef-cell-btn" onClick={() => { setDraft(String(value ?? '')); setEditing(true); }}>
      {value || <span className="ef-muted">{placeholder}</span>}
    </button>
  );
}

function adaptScanLog(row) {
  const map = {
    in_library: { label: 'Clean',    tone: 'ok' },
    infected:   { label: 'Infected', tone: 'danger' },
    scan_error: { label: 'Error',    tone: 'warn' },
    queued:     { label: 'Pending',  tone: 'neutral' },
    scanning:   { label: 'Scanning', tone: 'neutral' },
  };
  const { label, tone } = map[row.status] || { label: row.status, tone: 'neutral' };
  const dt = new Date(row.queued_at.replace(' ', 'T'));
  const hh = String(dt.getHours()).padStart(2, '0');
  const mm = String(dt.getMinutes()).padStart(2, '0');
  const gb = row.size_bytes / (1024 ** 3);
  const size = gb >= 1 ? gb.toFixed(1) + ' GB'
                       : Math.round(row.size_bytes / (1024 ** 2)) + ' MB';
  return { id: row.id, time: `${hh}:${mm}`, file: row.filename,
           size, verdict: label, tone, from: row.uploader };
}

function AdminActivity() {
  const [scanLog,     setScanLog]     = React.useState([]);
  const [selected,    setSelected]    = React.useState(() => new Set());
  const [lastDeleted, setLastDeleted] = React.useState(null);

  React.useEffect(() => {
    fetch('/api/scan-log')
      .then(r => r.ok ? r.json() : [])
      .then(rows => setScanLog(rows.map(adaptScanLog)));
  }, []);

  const allSelected  = selected.size > 0 && selected.size === scanLog.length;
  const someSelected = selected.size > 0 && !allSelected;

  const toggle = (id) => setSelected((prev) => {
    const next = new Set(prev);
    if (next.has(id)) next.delete(id); else next.add(id);
    return next;
  });
  const toggleAll      = () => allSelected
    ? setSelected(new Set())
    : setSelected(new Set(scanLog.map(r => r.id)));
  const clearSelection = () => setSelected(new Set());

  const deleteSelected = async () => {
    const ids    = [...selected];
    const kept   = scanLog.filter(r => !selected.has(r.id));
    const removed = scanLog.filter(r => selected.has(r.id));
    setScanLog(kept);
    setLastDeleted(removed);
    setSelected(new Set());
    await fetch('/api/scan-log', {
      method: 'DELETE',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ids }),
    });
  };

  const undoDelete = () => {
    if (!lastDeleted) return;
    setScanLog([...lastDeleted, ...scanLog]);
    setLastDeleted(null);
  };

  return (
    <>
      <div className="ef-admin-status">
        <StatusBlock label="ClamAV" value="Running" tone="ok"
                     sub="signatures updated 14&nbsp;min ago" />
        <StatusBlock label="Quarantine" value="0 holding" tone="neutral"
                     sub="nothing waiting on scan" />
        <StatusBlock label="Library" value="412 files" tone="neutral"
                     sub="3.2&nbsp;TB of 8.0&nbsp;TB" />
        <StatusBlock label="Disk" value="60% used" tone="warn"
                     sub="Jellyfin volume" />
      </div>

      <div className="ef-admin-cols">
        <div className="ef-admin-col">
          <div className="ef-section-bar">
            <Eyebrow>Scan log · last 24 hours</Eyebrow>
            <div className="ef-selection-actions">
              {lastDeleted && selected.size === 0 && (
                <button className="ef-row-action" onClick={undoDelete}>
                  Undo delete ({lastDeleted.length})
                </button>
              )}
              {selected.size > 0 && (
                <>
                  <span className="ef-selection-count">{selected.size} selected</span>
                  <button className="ef-btn ef-btn--ghost ef-btn--sm"
                          onClick={clearSelection}>Clear</button>
                  <button className="ef-btn ef-btn--danger ef-btn--sm"
                          onClick={deleteSelected}>Delete {selected.size}</button>
                </>
              )}
            </div>
          </div>
          <table className="ef-admin-table">
            <thead>
              <tr>
                <th className="ef-admin-cb-col">
                  <Checkbox checked={allSelected} indeterminate={someSelected}
                            onChange={toggleAll} label="Select all" />
                </th>
                <th>Time</th><th>File</th><th>Size</th><th>Verdict</th><th>From</th>
              </tr>
            </thead>
            <tbody>
              {scanLog.map((row) => {
                const isSel = selected.has(row.id);
                return (
                  <tr key={row.id} data-selected={isSel ? '1' : '0'}
                      onClick={() => toggle(row.id)}>
                    <td className="ef-admin-cb-col">
                      <Checkbox checked={isSel} onChange={() => toggle(row.id)}
                                label={'Select ' + row.file} />
                    </td>
                    <td className="ef-mono">{row.time}</td>
                    <td>{row.file}</td>
                    <td className="ef-mono">{row.size}</td>
                    <td><Pill tone={row.tone}>{row.verdict}</Pill></td>
                    <td>{row.from}</td>
                  </tr>
                );
              })}
              {scanLog.length === 0 && (
                <tr><td colSpan="6" className="ef-admin-empty-row">
                  The log is clear.
                </td></tr>
              )}
            </tbody>
          </table>
        </div>

        <div className="ef-admin-col ef-admin-col--side">
          <Eyebrow>Approvals queue</Eyebrow>
          <div className="ef-admin-empty">
            Nothing's asking for your eye right now.
          </div>

          <div className="ef-admin-spacer" />

          <Eyebrow>System</Eyebrow>
          <dl className="ef-admin-meta">
            <div><dt>Host</dt><dd>Waratah <span className="ef-mono">(Pi 5, Linux)</span></dd></div>
            <div><dt>Reverse proxy</dt><dd>Caddy · TLS auto</dd></div>
            <div><dt>Hosted at</dt><dd className="ef-mono">collabs.eidosfactum.org</dd></div>
            <div><dt>Quarantine path</dt><dd className="ef-mono">/srv/collabs/_quarantine</dd></div>
            <div><dt>Library path</dt><dd className="ef-mono">/srv/jellyfin/files</dd></div>
            <div><dt>Notify on infected</dt><dd>hunter@eidosfactum.org</dd></div>
          </dl>
        </div>
      </div>
    </>
  );
}

function AdminPeople({ currentUser }) {
  const [people, setPeople] = React.useState([]);
  const [freshPass, setFreshPass] = React.useState({});
  const [enrolling, setEnrolling] = React.useState(false);
  const [revealed, setRevealed] = React.useState(() => new Set());
  const [form, setForm] = React.useState({
    name: '', username: '', usernameTouched: false, role: 'uploader',
  });
  const [draftPass, setDraftPass] = React.useState(__genPhrase());
  const [copied, setCopied] = React.useState(null);

  const loadPeople = () =>
    fetch('/api/people')
      .then(r => r.ok ? r.json() : [])
      .then(rows => setPeople(rows.map(adaptPerson)));

  React.useEffect(() => { loadPeople(); }, []);

  const setName = (name) => setForm(f => ({
    ...f, name,
    username: f.usernameTouched ? f.username : __suggestUsername(name),
  }));
  const setUsername = (username) => setForm(f => ({
    ...f, username: username.toLowerCase().replace(/[^a-z0-9_.-]/g, ''),
    usernameTouched: true,
  }));

  const toggleReveal = (username) => setRevealed(prev => {
    const next = new Set(prev);
    if (next.has(username)) next.delete(username); else next.add(username);
    return next;
  });

  const copyPass = async (username, pass) => {
    try { await navigator.clipboard.writeText(pass); } catch {}
    setCopied(username);
    setTimeout(() => setCopied(c => c === username ? null : c), 1400);
  };

  const patchPerson = (username, updates) =>
    fetch(`/api/people/${username}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(updates),
    });

  const saveFolder = (username, value) => {
    setPeople(prev => prev.map(p =>
      p.username === username ? { ...p, mediaFolder: value } : p));
    patchPerson(username, { media_folder: value });
  };

  const saveQuota = (username, gbString) => {
    const gb = parseFloat(gbString);
    if (isNaN(gb) || gb < 0) return;
    const bytes = Math.round(gb * 1024 ** 3);
    setPeople(prev => prev.map(p =>
      p.username === username ? { ...p, quotaBytes: bytes } : p));
    patchPerson(username, { quota_bytes: bytes });
  };

  const resetQuota = async (username) => {
    await fetch(`/api/people/${username}/reset-quota`, { method: 'POST' });
    setPeople(prev => prev.map(p =>
      p.username === username ? { ...p, quotaUsed: 0 } : p));
  };

  const rerollPass = async (username) => {
    const r = await fetch(`/api/people/${username}/reroll`, { method: 'POST' });
    if (r.ok) {
      const { passphrase } = await r.json();
      setFreshPass(fp => ({ ...fp, [username]: passphrase }));
      setRevealed(prev => new Set([...prev, username]));
    }
  };

  const revoke = (username) => {
    setPeople(prev => prev.map(p =>
      p.username === username ? { ...p, status: 'Inactive' } : p));
    patchPerson(username, { status: 'inactive' });
  };

  const reinstate = (username) => {
    setPeople(prev => prev.map(p =>
      p.username === username ? { ...p, status: 'Active' } : p));
    patchPerson(username, { status: 'active' });
  };

  const startEnrol = () => {
    setForm({ name: '', username: '', usernameTouched: false, role: 'uploader' });
    setDraftPass(__genPhrase());
    setEnrolling(true);
  };

  const submitEnrol = async (e) => {
    e?.preventDefault();
    if (!form.name.trim() || !form.username.trim()) return;
    const r = await fetch('/api/people', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        name: form.name.trim(), username: form.username.trim(),
        role: form.role, passphrase: draftPass,
      }),
    });
    if (!r.ok) return;
    const { username, passphrase } = await r.json();
    setFreshPass(fp => ({ ...fp, [username]: passphrase }));
    setRevealed(prev => new Set([...prev, username]));
    await loadPeople();
    setEnrolling(false);
  };

  return (
    <div className="ef-admin-people">
      <div className="ef-section-bar">
        <Eyebrow>People · enrolled passphrases</Eyebrow>
        <div className="ef-people-meta">
          <span className="ef-selection-count">{people.length} enrolled</span>
          {!enrolling && (
            <button className="ef-btn ef-btn--primary ef-btn--sm" onClick={startEnrol}>
              + Enrol someone
            </button>
          )}
        </div>
      </div>

      {enrolling && (
        <form className="ef-enrol-form" onSubmit={submitEnrol}>
          <div className="ef-enrol-head">
            <Eyebrow>New enrolment</Eyebrow>
            <p>Give them a name, choose a role, and we'll mint a passphrase you can hand them.</p>
          </div>
          <div className="ef-enrol-grid">
            <label className="ef-field">
              <span className="ef-field-lbl">Name</span>
              <input type="text" value={form.name} autoFocus
                     onChange={e => setName(e.target.value)}
                     placeholder="e.g. Annika R." />
            </label>
            <label className="ef-field">
              <span className="ef-field-lbl">
                Username <span className="ef-field-opt">(for login, lowercase)</span>
              </span>
              <input type="text" value={form.username}
                     onChange={e => setUsername(e.target.value)}
                     placeholder="auto-suggested from name"
                     autoCapitalize="none" spellCheck="false" />
            </label>
            <div className="ef-field">
              <span className="ef-field-lbl">Role</span>
              <div className="ef-role-segs" role="radiogroup">
                <button type="button" role="radio" aria-checked={form.role === 'uploader'}
                        className={form.role === 'uploader' ? 'is-on' : ''}
                        onClick={() => setForm({ ...form, role: 'uploader' })}>Uploader</button>
                <button type="button" role="radio" aria-checked={form.role === 'admin'}
                        className={form.role === 'admin' ? 'is-on' : ''}
                        onClick={() => setForm({ ...form, role: 'admin' })}>Admin</button>
              </div>
            </div>
            <div className="ef-field ef-field--wide">
              <span className="ef-field-lbl">Passphrase to hand over</span>
              <div className="ef-passphrase-row">
                <code className="ef-passphrase-code">{draftPass}</code>
                <button type="button" className="ef-link-btn"
                        onClick={() => setDraftPass(__genPhrase())}>Re-roll</button>
              </div>
            </div>
          </div>
          <div className="ef-enrol-actions">
            <button type="button" className="ef-btn ef-btn--ghost ef-btn--sm"
                    onClick={() => setEnrolling(false)}>Cancel</button>
            <button type="submit" className="ef-btn ef-btn--primary ef-btn--sm"
                    disabled={!form.name.trim() || !form.username.trim()}>
              Enrol & generate
            </button>
          </div>
        </form>
      )}

      <table className="ef-admin-table ef-admin-table--people">
        <thead>
          <tr>
            <th>Name</th>
            <th>Username</th>
            <th>Role</th>
            <th>Passphrase</th>
            <th>Media folder</th>
            <th>Storage</th>
            <th>Last seen</th>
            <th>Status</th>
            <th aria-label="Actions"></th>
          </tr>
        </thead>
        <tbody>
          {people.map(p => {
            const isRev = revealed.has(p.username);
            const pass = freshPass[p.username] || null;
            const isMe = p.username === currentUser?.username;
            const usedGB = (p.quotaUsed / (1024 ** 3)).toFixed(1);
            const limitGB = (p.quotaBytes / (1024 ** 3)).toFixed(0);
            const tripped = p.quotaUsed >= p.quotaBytes;
            return (
              <tr key={p.username}>
                <td><span className="ef-person-name">{p.name}</span></td>
                <td><code className="ef-username">{p.username}</code></td>
                <td>
                  <Pill tone={p.role === 'admin' ? 'accent' : 'neutral'}>{p.role}</Pill>
                </td>
                <td>
                  {pass ? (
                    <span className="ef-passphrase">
                      <code className="ef-passphrase-code">
                        {isRev ? pass : '•••• •••• ••••'}
                      </code>
                      <button className="ef-link-btn" onClick={() => toggleReveal(p.username)}>
                        {isRev ? 'Hide' : 'Reveal'}
                      </button>
                      {isRev && (
                        <button className="ef-link-btn" onClick={() => copyPass(p.username, pass)}>
                          {copied === p.username ? 'Copied' : 'Copy'}
                        </button>
                      )}
                    </span>
                  ) : (
                    <span className="ef-passphrase">
                      <code className="ef-passphrase-code ef-muted">••••••</code>
                      <button className="ef-link-btn" onClick={() => rerollPass(p.username)}>
                        Re-roll
                      </button>
                    </span>
                  )}
                </td>
                <td>
                  <InlineEdit
                    value={p.mediaFolder}
                    placeholder={p.username}
                    onSave={v => saveFolder(p.username, v)}
                  />
                </td>
                <td className="ef-quota-cell">
                  <span className={tripped ? 'ef-quota-tripped' : ''}>
                    {usedGB}&thinsp;GB
                  </span>
                  {' / '}
                  <InlineEdit
                    value={limitGB}
                    type="number"
                    placeholder="10"
                    onSave={v => saveQuota(p.username, v)}
                  />
                  {' GB'}
                  {p.quotaUsed > 0 && (
                    <button className="ef-link-btn ef-link-btn--warn"
                            onClick={() => resetQuota(p.username)}>Reset</button>
                  )}
                </td>
                <td className="ef-mono">{p.lastSeen}</td>
                <td>
                  <Pill tone={p.status === 'Active' ? 'ok' : 'warn'}>{p.status}</Pill>
                </td>
                <td className="ef-row-actions">
                  {isMe ? (
                    <span className="ef-row-action ef-row-action--muted">You</span>
                  ) : p.status === 'Active' ? (
                    <button className="ef-row-action ef-row-action--danger"
                            onClick={() => revoke(p.username)}>Revoke</button>
                  ) : (
                    <button className="ef-row-action"
                            onClick={() => reinstate(p.username)}>Reinstate</button>
                  )}
                </td>
              </tr>
            );
          })}
          {people.length === 0 && (
            <tr>
              <td colSpan="9" className="ef-admin-empty-row">No one enrolled yet.</td>
            </tr>
          )}
        </tbody>
      </table>
    </div>
  );
}

function StatusBlock({ label, value, sub, tone }) {
  return (
    <div className={'ef-stat ef-stat--' + tone}>
      <div className="ef-stat-label">{label}</div>
      <div className="ef-stat-value">{value}</div>
      <div className="ef-stat-sub">{sub}</div>
    </div>
  );
}

Object.assign(window, { Login, DropZone, History, Admin });
