/* ==================================================================
   Admin → Teachers
   ------------------------------------------------------------------
   The cockpit is the admin panel. Teachers themselves use the mobile
   app. This screen lets the admin add a teacher, assign them to a
   set of (class × subject) pairs (controls which tests they can
   author + grade on mobile), reset passwords, and deactivate (which
   immediately revokes any live mobile session).
   ================================================================== */

const { Icon: AdIcon } = window.AppUI;

const ROLE_LABEL = { admin: "Admin", hod: "HoD", teacher: "Teacher", coordinator: "Coordinator", boarder: "Boarder", support_staff: "Support Staff" };

const TeachersScreen = () => {
  const [teachers, setTeachers] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [err, setErr] = React.useState(null);
  const [selectedId, setSelectedId] = React.useState(null);
  const [addOpen, setAddOpen] = React.useState(false);
  const [credentialFlash, setCredentialFlash] = React.useState(null); // { username, temp_password }
  const [search, setSearch] = React.useState("");
  const [resets, setResets] = React.useState([]);          // pending password-reset requests
  const [actBusy, setActBusy] = React.useState(null);      // id currently being approved/rejected

  const refresh = React.useCallback(async () => {
    setLoading(true); setErr(null);
    try {
      const [t, pr] = await Promise.all([
        window.AppApi.get("/admin/teachers"),
        window.AppApi.get("/password-resets?status=pending").catch(() => []),
      ]);
      setTeachers(t); setResets(pr || []);
    }
    catch (e) { setErr(String(e.message || e)); }
    finally { setLoading(false); }
  }, []);

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

  // Self-registered teachers awaiting approval are surfaced in a dedicated card
  // above the roster (and kept OUT of the main list until activated).
  const pendingUsers = teachers.filter(t => t.status === "pending");
  const activeUsers  = teachers.filter(t => t.status !== "pending");

  const approveTeacher = async (id) => {
    setActBusy(id);
    try { await window.AppApi.post(`/admin/teachers/${id}/approve`, {}); await refresh(); }
    catch (e) { setErr(String(e.message || e)); }
    finally { setActBusy(null); }
  };
  const rejectTeacher = async (id, name) => {
    if (!window.confirm(`Reject and delete the registration for ${name || "this teacher"}?`)) return;
    setActBusy(id);
    try { await window.AppApi.post(`/admin/teachers/${id}/reject`, {}); if (selectedId === id) setSelectedId(null); await refresh(); }
    catch (e) { setErr(String(e.message || e)); }
    finally { setActBusy(null); }
  };
  const decideReset = async (id, kind) => {
    setActBusy(id);
    try { await window.AppApi.post(`/password-resets/${id}/${kind}`, {}); await refresh(); }
    catch (e) { setErr(String(e.message || e)); }
    finally { setActBusy(null); }
  };

  const filtered = activeUsers.filter(t => {
    if (!search) return true;
    const q = search.toLowerCase();
    return (t.name || "").toLowerCase().includes(q)
        || (t.email || "").toLowerCase().includes(q)
        || (t.username || "").toLowerCase().includes(q);
  });

  const selected = selectedId ? teachers.find(t => t.id === selectedId) : null;

  const AdBadge = window.AppUI.StatusBadge;
  return (
    <div className={`cx-roster-split${selected ? " has-detail" : ""}`}>
      <div className="cx-page" style={{ minWidth: 0, width: "100%" }}>
        <window.AppUI.PageHeader
          eyebrow="Admin · access control"
          title="Teachers"
          meta={["Each teacher logs in to the mobile app and only sees tests for the (class × subject) pairs you assign here. Deactivating a teacher revokes their open sessions immediately."]}
          actions={
            <button className="btn primary" onClick={() => setAddOpen(true)}>
              <AdIcon name="userPlus" size={15}/>Add teacher
            </button>
          }
        />

        {credentialFlash && (
          <div className="ui-alert green" style={{ marginBottom: 20, display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12 }}>
            <div style={{ display: "flex", gap: 10, minWidth: 0 }}>
              <AdIcon name="key" size={16} style={{ marginTop: 2, flexShrink: 0 }}/>
              <div style={{ minWidth: 0 }}>
                <div style={{ fontWeight: 600, marginBottom: 4 }}>Credentials issued — copy now, won't be shown again</div>
                <div className="mono" style={{ fontSize: 13, color: "var(--ink-0)" }}>
                  Username: <b>{credentialFlash.username}</b>
                  &nbsp;&nbsp;·&nbsp;&nbsp;
                  Password: <b>{credentialFlash.temp_password}</b>
                </div>
              </div>
            </div>
            <window.AppUI.IconButton icon="x" label="Dismiss" className="sm" onClick={() => setCredentialFlash(null)}/>
          </div>
        )}

        {pendingUsers.length > 0 && (
          <div className="cx-card" style={{ marginBottom: 20, borderColor: "color-mix(in srgb, var(--amber-solid) 45%, var(--line))" }}>
            <div className="cx-card-head" style={{ marginBottom: 4 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                <div className="cx-card-title">Pending approval</div>
                <AdBadge tone="amber" variant="outline">{pendingUsers.length} waiting</AdBadge>
              </div>
              <span className="muted" style={{ fontSize: 12.5 }}>Self-registered from the mobile app</span>
            </div>
            <div className="ui-list">
              {pendingUsers.map(t => (
                <div key={t.id} className="ui-row cx-tchqueue" style={{ gap: 12 }}>
                  <div style={{ cursor: "pointer", display: "flex", alignItems: "center", gap: 12, minWidth: 0 }} onClick={() => setSelectedId(t.id)}>
                    <window.AppUI.Avatar name={t.name}/>
                    <div style={{ minWidth: 0 }}>
                      <div className="ui-row-title" style={{ display: "flex", alignItems: "center", gap: 8 }}>
                        {t.name}
                        {t.boarding && <AdBadge tone="blue">Boarding</AdBadge>}
                      </div>
                      <div className="ui-row-sub">{t.email} · {t.phone || "no phone"}</div>
                    </div>
                  </div>
                  <div style={{ fontSize: 13, color: "var(--ink-2)" }}>{ROLE_LABEL[t.role] || t.role}</div>
                  <div className="muted tabular" style={{ fontSize: 13 }}>{t.assignment_count} {t.assignment_count === 1 ? "scope" : "scopes"}</div>
                  <div style={{ display: "flex", gap: 6, justifyContent: "flex-end" }}>
                    <button className="btn sm" disabled={actBusy === t.id} onClick={() => rejectTeacher(t.id, t.name)}>
                      <AdIcon name="xCircle" size={14}/>Reject
                    </button>
                    <button className="btn sm primary" disabled={actBusy === t.id} onClick={() => approveTeacher(t.id)}>
                      <AdIcon name="check" size={14}/>Approve
                    </button>
                  </div>
                </div>
              ))}
            </div>
          </div>
        )}

        {resets.length > 0 && (
          <div className="cx-card" style={{ marginBottom: 20 }}>
            <div className="cx-card-head" style={{ marginBottom: 4 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                <div className="cx-card-title">Password reset requests</div>
                <AdBadge tone="blue" variant="outline">{resets.length}</AdBadge>
              </div>
              <span className="muted" style={{ fontSize: 12.5 }}>Approving renews the password &amp; signs them out</span>
            </div>
            <div className="ui-list">
              {resets.map(r => (
                <div key={r.id} className="ui-row cx-tchqueue reset" style={{ gap: 12 }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 12, minWidth: 0 }}>
                    <window.AppUI.Avatar name={r.teacher.name}/>
                    <div style={{ minWidth: 0 }}>
                      <div className="ui-row-title">{r.teacher.name}</div>
                      <div className="ui-row-sub tabular">{r.teacher.phone || r.teacher.email}</div>
                    </div>
                  </div>
                  <div style={{ fontSize: 13, color: "var(--ink-2)" }}>{ROLE_LABEL[r.teacher.role] || r.teacher.role}</div>
                  <div style={{ display: "flex", gap: 6, justifyContent: "flex-end" }}>
                    <button className="btn sm" disabled={actBusy === r.id} onClick={() => decideReset(r.id, "reject")}>
                      <AdIcon name="xCircle" size={14}/>Reject
                    </button>
                    <button className="btn sm primary" disabled={actBusy === r.id} onClick={() => decideReset(r.id, "approve")}>
                      <AdIcon name="check" size={14}/>Approve
                    </button>
                  </div>
                </div>
              ))}
            </div>
          </div>
        )}

        <div className="cx-tablecard">
          <div className="cx-tablecard-head" style={{ display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
            <span className="cx-tablecard-title">{activeUsers.length} {activeUsers.length === 1 ? "user" : "users"}</span>
            <div className="ui-search" style={{ flex: 1, minWidth: 200, marginLeft: "auto", maxWidth: 360 }}>
              <AdIcon name="search" size={14}/>
              <input className="input" placeholder="Search name, email, username…" value={search} onChange={e => setSearch(e.target.value)}/>
            </div>
          </div>

          {/* Column header for the hairline rows below */}
          {!loading && filtered.length > 0 && window.matchMedia("(min-width: 761px)").matches && (
            <div className="cx-tchrow ui-section-label" style={{ padding: "10px 20px", gap: 12, borderBottom: "1px solid var(--line)", margin: 0 }}>
              <span>Name</span><span>Username</span><span>Role</span><span>Scope</span><span style={{ textAlign: "right" }}>Session</span>
            </div>
          )}

          {loading && <div style={{ padding: 28, color: "var(--ink-3)", fontSize: 13 }}>Loading…</div>}
          {err && <div className="ui-alert red" style={{ margin: 16 }}>{err}</div>}
          {!loading && filtered.length === 0 && (
            <window.AppUI.EmptyState compact icon="users"
              title={teachers.length === 0 ? "No teachers yet" : "No matches"}
              hint={teachers.length === 0 ? "Click Add teacher to create one." : null}/>
          )}

          {filtered.map(t => (
            <div key={t.id}
              onClick={() => setSelectedId(t.id)}
              className="cx-tchrow ui-row clickable"
              style={{
                padding: "12px 20px", gap: 12,
                background: selectedId === t.id ? "var(--surface-active)" : undefined,
              }}>
              <div style={{ display: "flex", alignItems: "center", gap: 12, minWidth: 0 }}>
                <window.AppUI.Avatar name={t.name}/>
                <div style={{ minWidth: 0 }}>
                  <div className="ui-row-title" style={{ display: "flex", alignItems: "center", gap: "2px 8px", flexWrap: "wrap", whiteSpace: "normal" }}>
                    <span style={{ overflow: "hidden", textOverflow: "ellipsis" }}>{t.name}</span>
                    {!t.active && <AdBadge tone="grey">Disabled</AdBadge>}
                    {t.must_reset && t.active && <AdBadge tone="amber">Reset pending</AdBadge>}
                  </div>
                  <div className="ui-row-sub">{t.email}</div>
                </div>
              </div>
              <div className="ui-id" style={{ overflow: "hidden", textOverflow: "ellipsis" }}>{t.username || "—"}</div>
              <div style={{ fontSize: 13, color: t.role === "admin" ? "var(--accent)" : "var(--ink-2)", fontWeight: t.role === "admin" ? 500 : 400 }}>{ROLE_LABEL[t.role] || t.role}</div>
              <div className="muted tabular" style={{ fontSize: 13 }}>
                {t.assignment_count} {t.assignment_count === 1 ? "scope" : "scopes"}
              </div>
              <div style={{ textAlign: "right" }}>
                {t.active_sessions > 0 ? <AdBadge tone="green" variant="solid" icon={false}>Live</AdBadge> : <span className="muted">—</span>}
              </div>
            </div>
          ))}
        </div>
      </div>

      {selected && (
        <TeacherDetail
          key={selected.id}
          teacherSummary={selected}
          onClose={() => setSelectedId(null)}
          onChanged={async (flashCreds) => { if (flashCreds) setCredentialFlash(flashCreds); await refresh(); }}
        />
      )}

      <AddTeacherModal
        open={addOpen}
        onClose={() => setAddOpen(false)}
        onCreated={async (creds) => { setAddOpen(false); setCredentialFlash(creds); await refresh(); setSelectedId(creds.id); }}
      />
    </div>
  );
};

/* ---------- Right-hand detail panel ---------- */
const TeacherDetail = ({ teacherSummary, onClose, onChanged }) => {
  // { url, expires_in_minutes } — shown once, right under the button that made
  // it. The server stores only a hash, so there is no way to re-read it later.
  const [magicLink, setMagicLink] = React.useState(null);
  const [copied, setCopied] = React.useState(false);
  const [full, setFull] = React.useState(null);
  const [classSections, setClassSections] = React.useState([]);
  const [subjects, setSubjects] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(null);

  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      setLoading(true);
      try {
        const [t, cs, sb] = await Promise.all([
          window.AppApi.get(`/admin/teachers/${teacherSummary.id}`),
          window.AppApi.get("/admin/class-sections"),
          window.AppApi.get("/admin/subjects"),
        ]);
        if (cancelled) return;
        setFull(t); setClassSections(cs); setSubjects(sb);
      } catch (e) { if (!cancelled) setErr(String(e.message || e)); }
      finally { if (!cancelled) setLoading(false); }
    })();
    return () => { cancelled = true; };
  }, [teacherSummary.id]);

  // selected set, keyed "class_section_id::subject_id"
  const [selected, setSelected] = React.useState(new Set());
  React.useEffect(() => {
    if (full) setSelected(new Set(full.assignments.map(a => `${a.class_section_id}::${a.subject_id}`)));
  }, [full]);

  const toggle = (csId, sbId) => {
    const key = `${csId}::${sbId}`;
    setSelected(prev => {
      const next = new Set(prev);
      if (next.has(key)) next.delete(key); else next.add(key);
      return next;
    });
  };

  const saveAssignments = async () => {
    setBusy(true); setErr(null);
    try {
      const assignments = Array.from(selected).map(k => {
        const [class_section_id, subject_id] = k.split("::");
        return { class_section_id, subject_id };
      });
      await window.AppApi.put(`/admin/teachers/${teacherSummary.id}/assignments`, { assignments });
      await onChanged();
    } catch (e) { setErr(String(e.message || e)); }
    finally { setBusy(false); }
  };

  const patch = async (body) => {
    setBusy(true); setErr(null);
    try {
      await window.AppApi.patch(`/admin/teachers/${teacherSummary.id}`, body);
      await onChanged();
    } catch (e) { setErr(String(e.message || e)); }
    finally { setBusy(false); }
  };

  const resetPassword = async () => {
    if (!window.confirm("Generate a new temporary password? Existing mobile sessions will be revoked.")) return;
    setBusy(true); setErr(null);
    try {
      const out = await window.AppApi.post(`/admin/teachers/${teacherSummary.id}/reset-password`, {});
      await onChanged({ id: teacherSummary.id, username: out.username, temp_password: out.temp_password });
    } catch (e) { setErr(String(e.message || e)); }
    finally { setBusy(false); }
  };

  const sendMagicLink = async (target) => {
    setBusy(true); setErr(null); setCopied(false);
    try {
      const out = await window.AppApi.post(`/admin/teachers/${teacherSummary.id}/magic-link`, { target });
      setMagicLink(out);
    } catch (e) { setErr(String(e.message || e)); }
    finally { setBusy(false); }
  };

  const copyMagicLink = async () => {
    try {
      await navigator.clipboard.writeText(magicLink.url);
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch { /* clipboard blocked — the input below is selectable as a fallback */ }
  };

  const removeTeacher = async () => {
    if (!window.confirm(
      `Remove ${teacherSummary.name}?\n\n` +
      `If they've never authored a test, review, or rubric, the account is deleted outright. ` +
      `Otherwise it's deactivated and the login is dropped — past work they own is preserved.`
    )) return;
    setBusy(true); setErr(null);
    try {
      const out = await window.AppApi.del(`/admin/teachers/${teacherSummary.id}`);

      // Soft-delete branch: backend tells us what's blocking. If the only
      // blocker is the teacher's own tests/submissions, offer a second
      // confirm that escalates to ?cascade=tests and nukes the lot.
      if (out && out.removed === "soft") {
        const b = out.blocked_by || {};
        const testsCreated = Number(b.tests_created || 0);
        const subs         = Number(b.submissions   || 0);
        const otherReviews = Number(b.reviews       || 0);
        const offersCascade = testsCreated > 0;

        if (offersCascade && window.confirm(
          `${teacherSummary.name} authored ${testsCreated} test${testsCreated === 1 ? "" : "s"} ` +
          `with ${subs} student submission${subs === 1 ? "" : "s"}.\n\n` +
          `Delete the teacher's tests AND every submission underneath them, then remove ` +
          `the account?\n\nThis can't be undone.`
        )) {
          const cascade = await window.AppApi.del(`/admin/teachers/${teacherSummary.id}?cascade=tests`);
          await onChanged();
          onClose();
          if (cascade?.removed === "hard") {
            window.alert(
              `Removed ${teacherSummary.name}. ` +
              `Cleared ${cascade.cascaded?.tests ?? 0} test(s) and ` +
              `${cascade.cascaded?.submissions ?? 0} submission(s).`,
            );
          } else {
            // Still soft — they had reviews on OTHER teachers' work that
            // we won't touch (NOT NULL teacher_id, audit-trail material).
            window.alert(
              `${teacherSummary.name} also has ${cascade?.blocked_by?.reviews ?? otherReviews} ` +
              `review${(cascade?.blocked_by?.reviews ?? otherReviews) === 1 ? "" : "s"} on ` +
              `other teachers' work. The account was deactivated (login removed) but the ` +
              `user row stays as a tombstone so those reviews keep their author.`,
            );
          }
          return;
        }

        await onChanged();
        onClose();
        window.alert(
          `${teacherSummary.name} has work that's still referenced ` +
          `(${testsCreated} test(s), ${otherReviews} review(s) on others, etc.), so the ` +
          `account was deactivated instead of fully removed. Their login is gone and ` +
          `they can no longer sign in.`,
        );
        return;
      }

      // Hard delete on the first call — row just vanishes after refresh.
      await onChanged();
      onClose();
    } catch (e) { setErr(String(e.message || e)); setBusy(false); }
  };

  // Same reasoning as the student roster: while the pane is a bottom sheet the
  // 28-row teacher list behind it must not scroll, or a drag inside the sheet
  // moves the page underneath and the sheet appears to drift.
  useEffect(() => {
    const sheet = window.matchMedia("(max-width: 1000px)");
    if (!sheet.matches) return;
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    return () => {
      document.body.style.overflow = prev;
      window.removeEventListener("keydown", onKey);
    };
  }, [onClose]);

  return (
    <>
    <div className="cx-roster-scrim" onClick={onClose} aria-hidden="true"/>
    <aside className="cx-roster-detail">
      <div className="cx-sheet-grip" aria-hidden="true"/>
      <div className="cx-roster-detail-head" style={{ padding: "18px 20px", borderBottom: "1px solid var(--line)", display: "flex", alignItems: "center", gap: 12 }}>
        <window.AppUI.Avatar name={teacherSummary.name} size="lg"/>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ color: "var(--ink-0)", fontSize: 16, fontWeight: 600, display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
            {teacherSummary.name}
            {teacherSummary.active
              ? <window.AppUI.StatusBadge tone="green" variant="outline">Active</window.AppUI.StatusBadge>
              : <window.AppUI.StatusBadge tone="grey" variant="outline">Disabled</window.AppUI.StatusBadge>}
          </div>
          <div className="muted" style={{ fontSize: 12.5, marginTop: 2, overflow: "hidden", textOverflow: "ellipsis" }}>{teacherSummary.email}</div>
        </div>
        <window.AppUI.IconButton icon="x" label="Close" onClick={onClose}/>
      </div>

      {err && <div className="ui-alert red" style={{ margin: "14px 20px 0" }}>{err}</div>}

      {loading && <div style={{ padding: 28, color: "var(--ink-3)", fontSize: 13 }}>Loading…</div>}

      {!loading && full && (
        <>
          {/* Account block */}
          <section style={{ padding: "18px 20px", borderBottom: "1px solid var(--line)" }}>
            <div className="ui-section-label" style={{ marginBottom: 10 }}>Account</div>
            <EditableRow label="Name" value={full.name} onSave={v => patch({ name: v })} disabled={busy}/>
            <EditableRow label="Email" value={full.email} onSave={v => patch({ email: v })} disabled={busy}/>
            <RoleRow value={full.role} onSave={v => patch({ role: v })} disabled={busy}/>
            <AdDetailRow label="Username">
              <span className="ui-id">{full.username || "—"}</span>
            </AdDetailRow>
            {/* This number receives the sign-in code, so it's the one field
                that decides whether this person can get into the app at all. */}
            <EditableRow
              label="Phone" mono allowEmpty
              value={full.phone}
              placeholder="10-digit mobile"
              hint="Receives the sign-in code. Saved as +91…; leaving it blank disables code sign-in for this person."
              onSave={v => patch({ phone: v })}
              disabled={busy}/>
            <AdDetailRow label="Boarding">
              <span style={{ fontSize: 13.5, color: full.boarding ? "var(--accent)" : "var(--ink-1)" }}>{full.boarding ? "Yes" : "No"}</span>
            </AdDetailRow>
            <AdDetailRow label="Login">
              <label style={{ display: "inline-flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13.5 }}>
                <input type="checkbox" checked={full.active} disabled={busy}
                  onChange={e => patch({ active: e.target.checked })}/>
                <span>{full.active ? "Active" : "Disabled"}</span>
              </label>
            </AdDetailRow>
            <div style={{ display: "flex", gap: 6, marginTop: 14, flexWrap: "wrap" }}>
              {(full.magic_link_targets || [{ target: "teacher", label: "Teacher app" }]).map(t => (
                <button key={t.target} className="btn sm" disabled={busy}
                  onClick={() => sendMagicLink(t.target)}>
                  <AdIcon name="mail" size={14}/>Sign-in link · {t.label.replace(/ app$/, "")}
                </button>
              ))}
              <button className="btn sm" disabled={busy || !full.username} onClick={resetPassword}>
                <AdIcon name="key" size={14}/>Reset password
              </button>
              <button className="btn sm danger" disabled={busy} onClick={removeTeacher}>
                <AdIcon name="trash" size={14}/>Remove
              </button>
            </div>

            {magicLink && (
              <div className="ui-alert green" style={{ marginTop: 14 }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8, marginBottom: 8 }}>
                  <b>{magicLink.target_label} sign-in link — copy now, shown once</b>
                  <window.AppUI.IconButton icon="x" label="Dismiss" className="sm" onClick={() => setMagicLink(null)}/>
                </div>
                <div style={{ display: "flex", gap: 6 }}>
                  {/* readOnly input rather than plain text: it stays selectable
                      on desktop when the clipboard API is unavailable. */}
                  <input className="input mono" readOnly value={magicLink.url}
                    onFocus={e => e.target.select()}
                    style={{ flex: 1, minWidth: 0, fontSize: 12 }}/>
                  <button className="btn" onClick={copyMagicLink}>
                    <AdIcon name={copied ? "check" : "copy"} size={14}/>{copied ? "Copied" : "Copy"}
                  </button>
                </div>
                <div style={{ fontSize: 12, marginTop: 8, color: "var(--ink-2)" }}>
                  Works once, expires in {magicLink.expires_in_minutes} minutes, and signs
                  {" "}{teacherSummary.name} straight into the {magicLink.target_label}.
                  Sending another {magicLink.target_label} link cancels this one.
                </div>
              </div>
            )}
          </section>

          {/* Assignments matrix */}
          <section style={{ padding: "18px 20px" }}>
            <div className="ui-section-label" style={{ marginBottom: 6 }}>Question-authoring scope</div>
            <p className="muted" style={{ fontSize: 12.5, margin: "0 0 14px", lineHeight: 1.5 }}>
              Tick every (class × subject) this teacher can create tests for and grade. Mobile login filters tests by this matrix.
              {full.role === "admin" && <span style={{ color: "var(--accent)", display: "block", marginTop: 4 }}>Admins bypass scope and see every test.</span>}
            </p>
            {classSections.length === 0 && (
              <window.AppUI.EmptyState compact icon="grid" title="No class sections"
                hint="No class sections in this school yet. Add one (via the database / a future Classes screen) before assigning."/>
            )}
            {classSections.length > 0 && subjects.length > 0 && (
              <div className="cx-tablecard cx-tablescroll">
                <table className="cx-table compact">
                  <thead>
                    <tr>
                      <th style={{ position: "sticky", left: 0, background: "var(--surface)", zIndex: 1 }}>Class</th>
                      {subjects.map(s => (
                        <th key={s.id} style={{ whiteSpace: "nowrap", textAlign: "center" }}>{s.name}</th>
                      ))}
                    </tr>
                  </thead>
                  <tbody>
                    {classSections.map(cs => (
                      <tr key={cs.id}>
                        <td style={{ whiteSpace: "nowrap", position: "sticky", left: 0, background: "var(--surface)" }}>
                          <span style={{ fontWeight: 500 }}>{cs.label}</span> <span className="muted" style={{ fontSize: 11 }}>{cs.academic_year}</span>
                        </td>
                        {subjects.map(s => {
                          const key = `${cs.id}::${s.id}`;
                          return (
                            <td key={s.id} style={{ textAlign: "center" }}>
                              <input type="checkbox"
                                checked={selected.has(key)}
                                onChange={() => toggle(cs.id, s.id)}/>
                            </td>
                          );
                        })}
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            )}
            <div style={{ display: "flex", justifyContent: "flex-end", marginTop: 14 }}>
              <button className="btn primary" disabled={busy} onClick={saveAssignments}>
                <AdIcon name="check" size={15}/>{busy ? "Saving…" : "Save scope"}
              </button>
            </div>
          </section>
        </>
      )}
    </aside>
    </>
  );
};

/* ---------- Add-teacher modal ---------- */
const AddTeacherModal = ({ open, onClose, onCreated }) => {
  const [name, setName] = React.useState("");
  const [email, setEmail] = React.useState("");
  const [role, setRole] = React.useState("teacher");
  const [password, setPassword] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(null);

  React.useEffect(() => {
    if (!open) { setName(""); setEmail(""); setRole("teacher"); setPassword(""); setErr(null); }
  }, [open]);

  if (!open) return null;

  const submit = async (e) => {
    e?.preventDefault();
    if (!name.trim() || !email.trim()) return;
    setBusy(true); setErr(null);
    try {
      const body = { name: name.trim(), email: email.trim(), role };
      if (password.trim()) body.password = password.trim();
      const out = await window.AppApi.post("/admin/teachers", body);
      onCreated(out);
    } catch (e) { setErr(String(e.message || e)); }
    finally { setBusy(false); }
  };

  const canCreate = !busy && !!name.trim() && !!email.trim();
  return (
    <window.AppUI.Modal open onClose={onClose} title="Add teacher" icon="userPlus"
      sub="Credentials are shown once after creation."
      footer={<>
        <button type="button" className="btn" onClick={onClose} disabled={busy}>Cancel</button>
        <button type="button" className="btn primary" disabled={!canCreate} onClick={submit}>
          <AdIcon name="check" size={15}/>{busy ? "Creating…" : "Create teacher"}
        </button>
      </>}>
      <form onSubmit={submit}>
        {/* Both name and email are required (email is the unique key on
            the users table and seeds the username). Mark them with * so
            the disabled Create button isn't a mystery. */}
        <div className="field">
          <label className="label">Full name <span style={{ color: "var(--red)" }}>*</span></label>
          <input className="input" autoFocus value={name} onChange={e => setName(e.target.value)} style={{ width: "100%" }}/>
        </div>

        <div className="field">
          <label className="label">Email <span style={{ color: "var(--red)" }}>*</span></label>
          <input className="input" type="email" value={email} onChange={e => setEmail(e.target.value)}
            placeholder="teacher@example.com" style={{ width: "100%" }}/>
          <div className="hint">Required — becomes the unique account id and the source of the username.</div>
        </div>

        <div className="field">
          <label className="label">Role</label>
          <window.AppUI.Tabs full value={role} onChange={setRole} ariaLabel="Role"
            items={["teacher","hod","coordinator","support_staff","boarder","admin"].map(r => ({ value: r, label: ROLE_LABEL[r] }))}/>
        </div>

        <div className="field">
          <label className="label">Password (leave blank to auto-generate)</label>
          <input className="input" value={password} onChange={e => setPassword(e.target.value)}
            placeholder="random 8-char password" style={{ width: "100%" }}/>
          <div className="hint">Username is derived from the email local-part. You'll see the credentials once after creation — copy them then.</div>
        </div>

        {err && <div className="ui-alert red" style={{ marginTop: 10 }}>{err}</div>}

        {/* Surface why the button is greyed out so the admin doesn't tap
            into a wall. Reads "Add an email…" or "Add a name…" depending
            on what's missing. */}
        {(!name.trim() || !email.trim()) && !err && (
          <div className="ui-alert amber" style={{ marginTop: 10 }}>
            {!name.trim() && !email.trim()
              ? "Add a name and email to enable Create."
              : !name.trim() ? "Add a name to enable Create."
              : "Add an email to enable Create."}
          </div>
        )}
        {/* Enter-to-submit; the visible buttons live in the modal footer. */}
        <button type="submit" hidden disabled={!canCreate}/>
      </form>
    </window.AppUI.Modal>
  );
};

/* ---------- tiny editable row helpers ---------- */
const Row = ({ label, children }) => (
  <div style={{ display: "grid", gridTemplateColumns: "90px 1fr", gap: 10, alignItems: "center", padding: "4px 0", fontSize: 12 }}>
    <span className="muted" style={{ fontSize: 11 }}>{label}</span>
    <div>{children}</div>
  </div>
);

// Detail-pane row in the new feel: muted label column, value column, hairline.
// `Row` above is left as-is because modals/engine-config-modal.jsx uses it.
const AdDetailRow = ({ label, children }) => (
  <div style={{ display: "grid", gridTemplateColumns: "96px minmax(0, 1fr)", gap: 12, alignItems: "center", padding: "7px 0", fontSize: 13.5, borderBottom: "1px solid var(--line-soft)" }}>
    <span style={{ fontSize: 12.5, color: "var(--ink-3)" }}>{label}</span>
    <div style={{ minWidth: 0 }}>{children}</div>
  </div>
);

// `allowEmpty` lets a field be cleared (phone uses it — an admin has to be able
// to remove a wrong number). `hint` renders under the input; the phone row uses
// it to warn that clearing the number removes code sign-in. `mono` matches the
// read-only phone styling this replaced.
const EditableRow = ({ label, value, onSave, disabled, allowEmpty, hint, mono, placeholder }) => {
  const [editing, setEditing] = React.useState(false);
  const [draft, setDraft] = React.useState(value || "");
  React.useEffect(() => setDraft(value || ""), [value]);
  if (!editing) {
    return (
      <AdDetailRow label={label}>
        <div style={{ display: "flex", alignItems: "center", gap: 6, minWidth: 0 }}>
          <span className={mono ? "mono" : undefined}
            style={{ color: "var(--ink-0)", fontSize: mono ? 13 : undefined, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{value || "—"}</span>
          <button className="btn ghost xs icon-only" disabled={disabled} onClick={() => setEditing(true)}
            title={`Edit ${String(label).toLowerCase()}`} aria-label={`Edit ${String(label).toLowerCase()}`}>
            <AdIcon name="edit" size={13}/>
          </button>
        </div>
      </AdDetailRow>
    );
  }
  const trimmed = draft.trim();
  const unchanged = trimmed === (value || "");
  const canSave = !disabled && !unchanged && (allowEmpty || !!trimmed);
  return (
    <AdDetailRow label={label}>
      <div style={{ flex: 1 }}>
        <div style={{ display: "flex", gap: 6 }}>
          <input className="input" value={draft} placeholder={placeholder || ""}
            onChange={e => setDraft(e.target.value)} style={{ flex: 1 }}/>
          <button className="btn sm primary" disabled={!canSave}
            onClick={() => { onSave(trimmed); setEditing(false); }}>Save</button>
          <button className="btn sm ghost" onClick={() => { setDraft(value || ""); setEditing(false); }}>Cancel</button>
        </div>
        {hint && <div className="hint" style={{ marginTop: 6 }}>{hint}</div>}
      </div>
    </AdDetailRow>
  );
};

const RoleRow = ({ value, onSave, disabled }) => (
  <AdDetailRow label="Role">
    <select className="select" value={value} disabled={disabled}
      onChange={e => onSave(e.target.value)}
      style={{ height: 30 }}>
      <option value="teacher">Teacher</option>
      <option value="hod">HoD</option>
      <option value="coordinator">Coordinator</option>
      <option value="support_staff">Support Staff</option>
      <option value="boarder">Boarder</option>
      <option value="admin">Admin</option>
    </select>
  </AdDetailRow>
);

window.TeachersScreen = TeachersScreen;
