// ============================================================================
//  Screens — Login, StudentHome, Solve, MyResults, AdminUsers, AdminGradebook
// ============================================================================

// --------------------------------------------------------------- MARKDOWN ----
// Tiny, dependency-free renderer for the subset used in problem statements:
// `code` spans, **bold**, ordered/bulleted lists, and paragraphs with line
// breaks. Everything is rendered as React nodes (auto-escaped), so angle-bracket
// placeholders like <StudentName> show literally and there is no XSS surface.
const mdCodeStyle = { fontFamily: 'var(--mono)', fontSize: '0.92em', background: 'rgba(127,127,127,0.14)', borderRadius: 4, padding: '1px 5px' };
const mdListStyle = { margin: '2px 0 9px', paddingLeft: 22 };

function mdInline(text, keyBase) {
  const out = [];
  const re = /`([^`]+)`|\*\*([^*]+)\*\*/g;
  let last = 0, m, k = 0;
  while ((m = re.exec(text)) !== null) {
    if (m.index > last) out.push(text.slice(last, m.index));
    if (m[1] != null) out.push(<code key={keyBase + '-' + k++} style={mdCodeStyle}>{m[1]}</code>);
    else out.push(<strong key={keyBase + '-' + k++}>{m[2]}</strong>);
    last = re.lastIndex;
  }
  if (last < text.length) out.push(text.slice(last));
  return out;
}

function Markdown({ text }) {
  const lines = String(text || '').split('\n');
  const blocks = [];
  let para = [], list = null, code = null; // code = null when not inside a ``` fence
  const flushPara = () => { if (para.length) { blocks.push({ t: 'p', lines: para }); para = []; } };
  const flushList = () => { if (list) { blocks.push({ t: 'list', ordered: list.ordered, items: list.items }); list = null; } };
  const flushCode = () => { if (code !== null) { blocks.push({ t: 'code', lines: code }); code = null; } };
  for (const line of lines) {
    // Fenced code block: ``` toggles a verbatim, whitespace-preserving block.
    // Critical for example I/O — inside <pre> the exact spaces survive (an inline
    // `code` span collapses runs of whitespace, which silently mangles aligned output).
    if (/^\s*```/.test(line)) {
      if (code !== null) flushCode(); else { flushPara(); flushList(); code = []; }
      continue;
    }
    if (code !== null) { code.push(line); continue; }
    const om = line.match(/^\s*\d+\.\s+(.*)$/);
    const um = line.match(/^\s*[-*]\s+(.*)$/);
    if (om) { flushPara(); if (!list || !list.ordered) { flushList(); list = { ordered: true, items: [] }; } list.items.push(om[1]); }
    else if (um) { flushPara(); if (!list || list.ordered) { flushList(); list = { ordered: false, items: [] }; } list.items.push(um[1]); }
    else if (line.trim() === '') { flushPara(); flushList(); }
    else { flushList(); para.push(line); }
  }
  flushPara(); flushList(); flushCode();
  return (
    <div className="md">
      {blocks.map((b, i) => {
        if (b.t === 'code') {
          return <pre key={i} style={{ margin: '2px 0 11px', padding: '11px 13px', background: 'var(--bg-2)', border: '1px solid var(--line)', borderRadius: 8, fontFamily: 'var(--mono)', fontSize: 12.5, lineHeight: 1.55, color: 'var(--text)', overflow: 'auto', whiteSpace: 'pre' }}>{b.lines.join('\n')}</pre>;
        }
        if (b.t === 'list') {
          const items = b.items.map((it, j) => <li key={j} style={{ marginBottom: 3 }}>{mdInline(it, i + '-' + j)}</li>);
          return b.ordered ? <ol key={i} style={mdListStyle}>{items}</ol> : <ul key={i} style={mdListStyle}>{items}</ul>;
        }
        return (
          <p key={i} style={{ margin: '0 0 9px' }}>
            {b.lines.map((ln, j) => <React.Fragment key={j}>{j > 0 && <br />}{mdInline(ln, i + '-' + j)}</React.Fragment>)}
          </p>
        );
      })}
    </div>
  );
}

// Flatten markdown to plain text for the compact card teaser.
function mdStrip(text) {
  return String(text || '').replace(/[`*]/g, '').replace(/\s+/g, ' ').trim();
}

// ---------------------------------------------------------------- LOGIN ------
function Login({ onAuthed }) {
  const [u, setU] = useState('');
  const [p, setP] = useState('');
  const [err, setErr] = useState('');
  const [busy, setBusy] = useState(false);

  const submit = async (uu = u, pp = p) => {
    setErr('');
    if (!uu || !pp) { setErr('Enter a username and password.'); return; }
    setBusy(true);
    try {
      const r = await api.login(uu, pp);
      onAuthed(r.data);
    } catch (e) {
      setErr(e.error || 'Login failed.');
    } finally { setBusy(false); }
  };

  return (
    <div style={{ minHeight: '100vh', display: 'grid', placeItems: 'center', position: 'relative', zIndex: 1, padding: 24 }}>
      <div className="fade-up" style={{ width: 420, maxWidth: '100%' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 13, marginBottom: 30 }}>
          <div className="brand-mark" style={{ width: 42, height: 42, fontSize: 18 }}>SC</div>
          <div>
            <div style={{ fontWeight: 600, fontSize: 18, letterSpacing: '-0.3px' }}>Stellan's Classroom</div>
            <div style={{ fontFamily: 'var(--mono)', fontSize: 12, color: 'var(--faint)' }}>console · v1.0</div>
          </div>
        </div>

        <div className="panel panel-pad" style={{ padding: 28 }}>
          <h1 style={{ margin: '0 0 4px', fontSize: 21, fontWeight: 600 }}>Sign in</h1>
          <p style={{ margin: '0 0 22px', color: 'var(--muted)', fontSize: 14 }}>Authenticate to access your assignments.</p>

          <form onSubmit={(e) => { e.preventDefault(); submit(); }} style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
            <div className="field">
              <label>USERNAME</label>
              <input className={`input ${err ? 'err' : ''}`} value={u} autoFocus
                onChange={(e) => setU(e.target.value)} placeholder="alice" />
            </div>
            <div className="field">
              <label>PASSWORD</label>
              <input className={`input ${err ? 'err' : ''}`} type="password" value={p}
                onChange={(e) => setP(e.target.value)} placeholder="••••••••" />
            </div>
            {err && <div className="field-err" style={{ display: 'flex', alignItems: 'center', gap: 7 }}><Icon name="x" size={12} stroke={2.2} />{err}</div>}
            <button className="btn btn-primary" disabled={busy} style={{ marginTop: 4 }}>
              {busy ? <Spinner>Authenticating</Spinner> : <><Icon name="lock" size={15} />Sign in</>}
            </button>
          </form>
        </div>
      </div>
    </div>
  );
}

// Per-problem status chip for a library card: passed / in-progress (best score
// so far) / not started — so a student can see at a glance which problems still
// need fixing.
function ProblemStatus({ result }) {
  if (!result) return <span className="badge" style={{ color: 'var(--faint)', borderColor: 'var(--line)' }}>Not started</span>;
  if (result.passed) return <span className="badge pass"><Icon name="check" size={11} stroke={2.2} />Passed</span>;
  return <span className="badge fail"><Icon name="x" size={11} stroke={2.2} />{result.best}/{result.total}</span>;
}

// ---------------------------------------------------- STUDENT · HOME --------
function StudentHome({ onOpen, folderId, setFolderId }) {
  const [list, setList] = useState(null);
  const [results, setResults] = useState({}); // assignmentId -> report-card row
  useEffect(() => {
    api.assignments().then((r) => setList(r.data.assignments));
    // Drives the pass/fail badges. Best-effort: on failure the cards just read "Not started".
    api.myResults().then((r) => setResults(Object.fromEntries(r.data.results.map((x) => [x.assignmentId, x])))).catch(() => {});
  }, []);

  // Group the granted problems into topic folders by their server-provided
  // `category` (uncategorised → "Other problems"). Folder grouping is now
  // backend-driven; no client-side id→folder table.
  const folders = React.useMemo(() => {
    if (!list) return [];
    const byCat = new Map();
    for (const a of list) {
      const name = a.category || 'Other problems';
      if (!byCat.has(name)) byCat.set(name, []);
      byCat.get(name).push(a.id);
    }
    return [...byCat.entries()].map(([name, items]) => ({ id: name, name, items }));
  }, [list]);

  const openFolder = folders.find((f) => f.id === folderId);
  const metaOf = (id) => list.find((a) => a.id === id);

  return (
    <div className="page fade-up">
      <div className="page-head">
        <p className="eyebrow">Assignments</p>
        {!openFolder ? (
          <>
            <h1 className="page-title">Problem library</h1>
            <p className="page-sub">Your granted problems, organised into folders by topic. Open a folder to see the problems inside — test cases are never shown.</p>
          </>
        ) : (
          <>
            <div className="crumbs">
              <button className="crumb" onClick={() => setFolderId(null)}>Library</button>
              <span className="crumb-sep">/</span>
              <span className="crumb cur">{openFolder.name}</span>
            </div>
            <h1 className="page-title" style={{ marginTop: 12 }}>{openFolder.name}</h1>
          </>
        )}
      </div>

      {!list ? <Loading label="Fetching assignments" /> :
        folders.length === 0 ? <Empty icon="book" title="No assignments yet" sub="An admin hasn't granted you access to any problems." /> :
        !openFolder ? (
          <div className="card-grid">
            {folders.map((f, i) => (
              <button key={f.id} className="acard" style={{ animationDelay: `${i * 45}ms` }} onClick={() => setFolderId(f.id)}>
                <div className="folder-top">
                  <span className="folder-ico"><Icon name="folder" size={22} /></span>
                  <span style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
                    {(() => {
                      const done = f.items.filter((id) => results[id] && results[id].passed).length;
                      return done > 0 ? <span className="badge pass"><Icon name="check" size={11} stroke={2.2} />{done}/{f.items.length}</span> : null;
                    })()}
                    <span className="badge">{f.items.length} {f.items.length === 1 ? 'problem' : 'problems'}</span>
                  </span>
                </div>
                <div className="acard-title">{f.name}</div>
                <div className="acard-foot">
                  <span style={{ fontFamily: 'var(--mono)', fontSize: 12, color: 'var(--faint)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{f.items.map((id) => id + '.pseudo').join('  ·  ')}</span>
                  <span className="acard-cta" style={{ flexShrink: 0 }}>Open <Icon name="arrow" size={14} /></span>
                </div>
              </button>
            ))}
          </div>
        ) : (
          <>
            <button className="btn btn-ghost btn-sm" style={{ marginBottom: 18 }} onClick={() => setFolderId(null)}>
              <Icon name="chevron" size={13} style={{ transform: 'rotate(180deg)' }} />Back to library
            </button>
            <div className="card-grid">
              {openFolder.items.map((id, i) => {
                const a = metaOf(id);
                return (
                  <button key={id} className="acard" style={{ animationDelay: `${i * 45}ms` }} onClick={() => onOpen(id)}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 11 }}>
                      <span className="file-ico"><Icon name="code" size={15} /></span>
                      <span className="acard-id">{id}.pseudo</span>
                      <span style={{ flex: 1 }} />
                      <ProblemStatus result={results[id]} />
                    </div>
                    <div className="acard-title">{a.title}</div>
                    {a.description && <div style={{ fontSize: 12.5, color: 'var(--faint)', marginTop: 6, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', lineHeight: 1.5 }}>{mdStrip(a.description)}</div>}
                    <div className="acard-foot">
                      <span style={{ fontFamily: 'var(--mono)', fontSize: 12, color: 'var(--faint)' }}>IGCSE 0478</span>
                      <span className="acard-cta">Solve <Icon name="arrow" size={14} /></span>
                    </div>
                  </button>
                );
              })}
            </div>
          </>
        )}
    </div>
  );
}

// ------------------------------------------------ SOURCE VIEW ---------------
// Read-only view of one stored submission's source — the admin's gradebook
// viewer. `submission` is undefined while loading, null if none, else the row.
function SourceView({ submission }) {
  if (submission === undefined) return <Loading label="Loading submission" />;
  if (!submission) return <Empty icon="file" title="No submission yet" sub="This student hasn't submitted this problem." />;
  const s = submission;
  return (
    <div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 12 }}>
        <PassBadge passed={s.passed} />
        <span style={{ fontFamily: 'var(--mono)', fontSize: 13 }}>
          <span style={{ color: s.awarded === s.total ? 'var(--acc)' : 'var(--text)', fontWeight: 600 }}>{s.awarded}</span>
          <span style={{ color: 'var(--faint)' }}> / {s.total}</span>
        </span>
        <span style={{ flex: 1 }} />
        {s.at && <span className="mono" style={{ fontSize: 12, color: 'var(--muted)' }}>{fmtTime(s.at)} UTC+8</span>}
      </div>
      {s.source != null
        ? <pre style={{ margin: 0, padding: '13px 16px', borderRadius: 9, border: '1px solid var(--line)', background: 'var(--bg-2)', fontFamily: 'var(--mono)', fontSize: 12.5, lineHeight: 1.6, color: 'var(--text)', overflow: 'auto', maxHeight: 420, whiteSpace: 'pre' }}>{s.source}</pre>
        : <div style={{ color: 'var(--faint)', fontSize: 13 }}>Source was not recorded for this submission.</div>}
    </div>
  );
}

// ---------------------------------------------------- STUDENT · SOLVE -------
function Solve({ assignmentId, meta, onBack }) {
  const starter = meta?.starter || '';
  const [src, setSrc] = useState(starter);
  const [busy, setBusy] = useState(false);
  const [res, setRes] = useState(null);
  const [err, setErr] = useState(null);
  const [last, setLast] = useState(undefined); // undefined=loading · null=none · {…}=have one
  const [origin, setOrigin] = useState('starter'); // which starting point is in the editor
  const toast = useToast();

  // Offer "resume from last version": fetch the student's latest submission once.
  useEffect(() => {
    let live = true;
    api.myLastSubmission(assignmentId)
      .then((r) => { if (live) setLast(r.data.submission); })
      .catch(() => { if (live) setLast(null); });
    return () => { live = false; };
  }, [assignmentId]);

  // The two starting points. Each replaces the editor contents with one click.
  const pickStarter = () => { setSrc(starter); setOrigin('starter'); setRes(null); setErr(null); };
  const pickLast = () => {
    if (last && last.source != null) { setSrc(last.source); setOrigin('last'); setRes(null); setErr(null); }
  };

  const submit = async () => {
    setBusy(true); setErr(null);
    try {
      const r = await api.submit(assignmentId, src);
      setRes(r.data);
      // What's in the editor is now the saved "last version".
      setLast({ source: src, passed: r.data.passed, awarded: r.data.awardedPoints, total: r.data.totalPoints, at: null });
      setOrigin('last');
      toast(r.data.passed ? 'All tests passed — nice.' : 'Submission graded.', r.data.passed ? '' : 'err');
    } catch (e) {
      setErr(e);
      if (e.status === 401) toast('Session expired — please sign in.', 'err');
      else toast(e.error || 'Submission failed.', 'err');
    } finally { setBusy(false); }
  };

  const bytes = new Blob([src]).size;

  return (
    <div className="page page-wide fade-up solve-page">
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 20 }}>
        <button className="btn btn-ghost btn-sm" onClick={onBack}><Icon name="chevron" size={13} style={{ transform: 'rotate(180deg)' }} />Back</button>
        <div style={{ flex: 1 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 11 }}>
            <span style={{ fontFamily: 'var(--mono)', fontSize: 13, color: 'var(--faint)' }}>#{assignmentId}</span>
            <h1 style={{ margin: 0, fontSize: 22, fontWeight: 600, letterSpacing: '-0.3px' }}>{meta?.title || assignmentId}</h1>
          </div>
        </div>
      </div>

      {meta?.description && (
        <div className="panel panel-pad" style={{ marginBottom: 16, maxHeight: 260, overflow: 'auto', fontSize: 13.5, lineHeight: 1.7, color: 'var(--text)' }}>
          <Markdown text={meta.description} />
        </div>
      )}

      {/* starting point: the starter template, or resume from your last submission */}
      <div className="panel panel-pad" style={{ marginBottom: 16, display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
        <span style={{ fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: 1, textTransform: 'uppercase', color: 'var(--faint)', marginRight: 2 }}>Start from</span>
        <button className={`btn btn-sm ${origin === 'starter' ? 'btn-primary' : 'btn-ghost'}`} onClick={pickStarter} disabled={busy}>
          <Icon name="file" size={13} />Starter template
        </button>
        <button className={`btn btn-sm ${origin === 'last' ? 'btn-primary' : 'btn-ghost'}`} onClick={pickLast}
          disabled={busy || !(last && last.source != null)}
          title={last && last.source != null ? 'Load your last submitted code' : 'No previous submission yet'}>
          <Icon name="code" size={13} />
          {last === undefined ? 'Last version…'
            : last && last.source != null ? <>Your last version · {last.awarded}/{last.total}</>
            : 'No previous submission'}
        </button>
        <span style={{ flex: 1 }} />
        <span style={{ fontSize: 12, color: 'var(--faint)' }}>Picking one replaces the editor contents.</span>
      </div>

      <div className="solve-grid">
        {/* editor column */}
        <div className="solve-edcol">
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 4px 10px' }}>
            <span style={{ fontFamily: 'var(--mono)', fontSize: 12, color: 'var(--muted)', display: 'flex', alignItems: 'center', gap: 8 }}>
              <Icon name="code" size={14} />pseudocode.igcse
            </span>
            <span style={{ fontFamily: 'var(--mono)', fontSize: 11.5, color: bytes > 64*1024 ? 'var(--fail)' : 'var(--faint)' }}>
              {(bytes/1024).toFixed(1)} KB / 64 KB
            </span>
          </div>
          <div style={{ flex: 1, minHeight: 0 }}>
            <CodeEditor value={src} onChange={setSrc} disabled={busy} />
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginTop: 14 }}>
            <button className="btn btn-primary" onClick={submit} disabled={busy || !src.trim()}>
              {busy ? <Spinner>Grading</Spinner> : <><Icon name="play" size={13} />Submit for grading</>}
            </button>
            <button className="btn btn-ghost" onClick={() => { setSrc(starter); setRes(null); setErr(null); }} disabled={busy}>Reset</button>
            <span style={{ fontFamily: 'var(--mono)', fontSize: 11.5, color: 'var(--faint)', marginLeft: 'auto' }}>one submission at a time</span>
          </div>
        </div>

        {/* result column */}
        <div className="panel solve-rescol">
          <div style={{ padding: '15px 20px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 9, fontFamily: 'var(--mono)', fontSize: 12, color: 'var(--muted)' }}>
            <span style={{ width: 7, height: 7, borderRadius: 99, background: busy ? 'var(--warn)' : (res ? (res.passed ? 'var(--acc)' : 'var(--fail)') : 'var(--line-2)'), boxShadow: busy ? '0 0 9px var(--warn)' : 'none' }}></span>
            RESULT
          </div>
          <div style={{ flex: 1, overflow: 'auto', padding: 20 }}>
            {busy ? <ResultGrading /> :
             err ? <ResultError err={err} /> :
             res ? <ResultPanel res={res} /> :
             <ResultIdle />}
          </div>
        </div>
      </div>
    </div>
  );
}

function ResultIdle() {
  return (
    <div style={{ color: 'var(--muted)', fontSize: 14, lineHeight: 1.6 }}>
      <div style={{ display: 'inline-grid', placeItems: 'center', width: 44, height: 44, borderRadius: 11, border: '1px solid var(--line)', color: 'var(--faint)', marginBottom: 16 }}>
        <Icon name="play" size={16} />
      </div>
      <p style={{ margin: 0, color: 'var(--text)', fontWeight: 600 }}>Awaiting submission</p>
      <p style={{ marginTop: 8 }}>Write your IGCSE pseudocode, then hit <span className="kbd">Submit</span>. You'll see a pass/fail for each sample case and a count of the hidden ones.</p>
    </div>
  );
}
function ResultGrading() {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 14, color: 'var(--muted)' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 11, color: 'var(--warn)', fontWeight: 600 }}><span className="spin"></span>Running test cases…</div>
      {[0,1,2].map((i) => (
        <div key={i} style={{ height: 44, borderRadius: 9, background: 'linear-gradient(90deg, var(--panel-2), var(--panel-3), var(--panel-2))', backgroundSize: '200% 100%', animation: `shimmer 1.3s ${i*0.15}s linear infinite` }} />
      ))}
      <style>{`@keyframes shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }`}</style>
    </div>
  );
}
function ResultError({ err }) {
  const map = { 401: 'Session expired', 403: 'No access', 404: 'Not found', 413: 'Program too long', 429: 'Rate limited', 503: 'Server busy', 500: 'Server error' };
  return (
    <div className="fade-up">
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, color: 'var(--fail)', fontWeight: 600, marginBottom: 12 }}>
        <Icon name="x" size={16} stroke={2.2} />{map[err.status] || 'Request rejected'} · {err.status}
      </div>
      <div style={{ fontFamily: 'var(--mono)', fontSize: 13, color: 'var(--muted)', background: 'oklch(0.68 0.205 22 / 0.08)', border: '1px solid oklch(0.68 0.205 22 / 0.3)', borderRadius: 9, padding: '12px 14px' }}>
        {err.error}
      </div>
      <p style={{ color: 'var(--faint)', fontSize: 13, marginTop: 14 }}>This is a <b style={{ color: 'var(--muted)' }}>request</b> error — your program wasn't run. A broken program returns 200 with failing cases instead.</p>
    </div>
  );
}
function ResultPanel({ res }) {
  return (
    <div className="fade-up">
      <div style={{ display: 'flex', alignItems: 'center', gap: 20, marginBottom: 22 }}>
        <ScoreRing value={res.awardedPoints} total={res.totalPoints} passed={res.passed} />
        <div>
          <PassBadge passed={res.passed} />
          <div style={{ fontSize: 14, color: 'var(--muted)', marginTop: 12, lineHeight: 1.5 }}>
            {res.passed ? 'Every case passed.' : `${res.awardedPoints} of ${res.totalPoints} points awarded.`}
          </div>
        </div>
      </div>

      <div style={{ fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: 1, textTransform: 'uppercase', color: 'var(--faint)', margin: '0 0 11px' }}>Sample cases</div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        {res.cases.map((c, i) => (
          <div key={i} style={{ display: 'flex', alignItems: 'flex-start', gap: 12, padding: '11px 13px', borderRadius: 9, border: '1px solid var(--line)', background: c.passed ? 'oklch(0.88 0.19 138 / 0.06)' : 'oklch(0.68 0.205 22 / 0.05)' }}>
            <span style={{ marginTop: 1, color: c.passed ? 'var(--acc)' : 'var(--fail)', display: 'inline-flex' }}>
              <Icon name={c.passed ? 'check' : 'x'} size={15} stroke={2.4} />
            </span>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontFamily: 'var(--mono)', fontSize: 13.5, color: 'var(--text)' }}>{c.name}</div>
              {c.stdin && c.stdin.length > 0 && (
                <div style={{ marginTop: 5, display: 'flex', flexWrap: 'wrap', gap: 5, alignItems: 'center' }}>
                  <span style={{ fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--faint)' }}>input</span>
                  {c.stdin.map((v, k) => <code key={k} style={mdCodeStyle}>{v}</code>)}
                </div>
              )}
              {c.expected != null && (
                <div style={{ marginTop: 7 }}>
                  <div style={{ fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--faint)', marginBottom: 4 }}>expected output</div>
                  <pre style={{ margin: 0, padding: '8px 10px', background: 'var(--bg-2)', border: '1px solid var(--line)', borderRadius: 7, fontFamily: 'var(--mono)', fontSize: 12, lineHeight: 1.5, color: 'var(--text)', whiteSpace: 'pre', overflow: 'auto' }}>{c.expected}</pre>
                </div>
              )}
              {c.error && <div style={{ fontFamily: 'var(--mono)', fontSize: 12, color: 'var(--fail)', marginTop: 5 }}>{c.error}</div>}
            </div>
            <span style={{ fontFamily: 'var(--mono)', fontSize: 11.5, color: c.passed ? 'var(--acc)' : 'var(--fail)' }}>{c.passed ? 'PASS' : 'FAIL'}</span>
          </div>
        ))}
      </div>

      <div style={{ marginTop: 14, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '12px 14px', borderRadius: 9, border: '1px dashed var(--line)', background: 'oklch(1 0 0 / 0.015)' }}>
        <span style={{ display: 'flex', alignItems: 'center', gap: 9, fontSize: 13.5, color: 'var(--muted)' }}><Icon name="lock" size={13} />Hidden tests</span>
        <span style={{ fontFamily: 'var(--mono)', fontSize: 13.5, color: res.hidden.passed === res.hidden.total ? 'var(--acc)' : 'var(--muted)' }}>{res.hidden.passed} / {res.hidden.total} passed</span>
      </div>
    </div>
  );
}

// ---------------------------------------------------- STUDENT · RESULTS -----
function MyResults() {
  const [rows, setRows] = useState(null);
  const [titles, setTitles] = useState({});
  useEffect(() => {
    api.myResults().then((r) => setRows(r.data.results));
    api.assignments().then((r) => setTitles(Object.fromEntries(r.data.assignments.map((a) => [a.id, a.title]))));
  }, []);
  const titleOf = (id) => titles[id] || id;
  return (
    <div className="page fade-up">
      <div className="page-head">
        <p className="eyebrow">Report card</p>
        <h1 className="page-title">My results</h1>
        <p className="page-sub">One row per assignment you've attempted. <b style={{ color: 'var(--text)' }}>Best</b> is your highest score across attempts.</p>
      </div>
      {!rows ? <Loading label="Loading report card" /> :
        rows.length === 0 ? <Empty icon="book" title="No attempts yet" sub="Submit a problem and it'll show up here." /> :
        <div className="tbl-wrap">
          <table className="tbl">
            <thead><tr><th>Assignment</th><th>Status</th><th>Attempts</th><th>Best</th><th>Last submitted (UTC+8)</th></tr></thead>
            <tbody>
              {rows.map((r) => (
                <tr key={r.assignmentId}>
                  <td><div style={{ fontWeight: 500 }}>{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>
                </tr>
              ))}
            </tbody>
          </table>
        </div>}
    </div>
  );
}

Object.assign(window, { Login, StudentHome, Solve, MyResults, SourceView });
