/* ============ pages-member.jsx — auth, account, confirm, librarian ============ */

const { useState: useStateM, useEffect: useEffectM, useMemo: useMemoM, useRef: useRefM } = React;

/* ---------------------------------------------------------------- LOGIN
   In this app, Clerk handles all sign-in / sign-up. The page renders the
   reference design's left-side welcome panel and mounts Clerk's widget on
   the right. */
window.LoginPage = function LoginPage({ go, route }){
  const mountRef = useRefM(null);

  useEffectM(() => {
    if (!mountRef.current) return;
    const el = mountRef.current;
    // small retry loop in case Clerk's instance isn't ready yet
    let cancelled = false;
    (async () => {
      for (let i = 0; i < 40 && !cancelled; i++) {
        if (window.Clerk && window.Clerk.mountSignIn) {
          try { window.Clerk.mountSignIn(el, { afterSignInUrl: '/', afterSignUpUrl: '/' }); }
          catch (e) { console.error('mountSignIn:', e); }
          return;
        }
        await new Promise(r => setTimeout(r, 100));
      }
    })();
    return () => {
      cancelled = true;
      try { window.Clerk?.unmountSignIn?.(el); } catch(e){}
    };
  }, []);

  return (
    <div className="auth-page">
      <div className="auth-side">
        <div style={{fontFamily:'var(--mono)', fontSize:11, letterSpacing:'0.22em', textTransform:'uppercase', color:'var(--accent-deep)'}}>
          Members’ entrance
        </div>
        <h1>Welcome back to<br/>the reading room.</h1>
        <p className="lede">
          Sign in with your society email to reserve volumes, manage your loans, and keep a wishlist for next month’s acquisitions meeting.
        </p>
        <div className="members-note">
          <span className="lbl">Not a member?</span>
          The library is a benefit of society membership. New members are welcomed each quarter — speak to the membership secretary at any clubhouse meeting, or write to <em>membership@mcmrs.org.uk</em>.
        </div>
      </div>

      {/* No .auth-card here — Clerk's widget brings its own card chrome.
          Wrapping it in our parchment card produces double padding/shadows. */}
      <div style={{ display:'flex', justifyContent:'center', alignItems:'flex-start' }}>
        <div ref={mountRef}/>
      </div>
    </div>
  );
};

/* ---------------------------------------------------------------- ACCOUNT */

window.AccountPage = function AccountPage({ go, data, session, onReturn, onRenew, wishlistToggle }){
  const [tab, setTab] = useStateM('loans');
  if (!session) { go({name:'login'}); return null; }

  const byId = id => data.books.find(b => String(b.id) === String(id));
  const loans   = session.loans;
  const history = session.history;
  const wish    = session.wishlist;

  const dueClass = (due) => {
    const d = window.daysBetween(window.today(), due);
    if (d < 0)  return 'overdue';
    if (d <= 7) return 'soon';
    return '';
  };
  const dueLabel = (due) => {
    const d = window.daysBetween(window.today(), due);
    if (d < 0)  return `${Math.abs(d)} days overdue`;
    if (d === 0)return 'Due today';
    return `Due in ${d} days`;
  };

  return (
    <div className="acct-page">
      <div className="acct-head">
        <div>
          <div style={{fontFamily:'var(--mono)', fontSize:11, letterSpacing:'0.22em', textTransform:'uppercase', color:'var(--ink-faded)'}}>
            Member’s Desk
          </div>
          <h1>{session.name}</h1>
        </div>
        <div className="meta">
          {session.member} · {loans.length} active loan{loans.length === 1 ? '' : 's'}
        </div>
      </div>

      <div className="acct-tabs">
        <button className={tab==='loans' ? 'active' : ''} onClick={() => setTab('loans')}>
          Current loans <span className="badge">{loans.length}</span>
        </button>
        <button className={tab==='history' ? 'active' : ''} onClick={() => setTab('history')}>Loan history</button>
        <button className={tab==='wishlist' ? 'active' : ''} onClick={() => setTab('wishlist')}>
          Wishlist <span className="badge">{wish.length}</span>
        </button>
        <button className={tab==='reviews' ? 'active' : ''} onClick={() => setTab('reviews')}>My reviews</button>
      </div>

      {tab==='loans' && (
        loans.length === 0
          ? <div className="empty">No active loans. Browse the catalogue and reserve a volume.</div>
          : <table className="ledger">
              <thead>
                <tr>
                  <th>Volume</th>
                  <th>Borrowed</th>
                  <th>Due</th>
                  <th>Status</th>
                  <th style={{textAlign:'right'}}>Actions</th>
                </tr>
              </thead>
              <tbody>
                {loans.map((l,i) => {
                  const b = byId(l.bookId); if (!b) return null;
                  const dc = dueClass(l.due);
                  return (
                    <tr key={i}>
                      <td>
                        <div className="tt" onClick={() => go({name:'book', id:b.id})}>{b.title}</div>
                        <span className="auth">by {b.author}</span>
                        {b.call && <span className="cl">{b.call}</span>}
                      </td>
                      <td className="cl">{window.fmtDate(l.borrowed)}</td>
                      <td className={"due " + dc}>{window.fmtDate(l.due)}</td>
                      <td><span className={"pill " + (dc === 'overdue' ? 'over' : dc === 'soon' ? 'res' : 'in')}>{dueLabel(l.due)}</span></td>
                      <td className="actions">
                        <button className="btn btn-ghost" onClick={() => onRenew(l)}>Renew</button>
                        <button className="btn btn-danger" onClick={() => onReturn(l)}>Return</button>
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
      )}

      {tab==='history' && (
        history.length === 0
          ? <div className="empty">No loan history yet.</div>
          : <table className="ledger">
              <thead>
                <tr>
                  <th>Volume</th>
                  <th>Borrowed</th>
                  <th>Returned</th>
                  <th>Duration</th>
                  <th style={{textAlign:'right'}}></th>
                </tr>
              </thead>
              <tbody>
                {history.map((l,i) => {
                  const b = byId(l.bookId); if (!b) return null;
                  return (
                    <tr key={i}>
                      <td>
                        <div className="tt" onClick={() => go({name:'book', id:b.id})}>{b.title}</div>
                        <span className="auth">by {b.author}</span>
                      </td>
                      <td className="cl">{window.fmtDate(l.borrowed)}</td>
                      <td className="cl">{window.fmtDate(l.returned)}</td>
                      <td className="cl">{window.daysBetween(l.borrowed, l.returned)} days</td>
                      <td className="actions">
                        <button className="btn btn-ghost" onClick={() => go({name:'book', id:b.id})}>Borrow again</button>
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
      )}

      {tab==='wishlist' && (
        <div className="shelf" style={{gridTemplateColumns:'repeat(5, 1fr)'}}>
          {wish.map(id => {
            const b = byId(id); if (!b) return null;
            return (
              <div key={id}>
                <window.Bookplate book={b} onClick={() => go({name:'book', id:b.id})}/>
                <div style={{marginTop:10, display:'flex', justifyContent:'space-between', alignItems:'center', fontFamily:'var(--mono)', fontSize:10, letterSpacing:'0.12em', textTransform:'uppercase', color:'var(--ink-faded)'}}>
                  <span>{b.copies - b.onLoan > 0 ? 'On shelf' : 'On loan'}</span>
                  <button className="btn btn-ghost" style={{padding:'5px 9px', fontSize:9}} onClick={() => wishlistToggle(id)}>Remove</button>
                </div>
              </div>
            );
          })}
          {wish.length === 0 && <div className="empty" style={{gridColumn:'1 / -1'}}>Your wishlist is empty. Browse the catalogue and add titles you’d like to read.</div>}
        </div>
      )}

      {tab==='reviews' && (
        <div className="empty">
          Reviews are coming in Phase 2 — once they’re wired, your reviews will appear here.
        </div>
      )}
    </div>
  );
};


/* ------------------------------------------------------------- CONFIRM */

window.ConfirmPage = function ConfirmPage({ go, data, route }){
  const b = data.books.find(b => String(b.id) === String(route.id));
  if (!b) return null;
  const ref = route.ref || ('MCMRS-' + Math.floor(Math.random()*9000 + 1000));
  return (
    <div className="confirm">
      <div className="stamp">
        {route.held ? 'On hold' : 'Reserved'}
        <span className="smallc">Mid-Counties MRS</span>
      </div>
      <h1>{route.held ? 'You’re in the queue.' : 'Your volume is reserved.'}</h1>
      <p className="lede">
        {route.held
          ? 'You’ll be notified when the volume is returned and ready to collect.'
          : 'Please collect at the clubhouse on the next library evening. Bring your membership card.'}
      </p>
      <div className="slip">
        <div className="slip-title">
          {b.title}
          <em>by {b.author}</em>
        </div>
        <dl>
          <dt>Reference</dt><dd>{ref}</dd>
          {b.call && <><dt>Call no.</dt><dd>{b.call}</dd></>}
          <dt>Collect at</dt><dd>Clubhouse library desk</dd>
          <dt>Collect by</dt><dd>{route.held ? 'When a copy is returned' : 'Next library evening'}</dd>
          <dt>Loan period</dt><dd>6 weeks · renewable once</dd>
        </dl>
      </div>
      <div className="btn-row" style={{ justifyContent:'center' }}>
        <button className="btn btn-primary" onClick={() => go({name:'account'})}>View my loans</button>
        <button className="btn btn-ghost" onClick={() => go({name:'home'})}>Back to the library</button>
      </div>
    </div>
  );
};


/* ------------------------------------------------------------- LIBRARIAN
   Admin-only: shows real outstanding loans (Phase 2 will populate),
   provides book add / edit / delete / CSV import, and admin management. */

window.LibrarianPage = function LibrarianPage({ go, data, isAdmin, reload }){
  const [barcode, setBarcode] = useStateM('');
  const [flash, setFlash] = useStateM(null);
  const [showAdd, setShowAdd] = useStateM(false);
  const [editingBook, setEditingBook] = useStateM(null);
  const [showImport, setShowImport] = useStateM(false);
  const [showAdmins, setShowAdmins] = useStateM(false);

  if (!isAdmin) {
    return (
      <div className="section">
        <div className="empty" style={{margin:'80px auto', maxWidth:520}}>
          The Librarian’s Dashboard is for admins only. Ask an existing admin to add your account.
        </div>
      </div>
    );
  }

  const onScan = (e) => {
    e.preventDefault();
    if (!barcode.trim()) return;
    const hit = data.books.find(b =>
      (b.call || '').toLowerCase() === barcode.trim().toLowerCase() ||
      String(b.id) === barcode.trim()
    );
    if (hit) {
      setFlash({
        book: hit,
        when: new Date().toLocaleTimeString('en-GB', {hour:'2-digit', minute:'2-digit'})
      });
      setTimeout(() => setFlash(null), 4200);
    } else {
      setFlash({ book: null, msg: `No volume found for "${barcode}".` });
      setTimeout(() => setFlash(null), 3000);
    }
    setBarcode('');
  };

  const totalCopies = data.books.reduce((s,b) => s + (b.copies||0), 0);
  const totalOnLoan = data.books.reduce((s,b) => s + (b.onLoan||0), 0);

  return (
    <div className="lib-shell">
      <div className="lib-head">
        <div>
          <div style={{fontFamily:'var(--mono)', fontSize:11, letterSpacing:'0.22em', textTransform:'uppercase', color:'var(--accent-deep)'}}>
            Staff view
          </div>
          <h1>Librarian’s Dashboard</h1>
        </div>
        <div className="role">Admin tools</div>
      </div>

      <div className="lib-kpis">
        <div className="kpi"><div className="k-lbl">Volumes catalogued</div><div className="k-num">{data.books.length}</div><div className="k-sub">{totalCopies} copies on the shelves</div></div>
        <div className="kpi"><div className="k-lbl">On loan</div><div className="k-num">{totalOnLoan}</div><div className="k-sub">across the collection</div></div>
        <div className="kpi"><div className="k-lbl">Acquisitions</div><div className="k-num">+{data.books.filter(b => (b.created_at||'').startsWith(String(new Date().getFullYear()))).length}</div><div className="k-sub">this year</div></div>
        <div className="kpi"><div className="k-lbl">Lending</div><div className="k-num" style={{fontSize:18, fontFamily:'var(--mono)'}}>Phase 2</div><div className="k-sub">loans / holds in next build</div></div>
      </div>

      <div className="lib-grid">
        <div className="panel">
          <h3>Catalogue management <span className="eyebrow">Admin actions</span></h3>
          <div style={{ padding:'16px 20px', display:'flex', gap:10, flexWrap:'wrap', borderBottom:'1px solid var(--rule-soft)' }}>
            <button className="btn btn-primary" onClick={() => { setEditingBook(null); setShowAdd(true); }}>+ Add volume</button>
            <button className="btn btn-ghost" onClick={() => setShowImport(true)}>Import CSV</button>
            <button className="btn btn-ghost" onClick={() => setShowAdmins(true)}>Manage admins</button>
          </div>

          <h3 style={{ borderTop:0, marginTop:0 }}>Check-in / Check-out <span className="eyebrow">Phase 2 — scan or type call no.</span></h3>
          <form className="scan-row" onSubmit={onScan}>
            <input
              placeholder="Scan barcode or type call number (e.g. 625.19 ROU) — Phase 2 preview"
              value={barcode}
              onChange={e => setBarcode(e.target.value)}
            />
            <button type="submit" className="btn btn-primary">Look up</button>
          </form>
          {flash && flash.book && (
            <div style={{ padding:'16px 20px', borderBottom:'1px solid var(--rule-soft)', display:'flex', gap:14, alignItems:'center' }}>
              <div style={{ flex:1 }}>
                <div style={{fontFamily:'var(--serif-display)', fontSize:17, fontWeight:500}}>{flash.book.title}</div>
                <div style={{fontFamily:'var(--mono)', fontSize:11, color:'var(--ink-faded)', letterSpacing:'0.06em'}}>
                  {flash.book.call || '—'} · {flash.when}
                </div>
              </div>
              <button className="btn btn-ghost" onClick={() => { setEditingBook(flash.book); setShowAdd(true); }}>Edit</button>
            </div>
          )}
          {flash && flash.msg && (
            <div style={{ padding:'16px 20px', color:'var(--ink-faded)', fontStyle:'italic' }}>{flash.msg}</div>
          )}

          <div className="lib-table">
            <table>
              <thead>
                <tr>
                  <th>Volume</th>
                  <th>Author</th>
                  <th>Region</th>
                  <th>Copies</th>
                  <th>Updated</th>
                  <th style={{textAlign:'right'}}>Actions</th>
                </tr>
              </thead>
              <tbody>
                {data.books.slice(0, 12).map(b => (
                  <tr key={b.id}>
                    <td>
                      <div className="tt">{b.title}</div>
                      {b.call && <div className="sub">{b.call}</div>}
                    </td>
                    <td>
                      <div style={{fontFamily:'var(--serif-body)', fontSize:14}}>{b.author}</div>
                    </td>
                    <td className="sub">{b.region}</td>
                    <td className="sub">{b.copies}</td>
                    <td className="sub">{window.fmtDate(b.updated_at)}</td>
                    <td className="actions" style={{textAlign:'right'}}>
                      <button className="btn btn-ghost" onClick={() => { setEditingBook(b); setShowAdd(true); }}>Edit</button>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
            {data.books.length > 12 && (
              <div style={{ padding:'12px 20px', fontStyle:'italic', color:'var(--ink-faded)' }}>
                Showing first 12 — use Search to find any volume to edit.
              </div>
            )}
          </div>
        </div>

        <div className="panel">
          <h3>Outstanding loans <span className="eyebrow">Phase 2 — coming soon</span></h3>
          <div className="empty" style={{ margin:24 }}>
            Once Phase 2 wires the loans API, this table will list members with books out, due dates, and overdue notice options.
          </div>
        </div>
      </div>

      {showAdd && (
        <BookEditor
          book={editingBook}
          onClose={() => { setShowAdd(false); setEditingBook(null); }}
          onSaved={() => { setShowAdd(false); setEditingBook(null); reload(); }}
        />
      )}
      {showImport && (
        <CsvImporter onClose={() => setShowImport(false)} onDone={() => { setShowImport(false); reload(); }}/>
      )}
      {showAdmins && (
        <AdminsDialog onClose={() => setShowAdmins(false)}/>
      )}
    </div>
  );
};

/* ---- Book editor (add / edit) ---- */
function BookEditor({ book, onClose, onSaved }){
  const [form, setForm] = useStateM(() => ({
    title:       book?.title       || '',
    author:      book?.author      || '',
    publisher:   book?.publisher   || '',
    year:        book?.year        || '',
    isbn:        book?.isbn        || '',
    call:        book?.call        || '',
    scale:       book?.scale       || '',
    era:         book?.era         || '',
    region:      book?.region      || '',
    subject:     book?.subject     || '',
    country:     book?.country     || '',
    condition:   book?.condition   || '',
    lang:        book?.lang        || 'English',
    pages:       book?.pages       || '',
    copies:      book?.copies      || 1,
    blurb:       book?.blurb       || '',
  }));
  const [busy, setBusy] = useStateM(false);
  const [err, setErr]   = useStateM(null);

  const change = (k, v) => setForm(f => ({ ...f, [k]: v }));

  const save = async (e) => {
    e.preventDefault();
    setBusy(true); setErr(null);
    try {
      const body = { ...form };
      if (book) await window.api(`/api/books/${book.id}`, { method:'PUT',  headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) });
      else      await window.api('/api/books',                { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) });
      onSaved();
    } catch(e) {
      setErr(e.message);
      setBusy(false);
    }
  };

  const del = async () => {
    if (!book || !confirm('Delete this volume from the catalogue?')) return;
    setBusy(true); setErr(null);
    try {
      await window.api(`/api/books/${book.id}`, { method:'DELETE' });
      onSaved();
    } catch(e) { setErr(e.message); setBusy(false); }
  };

  return (
    <ModalShell title={book ? 'Edit volume' : 'Add volume'} onClose={onClose}>
      <form onSubmit={save} style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:12}}>
        <Field label="Title *"     v={form.title}   onChange={v => change('title', v)} required full/>
        <Field label="Author"      v={form.author}  onChange={v => change('author', v)} full/>
        <Field label="Publisher"   v={form.publisher} onChange={v => change('publisher', v)}/>
        <Field label="Year"        v={form.year}    onChange={v => change('year', v)} type="number"/>
        <Field label="Call no."    v={form.call}    onChange={v => change('call', v)}/>
        <Field label="ISBN"        v={form.isbn}    onChange={v => change('isbn', v)}/>
        <Field label="Region"      v={form.region}  onChange={v => change('region', v)}/>
        <Field label="Era"         v={form.era}     onChange={v => change('era', v)}/>
        <Field label="Scale"       v={form.scale}   onChange={v => change('scale', v)}/>
        <Field label="Subject"     v={form.subject} onChange={v => change('subject', v)}/>
        <Field label="Country"     v={form.country} onChange={v => change('country', v)}/>
        <Field label="Condition"   v={form.condition} onChange={v => change('condition', v)}/>
        <Field label="Language"    v={form.lang}    onChange={v => change('lang', v)}/>
        <Field label="Pages"       v={form.pages}   onChange={v => change('pages', v)} type="number"/>
        <Field label="Copies"      v={form.copies}  onChange={v => change('copies', v)} type="number"/>
        <Field label="Blurb / notes" v={form.blurb} onChange={v => change('blurb', v)} textarea full/>
        {err && <div style={{gridColumn:'1 / -1', color:'#a3271c'}}>Error: {err}</div>}
        <div style={{gridColumn:'1 / -1', display:'flex', justifyContent:'space-between', gap:10, marginTop:8}}>
          <div>
            {book && <button type="button" className="btn btn-danger" disabled={busy} onClick={del}>Delete</button>}
          </div>
          <div style={{display:'flex', gap:10}}>
            <button type="button" className="btn btn-ghost" onClick={onClose} disabled={busy}>Cancel</button>
            <button type="submit" className="btn btn-primary" disabled={busy}>{busy ? 'Saving…' : 'Save'}</button>
          </div>
        </div>
      </form>
    </ModalShell>
  );
}

function Field({ label, v, onChange, type='text', textarea, full, required }){
  return (
    <label style={{ gridColumn: full ? '1 / -1' : 'auto', display:'block' }}>
      <div className="hint" style={{ marginBottom:4 }}>{label}</div>
      {textarea
        ? <textarea value={v} onChange={e => onChange(e.target.value)} required={required}
            style={{width:'100%', minHeight:80, padding:8, border:'1px solid var(--rule-soft)', borderRadius:2,
                    fontFamily:'inherit', background:'var(--paper)'}}/>
        : <input type={type} value={v} onChange={e => onChange(e.target.value)} required={required}
            style={{width:'100%', padding:'8px 10px', border:'1px solid var(--rule-soft)', borderRadius:2,
                    fontFamily:'inherit', background:'var(--paper)'}}/>
      }
    </label>
  );
}

/* ---- CSV import (existing endpoint) ---- */
function CsvImporter({ onClose, onDone }){
  const [busy, setBusy] = useStateM(false);
  const [err, setErr] = useStateM(null);
  const [result, setResult] = useStateM(null);
  const fileRef = useRefM(null);

  const submit = async (e) => {
    e.preventDefault();
    const file = fileRef.current?.files?.[0];
    if (!file) return;
    setBusy(true); setErr(null); setResult(null);
    try {
      const text = await file.text();
      const res = await window.api('/api/books/import', {
        method:'POST',
        headers:{'Content-Type':'text/csv'},
        body: text,
      });
      setResult(res);
    } catch(e) { setErr(e.message); }
    finally { setBusy(false); }
  };

  return (
    <ModalShell title="Import CSV" onClose={onClose}>
      <form onSubmit={submit}>
        <p className="hint">
          Header row required. Recognised columns: <code>title, author, publisher, year, isbn, scale, era, region, topic, country, condition, notes</code>.
          Only <code>title</code> is required.
        </p>
        <input ref={fileRef} type="file" accept=".csv,text/csv" required/>
        {err && <div style={{ marginTop:10, color:'#a3271c' }}>Error: {err}</div>}
        {result && <div style={{ marginTop:10 }}>Imported {result.imported} volume(s). Skipped {result.skipped}.</div>}
        <div style={{ marginTop:14, display:'flex', justifyContent:'flex-end', gap:10 }}>
          <button type="button" className="btn btn-ghost" onClick={result ? onDone : onClose} disabled={busy}>
            {result ? 'Done' : 'Cancel'}
          </button>
          {!result && <button type="submit" className="btn btn-primary" disabled={busy}>{busy ? 'Importing…' : 'Import'}</button>}
        </div>
      </form>
    </ModalShell>
  );
}

/* ---- Admin management (existing /api/admins endpoints) ---- */
function AdminsDialog({ onClose }){
  const [admins, setAdmins] = useStateM([]);
  const [newId, setNewId] = useStateM('');
  const [busy, setBusy] = useStateM(false);
  const [err, setErr] = useStateM(null);

  const load = async () => {
    try {
      const res = await window.api('/api/admins');
      setAdmins(res.admins || []);
    } catch(e) { setErr(e.message); }
  };
  useEffectM(() => { load(); }, []);

  const add = async (e) => {
    e.preventDefault();
    if (!newId.trim()) return;
    setBusy(true); setErr(null);
    try {
      await window.api('/api/admins', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ userId: newId.trim() }) });
      setNewId('');
      await load();
    } catch(e) { setErr(e.message); }
    finally { setBusy(false); }
  };

  const remove = async (userId) => {
    if (!confirm(`Revoke admin from ${userId}?`)) return;
    setBusy(true); setErr(null);
    try {
      await window.api(`/api/admins/${encodeURIComponent(userId)}`, { method:'DELETE' });
      await load();
    } catch(e) { setErr(e.message); }
    finally { setBusy(false); }
  };

  return (
    <ModalShell title="Manage admins" onClose={onClose}>
      <div style={{display:'flex', flexDirection:'column', gap:6, marginBottom:14}}>
        {admins.map(a => (
          <div key={a.userId} style={{display:'flex', justifyContent:'space-between', alignItems:'center', padding:'8px 0', borderBottom:'1px solid var(--rule-soft)'}}>
            <div>
              <div style={{fontFamily:'var(--serif-body)', fontSize:14}}>{a.name}</div>
              <div className="hint">{a.email || a.userId}</div>
            </div>
            <button className="btn btn-ghost" onClick={() => remove(a.userId)} disabled={busy}>Revoke</button>
          </div>
        ))}
        {admins.length === 0 && <div className="empty">No admins.</div>}
      </div>
      <form onSubmit={add} style={{display:'flex', gap:8}}>
        <input value={newId} onChange={e => setNewId(e.target.value)} placeholder="Clerk user id (user_xxx…)"
          style={{flex:1, padding:'8px 10px', border:'1px solid var(--rule-soft)', borderRadius:2, fontFamily:'inherit', background:'var(--paper)'}}/>
        <button type="submit" className="btn btn-primary" disabled={busy}>{busy ? 'Adding…' : 'Promote'}</button>
      </form>
      {err && <div style={{ marginTop:10, color:'#a3271c' }}>Error: {err}</div>}
      <div style={{ marginTop:14, display:'flex', justifyContent:'flex-end' }}>
        <button type="button" className="btn btn-ghost" onClick={onClose}>Close</button>
      </div>
    </ModalShell>
  );
}

/* ---- Generic modal shell ---- */
function ModalShell({ title, children, onClose }){
  useEffectM(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [onClose]);
  return (
    <div onClick={onClose}
      style={{ position:'fixed', inset:0, background:'rgba(20,16,8,0.55)', display:'flex',
               alignItems:'center', justifyContent:'center', zIndex:1000, padding:20 }}>
      <div onClick={e => e.stopPropagation()}
        style={{ background:'var(--paper)', border:'1px solid var(--rule)', maxWidth:720, width:'100%',
                 maxHeight:'90vh', overflow:'auto', boxShadow:'0 20px 60px rgba(0,0,0,0.4)' }}>
        <div style={{padding:'18px 22px', borderBottom:'1px solid var(--rule-soft)', display:'flex', justifyContent:'space-between', alignItems:'center'}}>
          <h3 style={{ margin:0, fontFamily:'var(--serif-display)', fontWeight:500, fontSize:20 }}>{title}</h3>
          <button onClick={onClose} style={{ background:'none', border:'none', cursor:'pointer', fontSize:22, color:'var(--ink-faded)' }}>×</button>
        </div>
        <div style={{ padding:22 }}>{children}</div>
      </div>
    </div>
  );
}

window.BrowsePage = function BrowsePage({ go, data, route }){
  return <window.SearchPage go={go} route={{...route, name:'search'}} data={data}/>;
};

Object.assign(window, {
  LoginPage: window.LoginPage,
  AccountPage: window.AccountPage,
  ConfirmPage: window.ConfirmPage,
  LibrarianPage: window.LibrarianPage,
  BrowsePage: window.BrowsePage,
});
