// ============================================================================
//  Admin screens — Users & access (two views) + Gradebook
//
//  Access management is a master–detail with two switchable views, not a wide
//  user×assignment matrix (which doesn't scale past a handful of problems):
//    • By student    — pick a student → category-grouped checklist of all problems
//    • By assignment  — pick a problem → student list with multi-select distribute
//  Both use only POST/DELETE /admin/users/:id/access. New problems are authored
//  from the command line, not here.
// ============================================================================

function groupByCategory(asgs) {
  const m = new Map();
  for (const a of asgs) {
    const c = a.category || 'Other';
    if (!m.has(c)) m.set(c, []);
    m.get(c).push(a);
  }
  return [...m.entries()].map(([name, items]) => ({ name, items }));
}

// Square grant toggle (✓ granted / dash not-granted). `locked` = admin row.
function GrantToggle({ granted, locked, onClick }) {
  return (
    <button
      onClick={() => !locked && onClick()}
      disabled={locked}
      title={locked ? 'admins have all access' : granted ? 'revoke' : 'grant'}
      style={{
        width: 26, height: 26, borderRadius: 7, cursor: locked ? 'default' : 'pointer', flexShrink: 0,
        display: 'inline-grid', placeItems: 'center', transition: 'all .14s',
        border: `1px solid ${granted ? 'oklch(0.88 0.19 138 / 0.45)' : 'var(--line)'}`,
        background: granted ? 'oklch(0.88 0.19 138 / 0.16)' : 'transparent',
        color: granted ? 'var(--acc)' : 'var(--faint)', opacity: locked ? 0.55 : 1,
      }}>
      {granted ? <Icon name="check" size={13} stroke={2.4} /> : <span style={{ width: 9, height: 1.5, background: 'currentColor', opacity: 0.5 }} />}
    </button>
  );
}

function CreateStudent({ onCreated }) {
  const [nu, setNu] = useState('');
  const [np, setNp] = useState('');
  const [ferr, setFerr] = useState('');
  const [busy, setBusy] = useState(false);
  const toast = useToast();
  const submit = async (e) => {
    e.preventDefault(); setFerr('');
    if (!/^[A-Za-z0-9_.-]{3,32}$/.test(nu)) { setFerr('Username: 3–32 chars of A–Z a–z 0–9 _ . -'); return; }
    if (np.length < 8) { setFerr('Password must be at least 8 characters.'); return; }
    setBusy(true);
    try { await api.createUser(nu, np); toast(`Created ${nu}.`); setNu(''); setNp(''); onCreated(); }
    catch (er) { setFerr(er.error); } finally { setBusy(false); }
  };
  return (
    <form onSubmit={submit} className="panel panel-pad" style={{ display: 'flex', flexDirection: 'column', gap: 12, marginBottom: 16 }}>
      <div className="field"><label>NEW USERNAME</label><input className={`input ${ferr ? 'err' : ''}`} value={nu} onChange={(e) => setNu(e.target.value)} placeholder="e.g. dara_k" /></div>
      <div className="field"><label>TEMP PASSWORD</label><input className={`input ${ferr ? 'err' : ''}`} type="password" value={np} onChange={(e) => setNp(e.target.value)} placeholder="min 8 chars" /></div>
      <button className="btn btn-primary" disabled={busy}>{busy ? <Spinner>Creating</Spinner> : <><Icon name="plus" size={14} />Create student</>}</button>
      {ferr && <div className="field-err">{ferr}</div>}
    </form>
  );
}

// --------------------------------------------------- ADMIN · USERS ----------
function AdminUsers() {
  const [view, setView] = useState('student');
  const [users, setUsers] = useState(null);
  const [asgs, setAsgs] = useState(null);
  const toast = useToast();

  const loadUsers = () => api.adminUsers().then((r) => setUsers(r.data.users));
  useEffect(() => { loadUsers(); api.assignments().then((r) => setAsgs(r.data.assignments)); }, []);

  // grant/revoke one (user, assignment), optimistic on the users list
  const setGrant = async (userId, asgId, on) => {
    setUsers((list) => list.map((u) => u.id === userId
      ? { ...u, access: on ? [...new Set([...u.access, asgId])] : u.access.filter((a) => a !== asgId) }
      : u));
    try { await api.grant(userId, asgId, on); }
    catch (er) { toast(er.error, 'err'); loadUsers(); }
  };

  const ready = users && asgs;
  return (
    <div className="page page-wide fade-up">
      <div className="page-head">
        <p className="eyebrow">Admin · access control</p>
        <h1 className="page-title">Users &amp; access</h1>
        <p className="page-sub">Grant which problems each student can attempt — pick a student, or distribute one problem to many students. New problems are added from the command line.</p>
      </div>

      <div style={{ display: 'inline-flex', gap: 6, padding: 4, border: '1px solid var(--line)', borderRadius: 12, background: 'var(--panel)', marginBottom: 20 }}>
        {[['student', 'user', 'By student'], ['assignment', 'book', 'By assignment']].map(([v, ico, label]) => (
          <button key={v} className="btn btn-sm" onClick={() => setView(v)} style={{
            border: 'none', background: view === v ? 'var(--panel-3)' : 'transparent',
            color: view === v ? 'var(--text)' : 'var(--muted)',
          }}><Icon name={ico} size={14} />{label}</button>
        ))}
      </div>

      {!ready ? <Loading label="Loading users & assignments" /> :
        view === 'student'
          ? <ByStudent users={users} asgs={asgs} setGrant={setGrant} reload={loadUsers} />
          : <ByAssignment users={users} asgs={asgs} setGrant={setGrant} />}
    </div>
  );
}

// ---- view A: by student ----------------------------------------------------
function ByStudent({ users, asgs, setGrant, reload }) {
  const [selId, setSelId] = useState(null);
  const [q, setQ] = useState('');
  const toast = useToast();
  const students = users.filter((u) => u.role === 'student');
  const sel = users.find((u) => u.id === selId) || null;
  const groups = React.useMemo(() => groupByCategory(asgs), [asgs]);

  // Multi-select: tick any number of problems (across groups) and grant/revoke
  // them for the selected student in one action — student↔assignment is many-to-many.
  const [picked, setPicked] = useState(() => new Set());
  React.useEffect(() => { setPicked(new Set()); }, [selId]);
  const togglePick = (id) => setPicked((p) => { const n = new Set(p); n.has(id) ? n.delete(id) : n.add(id); return n; });
  const applyPicked = (on) => {
    if (!sel) return;
    [...picked].forEach((id) => { const g = sel.access.includes(id); if (on ? !g : g) setGrant(sel.id, id, on); });
    setPicked(new Set());
  };

  const del = async (u, e) => {
    e.stopPropagation();
    if (!window.confirm(`Delete ${u.username}? This cascades their grants and results.`)) return;
    try { await api.deleteUser(u.id); toast(`Deleted ${u.username}.`); if (selId === u.id) setSelId(null); reload(); }
    catch (er) { toast(er.error, 'err'); }
  };

  const ql = q.trim().toLowerCase();
  const match = (a) => !ql || a.id.toLowerCase().includes(ql) || a.title.toLowerCase().includes(ql);

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '300px 1fr', gap: 18, alignItems: 'start' }}>
      {/* left: create + students */}
      <div>
        <CreateStudent onCreated={reload} />
        <div className="panel" style={{ overflow: 'hidden' }}>
          {students.length === 0 ? <div style={{ padding: 18, color: 'var(--muted)', fontSize: 14 }}>No students yet.</div> :
            students.map((u) => (
              <div key={u.id} onClick={() => setSelId(u.id)} style={{
                display: 'flex', alignItems: 'center', gap: 11, padding: '12px 14px', cursor: 'pointer',
                borderBottom: '1px solid var(--line)', background: selId === u.id ? 'var(--panel-3)' : 'transparent',
              }}>
                <span className="avatar">{u.username[0].toUpperCase()}</span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontWeight: 500 }}>{u.username}</div>
                  <div className="mono" style={{ color: 'var(--faint)', fontSize: 11.5 }}>{u.access.length} / {asgs.length} granted</div>
                </div>
                <button className="btn btn-danger btn-sm" onClick={(e) => del(u, e)} title="delete"><Icon name="trash" size={13} /></button>
              </div>
            ))}
        </div>
      </div>

      {/* right: access editor */}
      {!sel ? <Empty icon="user" title="Select a student" sub="Pick a student to manage which problems they can attempt." /> :
        <div className="panel panel-pad">
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginBottom: 18, flexWrap: 'wrap' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 11 }}>
              <span className="avatar">{sel.username[0].toUpperCase()}</span>
              <div>
                <div style={{ fontWeight: 600, fontSize: 17 }}>{sel.username}</div>
                <div className="mono" style={{ color: 'var(--faint)', fontSize: 12 }}>granted {sel.access.length} / {asgs.length}</div>
              </div>
            </div>
            <input className="input" value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search problems…" style={{ width: 220 }} />
          </div>

          {picked.size > 0 && (
            <div style={{ position: 'sticky', top: 0, zIndex: 2, display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16, padding: '10px 14px', borderRadius: 10, border: '1px solid var(--acc-2)', background: 'oklch(0.30 0.06 150)' }}>
              <span style={{ fontWeight: 600, fontSize: 13.5, color: 'var(--text)' }}>{picked.size} selected</span>
              <span style={{ flex: 1 }} />
              <button className="btn btn-sm" onClick={() => applyPicked(true)}>Grant selected</button>
              <button className="btn btn-ghost btn-sm" onClick={() => applyPicked(false)}>Revoke selected</button>
              <button className="btn btn-ghost btn-sm" onClick={() => setPicked(new Set())}>Clear</button>
            </div>
          )}

          {groups.map((g) => {
            const items = g.items.filter(match);
            if (!items.length) return null;
            return (
              <div key={g.name} style={{ marginBottom: 18 }}>
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 9 }}>
                  <span style={{ fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: 1, textTransform: 'uppercase', color: 'var(--faint)' }}>{g.name}</span>
                  <span style={{ display: 'flex', gap: 6 }}>
                    <button className="btn btn-ghost btn-sm" onClick={() => setPicked((p) => { const n = new Set(p); items.forEach((a) => n.add(a.id)); return n; })}>Select all</button>
                    <button className="btn btn-ghost btn-sm" onClick={() => setPicked((p) => { const n = new Set(p); items.forEach((a) => n.delete(a.id)); return n; })}>Clear</button>
                  </span>
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
                  {items.map((a) => {
                    const granted = sel.access.includes(a.id);
                    const isPicked = picked.has(a.id);
                    return (
                      <div key={a.id} onClick={() => togglePick(a.id)} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '9px 12px', borderRadius: 9, cursor: 'pointer', border: `1px solid ${isPicked ? 'var(--acc-2)' : 'var(--line)'}`, background: isPicked ? 'oklch(0.88 0.19 138 / 0.06)' : 'transparent' }}>
                        <input type="checkbox" checked={isPicked} readOnly style={{ width: 15, height: 15, accentColor: 'var(--acc)', cursor: 'pointer', flexShrink: 0 }} />
                        <span className="file-ico"><Icon name="code" size={14} /></span>
                        <div style={{ flex: 1, minWidth: 0 }}>
                          <div style={{ fontWeight: 500, fontSize: 14 }}>{a.title}</div>
                          <div className="mono" style={{ color: 'var(--faint)', fontSize: 11.5 }}>#{a.id} · {a.totalPoints} pts</div>
                        </div>
                        <span onClick={(e) => e.stopPropagation()} style={{ display: 'inline-flex' }}>
                          <GrantToggle granted={granted} locked={false} onClick={() => setGrant(sel.id, a.id, !granted)} />
                        </span>
                      </div>
                    );
                  })}
                </div>
              </div>
            );
          })}
        </div>}
    </div>
  );
}

// ---- view B: by assignment -------------------------------------------------
function ByAssignment({ users, asgs, setGrant }) {
  const [selId, setSelId] = useState(null);
  const [q, setQ] = useState('');
  const [picked, setPicked] = useState(() => new Set());
  const students = users.filter((u) => u.role === 'student');
  const sel = asgs.find((a) => a.id === selId) || null;
  const groups = React.useMemo(() => groupByCategory(asgs), [asgs]);

  useEffect(() => { setPicked(new Set()); }, [selId]);

  const countFor = (asgId) => students.filter((u) => u.access.includes(asgId)).length;
  const ql = q.trim().toLowerCase();
  const shown = students.filter((u) => !ql || u.username.toLowerCase().includes(ql));
  const togglePick = (id) => setPicked((s) => { const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n; });
  const bulk = (on) => {
    shown.forEach((u) => {
      if (picked.has(u.id) && (on ? !u.access.includes(sel.id) : u.access.includes(sel.id))) setGrant(u.id, sel.id, on);
    });
    setPicked(new Set());
  };

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '300px 1fr', gap: 18, alignItems: 'start' }}>
      {/* left: problems by category */}
      <div className="panel" style={{ overflow: 'hidden' }}>
        {groups.map((g) => (
          <div key={g.name}>
            <div style={{ padding: '10px 14px', fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: 1, textTransform: 'uppercase', color: 'var(--faint)', background: 'var(--bg-2)' }}>{g.name}</div>
            {g.items.map((a) => (
              <div key={a.id} onClick={() => setSelId(a.id)} style={{
                display: 'flex', alignItems: 'center', gap: 10, padding: '11px 14px', cursor: 'pointer',
                borderBottom: '1px solid var(--line)', background: selId === a.id ? 'var(--panel-3)' : 'transparent',
              }}>
                <span className="file-ico"><Icon name="code" size={14} /></span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontWeight: 500, fontSize: 14, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{a.title}</div>
                  <div className="mono" style={{ color: 'var(--faint)', fontSize: 11.5 }}>#{a.id} · {countFor(a.id)} students</div>
                </div>
              </div>
            ))}
          </div>
        ))}
      </div>

      {/* right: students for the selected problem */}
      {!sel ? <Empty icon="book" title="Select a problem" sub="Pick a problem to choose which students can attempt it." /> :
        <div className="panel panel-pad">
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginBottom: 16, flexWrap: 'wrap' }}>
            <div>
              <div style={{ fontWeight: 600, fontSize: 17 }}>{sel.title}</div>
              <div className="mono" style={{ color: 'var(--faint)', fontSize: 12 }}>#{sel.id} · granted to {countFor(sel.id)} / {students.length}</div>
            </div>
            <input className="input" value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search students…" style={{ width: 200 }} />
          </div>

          {picked.size > 0 && (
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14, padding: '10px 12px', borderRadius: 9, border: '1px dashed var(--line)' }}>
              <span style={{ fontFamily: 'var(--mono)', fontSize: 12.5, color: 'var(--muted)' }}>{picked.size} selected</span>
              <button className="btn btn-primary btn-sm" onClick={() => bulk(true)}>Grant to selected</button>
              <button className="btn btn-ghost btn-sm" onClick={() => bulk(false)}>Revoke from selected</button>
            </div>
          )}

          <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
            {shown.length === 0 ? <div style={{ color: 'var(--muted)', fontSize: 14 }}>No students match.</div> :
              shown.map((u) => {
                const granted = u.access.includes(sel.id);
                return (
                  <div key={u.id} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '9px 12px', borderRadius: 9, border: `1px solid ${picked.has(u.id) ? 'var(--line-2)' : 'var(--line)'}` }}>
                    <input type="checkbox" checked={picked.has(u.id)} onChange={() => togglePick(u.id)} style={{ accentColor: 'var(--acc)', width: 16, height: 16 }} />
                    <span className="avatar">{u.username[0].toUpperCase()}</span>
                    <div style={{ flex: 1, fontWeight: 500 }}>{u.username}</div>
                    <GrantToggle granted={granted} locked={false} onClick={() => setGrant(u.id, sel.id, !granted)} />
                  </div>
                );
              })}
          </div>
        </div>}
    </div>
  );
}

// --------------------------------------------------- ADMIN · GRADEBOOK ------
function AdminGradebook() {
  const [rows, setRows] = useState(null);
  const [users, setUsers] = useState([]);
  const [asgs, setAsgs] = useState([]);
  const [fUser, setFUser] = useState('');
  const [fAsg, setFAsg] = useState('');
  const [modal, setModal] = useState(null); // { username, title, submission } — source viewer
  const toast = useToast();

  const [reloading, setReloading] = useState(false);

  // Hot-reload the bank, then refresh the assignment axis so newly synced
  // problems appear in the filter immediately.
  const reload = async () => {
    setReloading(true);
    try {
      const r = await api.reloadProblems();
      const a = await api.assignments();
      setAsgs(a.data.assignments);
      toast(`Reloaded ${r.data.count} problems.`);
    } catch (e) {
      toast(e.error || 'Reload failed.', 'err');
    } finally { setReloading(false); }
  };

  // Open the source viewer for one (student, assignment) cell — their latest code.
  const openSubs = async (r) => {
    setModal({ username: r.username, title: titleOf(r.assignmentId), submission: undefined });
    try {
      const res = await api.adminLastSubmission(r.userId, r.assignmentId);
      setModal((m) => (m ? { ...m, submission: res.data.submission } : m));
    } catch (e) {
      setModal(null);
      toast(e.error || 'Could not load the submission.', 'err');
    }
  };

  useEffect(() => {
    api.adminUsers().then((r) => setUsers(r.data.users.filter((u) => u.role === 'student')));
    api.assignments().then((r) => setAsgs(r.data.assignments));
  }, []);
  useEffect(() => {
    setRows(null);
    const f = {};
    if (fUser) f.userId = Number(fUser);
    if (fAsg) f.assignmentId = fAsg;
    api.gradebook(f).then((r) => setRows(r.data.gradebook));
  }, [fUser, fAsg]);

  const titleOf = (id) => asgs.find((a) => a.id === id)?.title || id;
  const stats = rows ? {
    rows: rows.length,
    passed: rows.filter((r) => r.passed).length,
    avg: rows.length ? Math.round(rows.reduce((s, r) => s + (r.best / r.total) * 100, 0) / rows.length) : 0,
  } : null;

  return (
    <div className="page page-wide fade-up">
      <div className="page-head">
        <p className="eyebrow">Admin · whole class</p>
        <h1 className="page-title">Gradebook</h1>
        <p className="page-sub">Best score per student per assignment. Filter by student or problem.</p>
      </div>

      {stats && (
        <div style={{ display: 'flex', gap: 14, marginBottom: 20, flexWrap: 'wrap' }}>
          <Stat label="Records" value={stats.rows} />
          <Stat label="Passing" value={stats.passed} accent />
          <Stat label="Class average" value={stats.avg + '%'} />
        </div>
      )}

      <div className="panel panel-pad" style={{ display: 'flex', gap: 14, alignItems: 'flex-end', marginBottom: 22, flexWrap: 'wrap' }}>
        <span style={{ display: 'flex', alignItems: 'center', gap: 8, color: 'var(--muted)', fontFamily: 'var(--mono)', fontSize: 12 }}><Icon name="filter" size={14} />FILTERS</span>
        <div className="field" style={{ flex: '0 1 200px' }}>
          <label>STUDENT</label>
          <select className="input" value={fUser} onChange={(e) => setFUser(e.target.value)}>
            <option value="">All students</option>
            {users.map((u) => <option key={u.id} value={u.id}>{u.username}</option>)}
          </select>
        </div>
        <div className="field" style={{ flex: '0 1 220px' }}>
          <label>ASSIGNMENT</label>
          <select className="input" value={fAsg} onChange={(e) => setFAsg(e.target.value)}>
            <option value="">All assignments</option>
            {asgs.map((a) => <option key={a.id} value={a.id}>{a.title}</option>)}
          </select>
        </div>
        {(fUser || fAsg) && <button className="btn btn-ghost btn-sm" onClick={() => { setFUser(''); setFAsg(''); }}>Clear</button>}
        <span style={{ flex: 1 }} />
        <button className="btn btn-ghost btn-sm" onClick={reload} disabled={reloading} title="Re-read the problem bank from disk (after syncing problem files)">
          {reloading ? <Spinner>Reloading</Spinner> : <><Icon name="arrow" size={13} />Reload problems</>}
        </button>
      </div>

      {!rows ? <Loading label="Loading gradebook" /> :
        rows.length === 0 ? <Empty icon="grid" title="No records match" sub="Try clearing the filters." /> :
        <div className="tbl-wrap">
          <table className="tbl">
            <thead><tr><th>Student</th><th>Assignment</th><th>Status</th><th>Attempts</th><th>Best</th><th>Last submitted (UTC+8)</th><th>Code</th></tr></thead>
            <tbody>
              {rows.map((r, i) => (
                <tr key={i}>
                  <td><div style={{ display: 'flex', alignItems: 'center', gap: 10 }}><span className="avatar" style={{ width: 24, height: 24, fontSize: 11 }}>{r.username[0].toUpperCase()}</span><span style={{ fontWeight: 500 }}>{r.username}</span></div></td>
                  <td><div>{titleOf(r.assignmentId)}</div><div className="mono" style={{ color: 'var(--faint)', fontSize: 12 }}>#{r.assignmentId}</div></td>
                  <td><PassBadge passed={r.passed} /></td>
                  <td className="num" style={{ color: 'var(--muted)' }}>{r.attempts}</td>
                  <td className="num"><span style={{ color: r.best === r.total ? 'var(--acc)' : 'var(--text)', fontWeight: 600 }}>{r.best}</span><span style={{ color: 'var(--faint)' }}> / {r.total}</span></td>
                  <td className="mono" style={{ color: 'var(--muted)', fontSize: 12.5 }}>{fmtTime(r.lastAt)}</td>
                  <td><button className="btn btn-ghost btn-sm" onClick={() => openSubs(r)} title="View submitted source"><Icon name="code" size={13} />View</button></td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>}

      {modal && (
        <Modal wide onClose={() => setModal(null)}
          title={`${modal.username} · ${modal.title}`}
          sub="The student's latest submission — the exact source they last submitted.">
          <SourceView submission={modal.submission} />
        </Modal>
      )}
    </div>
  );
}

function Stat({ label, value, accent }) {
  return (
    <div className="panel" style={{ padding: '16px 22px', minWidth: 150, flex: '1 1 0' }}>
      <div style={{ fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: 1, textTransform: 'uppercase', color: 'var(--faint)' }}>{label}</div>
      <div style={{ fontFamily: 'var(--mono)', fontSize: 30, fontWeight: 700, marginTop: 7, color: accent ? 'var(--acc)' : 'var(--text)' }}>{value}</div>
    </div>
  );
}

Object.assign(window, { AdminUsers, AdminGradebook });
