/* blueyetutor — /students
 * Student CRUD + per-student tabs:
 *   - 부여한 문제집 — generated workbooks recorded against this student
 *   - 오답노트 만들기 — pick wrong problems from a filtered grid, save,
 *                       then generate a 오답노트 PDF (kind="pick" + --review)
 *
 * Assumed endpoints:
 *   GET    /api/students                          → Student[]
 *   POST   /api/students { name }
 *   DELETE /api/students/{id}
 *   GET    /api/students/{id}/wrong               → problem_id[]
 *   PUT    /api/students/{id}/wrong { ids[] }
 *   GET    /api/students/{id}/workbooks           → StudentWb[]
 *   POST   /api/students/{id}/workbooks/review { problem_ids[] }
 */
const { useState: stUseState, useMemo: stUseMemo, useEffect: stUseEffect } = React;

function StudentsPage() {
  const store = useStore();
  const students = store.students;
  const initialTab = (typeof hashQueryParam === "function" && hashQueryParam("tab")) || "manage";
  const [topTab, setTopTab] = stUseState(initialTab === "assigned" ? "assigned" : "manage");
  const [selId, setSelId] = stUseState(students[0] ? students[0].id : null);
  stUseEffect(() => {
    if (!students.find(s => s.id === selId) && students[0]) setSelId(students[0].id);
  }, [students]);

  // 배정 list 는 teacher 측에서 별도 fetch 가 필요. 기존 Assignments 페이지가
  // 진입 시 fetchAssignments() 했었는데, 합쳐졌으니 여기서 마운트 시 한 번.
  stUseEffect(() => { window.storeActions.fetchAssignments(); }, []);

  return (
    <div className="page col" style={{ gap: 20 }}>
      <div className="page-header">
        <div className="eyebrow">Students</div>
        <h1>학생 관리·배정 현황·분석을 한 곳에서</h1>
      </div>

      <Tabs
        tabs={[
          { value: "manage",   label: "학생 관리" },
          { value: "assigned", label: `배정 history (${(store.assignments || []).length})` },
        ]}
        value={topTab} onChange={setTopTab}
      />

      {topTab === "assigned" ? (
        <StudentsAssignmentsAll />
      ) : (
        <div className="row" style={{ gap: 16, alignItems: "flex-start", flexWrap: "wrap" }}>
          {/* ---- Left: roster ---- */}
          <div style={{ width: 280, maxWidth: "100%" }}>
            <Card title="학생" actions={<StudentAddBtn />}>
              {students.length === 0 ? (
                <div className="muted small">등록된 학생 없음</div>
              ) : (
                <div className="col" style={{ gap: 2 }}>
                  {students.map(s => {
                    const active = s.id === selId;
                    return (
                      <button key={s.id}
                              className="student-row"
                              data-active={active ? "true" : undefined}
                              onClick={() => setSelId(s.id)}>
                        <div style={{ flex: 1, minWidth: 0 }}>
                          <div className="name">{s.name}</div>
                          <div className="small muted">
                            {s.email ? <span className="mono">{s.email}</span> : <span style={{ opacity: 0.6 }}>이메일 미등록</span>}
                            {" · "}오답 {s.n_wrong} · 문제집 {s.n_wb}
                          </div>
                        </div>
                      </button>
                    );
                  })}
                </div>
              )}
            </Card>
          </div>

          {/* ---- Right: student detail ---- */}
          <div className="grow">
            {students.length === 0
              ? <EmptyState title="학생을 추가하세요" hint="좌측의 '추가' 버튼으로 시작합니다." />
              : selId && <StudentDetail student={students.find(s => s.id === selId)} />
            }
          </div>
        </div>
      )}
    </div>
  );
}

// 전체 배정 history — 옛 #/assignments (teacher 쪽 read-only 현황) 와 동일 내용.
// TeacherAssignmentRow 는 Assignments.jsx 에서 window 전역으로 노출됨.
function StudentsAssignmentsAll() {
  const store = useStore();
  const list = store.assignments || [];
  if (list.length === 0) {
    return <EmptyState
      title="배정한 과제가 없습니다"
      hint="학생 관리 탭에서 '문제집 배정' 또는 Workbook history 에서 '학생 배정' 버튼으로 보내세요." />;
  }
  return (
    <div className="col" style={{ gap: 12 }}>
      {list.map(a => <TeacherAssignmentRow key={a.id} a={a} />)}
    </div>
  );
}

function StudentAddBtn() {
  const [open, setOpen] = stUseState(false);
  const [name, setName] = stUseState("");
  const [email, setEmail] = stUseState("");
  const [busy, setBusy] = stUseState(false);
  const [msg, setMsg] = stUseState(null);   // {ok, text}
  const emailOk = !email.trim() || /\S+@\S+\.\S+/.test(email.trim());

  const submit = async () => {
    setBusy(true); setMsg(null);
    const r = await window.storeActions.addStudent(name.trim(), email.trim().toLowerCase());
    setBusy(false);
    if (r && r.ok) {
      setMsg({ ok: true, text: email.trim()
        ? `추가 완료 — 학생이 ${email.trim().toLowerCase()} 로 '학생' 가입하면 자동 연동되고, 이 이메일로 과제 배정이 됩니다.`
        : "추가 완료 (이메일 미등록 — 나중에 다시 추가하면 이메일을 채울 수 있어요)." });
      setName(""); setEmail("");
    } else {
      setMsg({ ok: false, text: (r && r.message) || "추가에 실패했습니다." });
    }
  };

  return (
    <>
      <Button size="sm" variant="primary" onClick={() => { setMsg(null); setOpen(true); }}><Icon.Plus /> 추가</Button>
      <Modal open={open} title="학생 추가" onClose={() => setOpen(false)}
        footer={
          <>
            <Button variant="ghost" onClick={() => setOpen(false)}>닫기</Button>
            <Button variant="primary" disabled={!name.trim() || !emailOk || busy} onClick={submit}>
              {busy ? "추가 중…" : "추가"}
            </Button>
          </>
        }>
        <div className="col" style={{ gap: 12 }}>
          <Field label="이름">
            <input className="input" autoFocus value={name} onChange={e => setName(e.target.value)} placeholder="학생 이름" />
          </Field>
          <Field label="학생 이메일 (선택)"
                 help="입력하면 이 이메일로 학생 가입·과제 배정이 가능해집니다. 비워두면 오답노트용 이름만 등록됩니다.">
            <input className="input" type="email" value={email}
                   onChange={e => setEmail(e.target.value)} placeholder="student@example.com" />
          </Field>
          {!emailOk && <Banner kind="error">이메일 형식이 올바르지 않습니다.</Banner>}
          {msg && <Banner kind={msg.ok ? "ok" : "error"}>{msg.text}</Banner>}
        </div>
      </Modal>
    </>
  );
}

function StudentDetail({ student }) {
  const [tab, setTab] = stUseState("wb");
  const [confirmDel, setConfirmDel] = stUseState(false);
  // bootstrap N+1 회피: 학생 detail 진입 시 lazy fetch (wrong + workbooks).
  // 이미 한번 fetch한 학생은 store 에 set/array 로 들어가 있어서 빈 dict 가
  // 아닌 경우만 skip. 학생 전환할 때마다 한 번만 호출.
  stUseEffect(() => {
    if (!student) return;
    const cached = window.storeSnapshot().studentWbs[student.id];
    if (cached === undefined) {
      window.storeActions.loadStudentExtras(student.id);
    }
  }, [student && student.id]);
  if (!student) return null;
  return (
    <div className="col" style={{ gap: 12 }}>
      <div className="card">
        <div className="card-header">
          <h3>{student.name}</h3>
          {student.email
            ? <Badge kind="navy" mono>{student.email}</Badge>
            : <Badge>이메일 미등록</Badge>}
          <Badge>오답 {student.n_wrong}</Badge>
          <Badge>문제집 {student.n_wb}</Badge>
          <div className="spacer" />
          {confirmDel ? (
            <>
              <span className="muted small">정말 삭제? (오답·문제집 기록 모두 삭제)</span>
              <Button size="sm" variant="danger"
                      onClick={() => { window.storeActions.deleteStudent(student.id); setConfirmDel(false); }}>
                삭제 확인
              </Button>
              <Button size="sm" variant="ghost" onClick={() => setConfirmDel(false)}>취소</Button>
            </>
          ) : (
            <Button size="sm" variant="ghost" onClick={() => setConfirmDel(true)}>학생 삭제</Button>
          )}
        </div>
      </div>

      <Tabs
        tabs={[
          { value: "wb",      label: "부여한 문제집" },
          { value: "assign",  label: "새 문제집 배정" },
          { value: "wrong",   label: "오답노트 만들기" },
          { value: "analyze", label: "학생 분석" },
        ]}
        value={tab} onChange={setTab}
      />

      {tab === "wb"      && <StudentWorkbooks student={student} />}
      {tab === "assign"  && <StudentAssign student={student} />}
      {tab === "wrong"   && <StudentWrongTab student={student} />}
      {tab === "analyze" && <StudentAnalysisTab student={student} />}
    </div>
  );
}

// 이 학생의 계정(이메일)에 일반 문제집(History의 워크북)을 배정 → 학생 '내 과제'에
// 뜨고 MCQ 자동채점·FRQ 사진제출이 됨. 백엔드는 P2 의 POST /api/assignments 재사용
// (배정 시 그 이메일 자동 초대).
function StudentAssign({ student }) {
  const store = useStore();
  const history = store.history || [];
  const [busyId, setBusyId] = stUseState(null);
  const [msg, setMsg] = stUseState(null);   // {ok, text}

  if (!student.email) {
    return (
      <Banner kind="warn" title="이메일 미등록 — 배정 불가">
        이 학생을 학생 계정에 연동하려면 이메일이 필요합니다. 좌측 목록 상단 <strong>추가</strong>에서
        같은 이름으로 이메일을 넣어 다시 등록하면 연동됩니다 (오답노트용 이름 기록은 유지).
      </Banner>
    );
  }
  if (history.length === 0) {
    return <EmptyState title="배정할 문제집이 없습니다"
      hint="Workbook 탭에서 문제집을 먼저 만들면 여기서 이 학생에게 배정할 수 있습니다." />;
  }

  const assign = async w => {
    setBusyId(w.id); setMsg(null);
    const r = await window.storeActions.assignWorkbook(w.id, student.email, w.file_name);
    setBusyId(null);
    if (r && r.ok) setMsg({ ok: true, text: `"${w.file_name}" → ${student.email} 배정 완료 (MCQ ${r.n_mcq} · FRQ ${r.n_frq}). 학생 '내 과제'에 표시됩니다.` });
    else setMsg({ ok: false, text: "배정 실패: " + ((r && r.message) || "원인 미상") });
  };

  return (
    <div className="col" style={{ gap: 10 }}>
      <div className="muted small">
        <strong className="mono">{student.email}</strong> 의 학생 계정에 배정합니다. 학생이 그 이메일로
        가입(또는 이미 가입)하면 '내 과제'에서 풀고 MCQ는 자동 채점됩니다.
      </div>
      {msg && <Banner kind={msg.ok ? "ok" : "error"}>{msg.text}</Banner>}
      {history.map(w => (
        <div className="card" key={w.id}>
          <div className="card-header">
            <Icon.File />
            <strong className="mono small">{w.file_name}</strong>
            <Badge>{w.subject || "?"}</Badge>
            <Badge>{w.qtype || "?"}</Badge>
            <span className="muted small">{w.n || "?"}문제 · {w.created_at}</span>
            <div className="spacer" />
            <Button size="sm" variant="primary" disabled={busyId === w.id}
              onClick={() => assign(w)}>
              {busyId === w.id ? "배정 중…" : "이 학생에게 배정"}
            </Button>
          </div>
        </div>
      ))}
    </div>
  );
}

// 부여한 문제집 — 두 source 합쳐서 sub-tab 으로 나눠 본다.
//   "배정 문제집" = store.assignments 중 student_email === student.email.
//                  학생 계정에 보내져 MCQ 자동채점·FRQ 사진제출이 되는 라이브 과제.
//   "오답노트"    = studentWbs[s.id] 의 review_pdf 기록. 학생 계정 연동 없음 (출력용).
// 박현우 명시 2026-05-20: 두 종류 다 보여야 한다 (예전엔 오답노트만 보였음).
function StudentWorkbooks({ student }) {
  const store = useStore();
  const [sub, setSub] = stUseState("assigned");
  const [openId, setOpenId] = stUseState(null);   // 클릭한 배정 진척 상세

  const allAssignments = (store.assignments || []).filter(
    a => student.email && (a.student_email || "").toLowerCase() === student.email.toLowerCase()
  );
  const allReview = (store.studentWbs[student.id] || []);

  return (
    <div className="col" style={{ gap: 10 }}>
      <Segmented
        options={[
          { value: "assigned", label: `배정 문제집 (${allAssignments.length})` },
          { value: "review",   label: `오답노트 (${allReview.length})` },
        ]}
        value={sub} onChange={setSub}
      />

      {sub === "assigned" && (
        allAssignments.length === 0 ? (
          <EmptyState
            title={student.email ? "이 학생에게 배정한 문제집 없음" : "이메일 미등록 — 배정 불가"}
            hint={student.email
              ? "'새 문제집 배정' 탭에서 보낼 수 있어요."
              : "학생을 같은 이름·이메일로 다시 추가하면 배정 가능."} />
        ) : (
          <div className="col" style={{ gap: 8 }}>
            {allAssignments.map(a => (
              <AssignedWorkbookRow key={a.id} a={a}
                open={openId === a.id}
                onToggle={() => setOpenId(openId === a.id ? null : a.id)} />
            ))}
          </div>
        )
      )}

      {sub === "review" && (
        allReview.length === 0 ? (
          <EmptyState title="아직 만든 오답노트 없음" hint="'오답노트 만들기' 탭에서 만들면 여기 기록됩니다." />
        ) : (
          <div className="col" style={{ gap: 8 }}>
            {allReview.map(w => (
              <div className="card" key={w.id}>
                <div className="card-header">
                  <Icon.File />
                  <strong>{w.file_name}</strong>
                  <Badge kind="navy">{w.kind}</Badge>
                  <Badge>{w.n_problems}문제</Badge>
                  <span className="muted small mono">{w.created_at}</span>
                  <div className="spacer" />
                  <Button size="sm" onClick={() => window.storeActions.downloadStudentWorkbook(student.id, w.id, w.file_name)}>
                    <Icon.Download /> 다운로드
                  </Button>
                </div>
              </div>
            ))}
          </div>
        )
      )}
    </div>
  );
}

// 배정한 문제집 한 줄 — 클릭하면 진척 상세 (제출 여부 · 점수 · 틀린 문제 · 단원별 통계).
// 백엔드 GET /api/assignments/{aid}/detail 가 채워주는 데이터를 lazy fetch.
function AssignedWorkbookRow({ a, open, onToggle }) {
  const [detail, setDetail] = stUseState(null);
  const [loading, setLoading] = stUseState(false);
  const [err, setErr] = stUseState(null);
  const s = a.submission || {};
  const submitted = !!s.submitted;

  React.useEffect(() => {
    if (!open || detail || loading) return;
    setLoading(true); setErr(null);
    window.storeActions.fetchAssignmentDetail(a.id)
      .then(d => { if (d && d.ok) setDetail(d.detail); else setErr((d && d.message) || "상세를 불러올 수 없어요."); })
      .finally(() => setLoading(false));
  }, [open]);

  return (
    <div className="card">
      <div className="card-header" style={{ cursor: "pointer" }} onClick={onToggle}>
        {open ? <Icon.ChevDown /> : <Icon.ChevRight />}
        <Icon.Notebook />
        <strong>{a.title}</strong>
        <Badge>{a.subject || "?"}</Badge>
        <span className="muted small">MCQ {a.n_mcq}{a.n_frq ? ` · FRQ ${a.n_frq}` : ""}</span>
        <div className="spacer" />
        {!submitted ? (
          <Badge kind="warn">미제출</Badge>
        ) : (
          <>
            <Badge kind="ok">MCQ {s.mcq_correct}/{s.mcq_total}</Badge>
            {a.n_frq > 0 && (s.graded
              ? <Badge kind="ok">FRQ {s.frq_score}/{s.frq_max}</Badge>
              : <Badge kind="warn">FRQ 채점 대기</Badge>)}
          </>
        )}
        <Button size="sm" variant="ghost"
          onClick={e => { e.stopPropagation(); window.storeActions.downloadAssignmentPdf(a.id, (a.title || "assignment") + ".pdf"); }}>
          <Icon.Download /> PDF
        </Button>
      </div>

      {open && (
        <div className="card-body col" style={{ gap: 12 }}>
          {!submitted ? (
            <Banner kind="info">학생이 아직 제출하지 않았어요.</Banner>
          ) : loading ? (
            <div className="muted small">상세 불러오는 중…</div>
          ) : err ? (
            <Banner kind="error">{err}</Banner>
          ) : detail ? (
            <AssignmentDetailView detail={detail} />
          ) : null}
        </div>
      )}
    </div>
  );
}

// 백엔드 detail 응답을 받아서: MCQ 표 (qnum · 학생답 · 정답 · O/X) +
// 단원별 정답률 + FRQ 채점 결과.
function AssignmentDetailView({ detail }) {
  const wrong = (detail.mcq_items || []).filter(it => !it.correct);
  return (
    <div className="col" style={{ gap: 14 }}>
      {(detail.mcq_total || 0) > 0 && (
        <div>
          <div style={{ fontSize: 12, fontWeight: 600, color: "var(--text-2)", marginBottom: 6 }}>
            MCQ — 맞춤 {detail.mcq_correct}/{detail.mcq_total} · 틀림 {wrong.length}
          </div>
          {(detail.mcq_items || []).length > 0 && (
            <table className="table">
              <thead><tr><th>문항</th><th>학생 답</th><th>정답</th><th>단원</th><th></th></tr></thead>
              <tbody>
                {detail.mcq_items.map(it => (
                  <tr key={it.n}>
                    <td className="mono">{it.n}</td>
                    <td><Badge>{it.given || "·"}</Badge></td>
                    <td><Badge kind="navy" mono>{it.answer || "?"}</Badge></td>
                    <td className="muted small">{(it.units || []).join(", ") || "-"}</td>
                    <td>{it.correct ? <Badge kind="ok">O</Badge> : <Badge kind="warn">X</Badge>}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
        </div>
      )}

      {(detail.units || []).length > 0 && (
        <div>
          <div style={{ fontSize: 12, fontWeight: 600, color: "var(--text-2)", marginBottom: 6 }}>
            단원별 정답률 (낮은 순)
          </div>
          <div className="col" style={{ gap: 4 }}>
            {detail.units.map(u => {
              const pct = u.total ? Math.round(u.correct / u.total * 100) : 0;
              return (
                <div key={u.unit} className="row" style={{ gap: 8, alignItems: "center" }}>
                  <span className="small" style={{ width: 240, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{u.unit}</span>
                  <div style={{ flex: 1, height: 8, background: "var(--bg-1, #eee)", borderRadius: 4, overflow: "hidden" }}>
                    <div style={{ width: pct + "%", height: "100%",
                                  background: pct >= 70 ? "var(--ok, #5a8)" : pct >= 40 ? "var(--warn, #c84)" : "var(--danger, #c44)" }} />
                  </div>
                  <span className="mono small" style={{ width: 80, textAlign: "right" }}>
                    {u.correct}/{u.total} · {pct}%
                  </span>
                </div>
              );
            })}
          </div>
        </div>
      )}

      {detail.frq && detail.frq.graded && (
        <Banner kind="ok" title={`FRQ 채점 — ${detail.frq.score}/${detail.frq.max}`}>
          {detail.frq.feedback || "선생님이 채점을 완료했어요."}
        </Banner>
      )}
    </div>
  );
}

// 학생 분석 탭 — AI 가 학생의 누적 데이터(오답·배정·라벨)를 보고 취약 단원 + 패턴을
// 자연어로 분석. "분석하기" 버튼 누르면 백엔드 호출.
function StudentAnalysisTab({ student }) {
  const [busy, setBusy] = stUseState(false);
  const [result, setResult] = stUseState(null);   // { units, ai_text } 등
  const [err, setErr] = stUseState(null);

  const run = async () => {
    setBusy(true); setErr(null);
    const r = await window.storeActions.analyzeStudent(student.id);
    setBusy(false);
    if (r && r.ok) setResult(r.analysis);
    else setErr((r && r.message) || "분석에 실패했어요.");
  };

  return (
    <Card>
      <div className="col" style={{ gap: 12 }}>
        <div className="row" style={{ alignItems: "center", justifyContent: "space-between" }}>
          <div>
            <h4 style={{ margin: 0 }}>학생 취약점 분석</h4>
            <div className="muted small">
              누적 오답 · 배정한 문제집의 MCQ 채점 결과 · 단원/토픽 라벨을 종합해서 약한 영역을 짚어냅니다.
              <strong> 분석하기</strong> 누르면 AI가 분석 시작.
            </div>
          </div>
          <Button variant="primary" disabled={busy} onClick={run}>
            <Icon.Wand /> {busy ? "분석 중…" : "분석하기"}
          </Button>
        </div>
        {err && <Banner kind="error">{err}</Banner>}
        {result && <StudentAnalysisView a={result} />}
      </div>
    </Card>
  );
}

function StudentAnalysisView({ a }) {
  const weak = (a.weak_units || []);
  return (
    <div className="col" style={{ gap: 14 }}>
      <div className="metric-grid">
        <Metric label="누적 풀이" value={a.n_attempted || 0} />
        <Metric label="누적 정답" value={a.n_correct || 0} />
        <Metric label="누적 오답" value={a.n_wrong || 0} highlight />
        <Metric label="정답률" value={a.n_attempted ? `${Math.round(100 * a.n_correct / a.n_attempted)}%` : "-"} />
      </div>

      {weak.length > 0 && (
        <div>
          <h4 style={{ margin: "4px 0" }}>약한 단원 (정답률 낮은 순)</h4>
          <div className="col" style={{ gap: 4 }}>
            {weak.map(u => {
              const pct = u.total ? Math.round(100 * u.correct / u.total) : 0;
              return (
                <div key={u.unit} className="row" style={{ gap: 8, alignItems: "center" }}>
                  <span className="small" style={{ width: 240, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{u.unit}</span>
                  <div style={{ flex: 1, height: 8, background: "var(--bg-1, #eee)", borderRadius: 4, overflow: "hidden" }}>
                    <div style={{ width: pct + "%", height: "100%",
                                  background: pct >= 70 ? "var(--ok, #5a8)" : pct >= 40 ? "var(--warn, #c84)" : "var(--danger, #c44)" }} />
                  </div>
                  <span className="mono small" style={{ width: 80, textAlign: "right" }}>{u.correct}/{u.total} · {pct}%</span>
                </div>
              );
            })}
          </div>
        </div>
      )}

      {a.ai_text && (
        <div>
          <h4 style={{ margin: "4px 0" }}>AI 분석</h4>
          <div style={{
            padding: "12px 14px", background: "var(--bg-1, #f8f8fa)",
            borderRadius: 8, lineHeight: 1.7, whiteSpace: "pre-wrap",
          }}>
            {a.ai_text}
          </div>
        </div>
      )}
    </div>
  );
}

function StudentWrongTab({ student }) {
  const store = useStore();
  const subjects = Object.keys(TAXONOMY).filter(s => store.pdfs.some(p => p.subject === s));
  const [subject, setSubject] = stUseState(subjects[0] || "Physics 1");
  const [qtype, setQtype] = stUseState("MCQ");
  const [filterUnits, setFilterUnits] = stUseState([]);

  // wrong set is server-of-truth — seed from store, mutate locally, save on click.
  const seed = store.studentWrong[student.id] || new Set();
  const [wrong, setWrong] = stUseState(seed);
  stUseEffect(() => { setWrong(new Set(store.studentWrong[student.id] || new Set())); }, [student.id]);

  const counts = stUseMemo(() => window.unitsFor(subject, qtype), [subject, qtype, store.problems]);
  const cb_units = TAXONOMY[subject] || [];
  const unitOpts = cb_units.filter(u => counts[u] > 0).map(u => `${u} (${counts[u]})`);
  const unitMap = Object.fromEntries(cb_units.map(u => [`${u} (${counts[u] || 0})`, u]));

  const problems = stUseMemo(() => window.browseProblems(subject, qtype, filterUnits), [subject, qtype, filterUnits, store.problems]);
  const names = Object.fromEntries(store.pdfs.map(p => [p.pdf_id, p.display_name || p.pdf_id]));
  const visibleIds = new Set(problems.map(p => p.id));
  const visibleWrong = [...wrong].filter(id => visibleIds.has(id)).length;

  const toggle = id => {
    const next = new Set(wrong);
    if (next.has(id)) next.delete(id); else next.add(id);
    setWrong(next);
  };

  const [genBusy, setGenBusy] = stUseState(false);
  const [genMsg, setGenMsg] = stUseState(null);   // {ok, text}
  const saveWrong = async () => {
    await window.storeActions.setStudentWrong(student.id, wrong);
  };

  const genReviewPdf = async () => {
    setGenBusy(true); setGenMsg(null);
    const r = await window.storeActions.generateReviewPdf(student.id, wrong);
    setGenBusy(false);
    if (r && r.ok) setGenMsg({ ok: true, text: "오답노트 PDF가 생성·다운로드되었습니다." });
    else setGenMsg({ ok: false, text: "오답노트 생성 실패: " + ((r && r.message) || "원인 미상") });
  };

  return (
    <Card>
      <div className="col" style={{ gap: 14 }}>
        <div className="row" style={{ gap: 12, flexWrap: "wrap" }}>
          <Field label="과목">
            <select className="select" value={subject} onChange={e => setSubject(e.target.value)}>
              {subjects.map(s => <option key={s} value={s}>{s}</option>)}
            </select>
          </Field>
          <Field label="유형">
            <Segmented options={["MCQ", "FRQ"]} value={qtype} onChange={setQtype} />
          </Field>
        </div>

        <Field label="단원 필터 (선택) — 비우면 과목+유형 전체">
          <select className="select" multiple size={Math.min(6, Math.max(2, unitOpts.length))}
                  value={filterUnits.map(u => `${u} (${counts[u] || 0})`)}
                  onChange={e => setFilterUnits(Array.from(e.target.selectedOptions).map(o => unitMap[o.value]).filter(Boolean))}>
            {unitOpts.map(o => <option key={o} value={o}>{o}</option>)}
          </select>
        </Field>

        <div className="metric-grid">
          <Metric label="이 학생 누적 오답" value={wrong.size} highlight />
          <Metric label="이 화면 표시 / 선택" value={`${problems.length} / ${visibleWrong}`} />
          <div className="col" style={{ justifyContent: "center", gap: 6 }}>
            <Button variant="primary" onClick={saveWrong}><Icon.Save /> 오답 선택 저장</Button>
          </div>
        </div>

        {problems.length === 0 ? (
          <Banner kind="info">표시할 문제 없음 — 필터를 바꿔보세요.</Banner>
        ) : (
          <div className={"problem-grid" + (qtype === "FRQ" ? " frq" : "")}>
            {problems.map(p => (
              <ProblemCard key={p.id} problem={p} mode="select" selected={wrong.has(p.id)}
                onToggle={() => toggle(p.id)} source={names[p.pdf_id]}
                scoringPaths={qtype === "FRQ" ? p.scoring_paths : undefined} />
            ))}
          </div>
        )}

        <div className="divider" />

        <div className="row" style={{ alignItems: "center", justifyContent: "space-between" }}>
          <div>
            <h4 style={{ margin: 0 }}>오답노트 PDF 생성</h4>
            <div className="muted small">현재 선택된 오답 {wrong.size}문제로 한 권의 PDF를 만듭니다.</div>
          </div>
          <Button variant="primary" size="lg" disabled={wrong.size === 0 || genBusy} onClick={genReviewPdf}>
            {genBusy ? "생성 중…" : `오답노트 생성 (${wrong.size}문제)`}
          </Button>
        </div>
        {genMsg && (
          <Banner kind={genMsg.ok ? "ok" : "error"}>
            {genMsg.text}
          </Banner>
        )}
      </div>
    </Card>
  );
}

Object.assign(window, { StudentsPage });
