/* blueyetutor — /workbook
 *
 * Restructure: a single "config" card at the top picks
 *   1) 과목 카테고리 (PROGRAMS: AP / IB / A-Level)
 *   2) 세부 과목 (e.g. Physics 1, Calculus BC)
 *   3) 문제집 종류 토글 (Segmented: 단원별 / 직접선택 / Mock Exam)
 * Below: the kind-specific form (units / pick / mock) operates on the
 * subject chosen above. Each sub-component receives `subject` as a prop.
 *
 * Assumed endpoint (build):
 *   POST /api/workbooks/build { kind, program, subject, qtype, units?, pdf_ids?,
 *                               n?, n_mcq?, n_frq?, problem_ids?, tag,
 *                               answers_included }
 */
const { useState: wbUseState, useMemo: wbUseMemo, useEffect: wbUseEffect } = React;

function WorkbookPage() {
  const store = useStore();
  const programs = Object.keys(PROGRAMS);
  const [program, setProgram] = wbUseState("AP");
  const subjectsForProgram = PROGRAMS[program].subjects;
  const [subject, setSubject] = wbUseState(subjectsForProgram[0] || "");
  const [kind, setKind] = wbUseState("auto");
  // Top-level sub-tab: 만들기 / history (legacy #/history 도 ?tab=history 로 redirect).
  const initialTab = (typeof hashQueryParam === "function" && hashQueryParam("tab")) || "make";
  const [tab, setTab] = wbUseState(initialTab === "history" ? "history" : "make");

  // when program changes, reset subject to the first available in that program
  wbUseEffect(() => {
    const subs = PROGRAMS[program].subjects;
    if (!subs.includes(subject)) setSubject(subs[0] || "");
  }, [program]);

  const dbSubj = new Set(store.pdfs.map(p => p.subject));
  const subjectIsReady = PROGRAMS[program].available && dbSubj.has(subject);

  return (
    <div className="page col" style={{ gap: 24 }}>
      <div className="page-header">
        <div className="eyebrow">Workbook</div>
        <h1>문제집을 만들고 기록을 확인합니다</h1>
      </div>

      <Tabs
        tabs={[
          { value: "make",    label: "문제집 만들기" },
          { value: "history", label: `이전에 만든 문제집 (${(store.history || []).length})` },
        ]}
        value={tab} onChange={setTab}
      />

      {tab === "history" ? <WorkbookHistorySection /> : (
      <>
      <Card>
        <div className="col" style={{ gap: 18 }}>
          <Field label="과목 카테고리">
            <div className="cluster">
              {programs.map(p => {
                const av = PROGRAMS[p].available;
                return (
                  <button key={p}
                          className={"chip " + (program === p ? "active" : "")}
                          onClick={() => av && setProgram(p)}
                          disabled={!av}
                          style={!av ? { opacity: 0.45, cursor: "not-allowed" } : undefined}>
                    {p}{!av && <span className="muted" style={{ marginLeft: 6 }}>준비 중</span>}
                  </button>
                );
              })}
            </div>
          </Field>

          <div className="row" style={{ gap: 18, flexWrap: "wrap" }}>
            <Field label="세부 과목" className="grow">
              <select className="select" value={subject} onChange={e => setSubject(e.target.value)}>
                {subjectsForProgram.map(s => (
                  <option key={s} value={s}>
                    {s}{dbSubj.has(s) ? "" : "  ·  DB 비어있음"}
                  </option>
                ))}
              </select>
            </Field>
            <Field label="문제집 종류" className="grow">
              <Segmented
                options={[
                  { value: "auto", label: "단원별" },
                  { value: "pick", label: "직접 선택" },
                  { value: "mock", label: "Mock Exam" },
                ]}
                value={kind} onChange={setKind}
              />
            </Field>
          </div>

          <div className="muted small">
            {kind === "auto" && "선택한 단원에서 자동으로 문제를 샘플링해 한 권으로 묶습니다."}
            {kind === "pick" && "라벨링된 풀에서 카드를 직접 클릭해 개별 문항을 모읍니다."}
            {kind === "mock" && "College Board 형식 (40 MCQ + 4 FRQ, 3시간) 으로 한 권의 모의시험을 구성합니다."}
          </div>
        </div>
      </Card>

      {!PROGRAMS[program].available ? (
        <Banner kind="warn" title={`${program} — 준비 중`}>
          {program} 파이프라인은 추후 합류 예정입니다. 지금은 AP 과목군만 사용할 수 있어요.
        </Banner>
      ) : (
        <>
          {kind === "auto" && <WbAuto subject={subject} />}
          {kind === "pick" && <WbPick subject={subject} />}
          {kind === "mock" && <WbMock subject={subject} />}
        </>
      )}
      </>
      )}
    </div>
  );
}

// 만든 문제집 기록 — Workbook 페이지 안 sub-tab. 이전 #/history 페이지 내용을
// 단순히 이쪽으로 옮긴 것. HistoryRow / HistoryAnswers 는 History.jsx 에서 그대로
// 재사용 (window 전역으로 노출돼 있음).
function WorkbookHistorySection() {
  const store = useStore();
  const rows = store.history || [];
  if (rows.length === 0) {
    return <EmptyState
      title="아직 생성된 문제집이 없습니다."
      hint="'문제집 만들기' 탭에서 첫 PDF 를 만들어 보세요." />;
  }
  return (
    <div className="col" style={{ gap: 8 }}>
      {rows.map(w => <HistoryRow key={w.id} w={w} />)}
    </div>
  );
}
Object.assign(window, { WorkbookHistorySection });

// 문제집 PDF 합성은 백엔드 subprocess(build_workbook.py) 동기 호출 — 큰 PDF
// 일수록 분 단위로 걸린다. 그 동안 페이지가 멈춰 보이지 않도록 명시적인
// fullscreen 오버레이로 진행 중임을 알리고 다른 클릭을 막는다. backdrop 의
// onClick 은 비워 둬서 우발적 dismiss 도 막는다 (취소 = 페이지 이동).
function BuildingOverlay({ open }) {
  if (!open) return null;
  return (
    <div className="modal-backdrop" style={{ cursor: "wait" }}>
      <div className="modal build-overlay-card" onClick={e => e.stopPropagation()}>
        <div className="build-overlay-title">현재 문제집을 생성중입니다…</div>
        <div className="build-overlay-hint">
          PDF 합성·렌더링에 시간이 좀 걸려요.<br />
          페이지를 닫지 말고 잠시만 기다려 주세요.
        </div>
        <div className="spinner" style={{ margin: "22px auto 0" }} />
      </div>
    </div>
  );
}

function AnswersToggle({ value, onChange }) {
  return (
    <label className="checkbox">
      <input type="checkbox" checked={value} onChange={e => onChange(e.target.checked)} />
      <span>답지 포함 <span className="muted small">(해제 시 PDF에 미출력 — 히스토리에선 확인 가능)</span></span>
    </label>
  );
}

// ---------- 단원자동 ----------
function WbAuto({ subject }) {
  const store = useStore();
  const [pdfIds, setPdfIds] = wbUseState([]);
  const [qtype, setQtype] = wbUseState("MCQ");
  const [units, setUnits] = wbUseState([]);
  const [tag, setTag] = wbUseState("web-" + new Date().toISOString().slice(0, 10));
  const [incAns, setIncAns] = wbUseState(true);

  wbUseEffect(() => { setUnits([]); setPdfIds([]); }, [subject, qtype]);

  const cb_units = TAXONOMY[subject] || [];
  const both = qtype === "MCQ + FRQ";
  const mcq_c = wbUseMemo(() => window.unitsFor(subject, "MCQ", pdfIds), [subject, pdfIds, store.problems]);
  const frq_c = wbUseMemo(() => window.unitsFor(subject, "FRQ", pdfIds), [subject, pdfIds, store.problems]);
  const counts = both
    ? Object.fromEntries(cb_units.map(u => [u, (mcq_c[u] || 0) + (frq_c[u] || 0)]))
    : qtype === "MCQ" ? mcq_c : frq_c;

  const poolMcq = units.length ? units.reduce((s, u) => s + (mcq_c[u] || 0), 0) : Object.values(mcq_c).reduce((a, b) => a + b, 0);
  const poolFrq = units.length ? units.reduce((s, u) => s + (frq_c[u] || 0), 0) : Object.values(frq_c).reduce((a, b) => a + b, 0);
  const poolN = units.length ? units.reduce((s, u) => s + (counts[u] || 0), 0) : Object.values(counts).reduce((a, b) => a + b, 0);

  const [n, setN] = wbUseState(10);
  const [nMcq, setNMcq] = wbUseState(10);
  const [nFrq, setNFrq] = wbUseState(2);

  const pdfOpts = store.pdfs.filter(p => p.subject === subject);
  const ok = both ? (nMcq <= poolMcq && nFrq <= poolFrq && nMcq + nFrq > 0)
                  : (poolN >= n);
  const toggleUnit = u => setUnits(s => s.includes(u) ? s.filter(x => x !== u) : [...s, u]);

  // §122 calculator filter (Calc subjects 전용 — Physics 등은 토글 안 보임)
  const isCalcSubject = /^Calculus\b/i.test(subject || "");
  const [calculator, setCalculator] = wbUseState("both");
  wbUseEffect(() => { if (!isCalcSubject) setCalculator("both"); }, [isCalcSubject]);

  const [busy, setBusy] = wbUseState(false);
  const build = async () => {
    setBusy(true);
    const r = await window.storeActions.buildWorkbook({
      mode: "auto", subject, qtype: both ? "BOTH" : qtype,
      units, pdf_ids: pdfIds, n, n_mcq: nMcq, n_frq: nFrq,
      tag, answers: incAns,
      calculator: isCalcSubject && calculator !== "both" ? calculator : undefined,
    });
    setBusy(false);
    if (r) window.storeActions.downloadWorkbook(r.workbook_id, r.file_name);
  };

  return (
    <Card title="단원별 문제집" actions={<Badge kind="navy">{subject}</Badge>}>
      <div className="col" style={{ gap: 16 }}>
        <div className="row" style={{ gap: 18, flexWrap: "wrap" }}>
          <Field label="문제 유형" className="grow">
            <Segmented options={["MCQ", "FRQ", "MCQ + FRQ"]} value={qtype} onChange={setQtype} />
          </Field>
          <Field label="파일 선택 (비우면 전체 파일에서 샘플)" className="grow">
            <FilePickerMulti opts={pdfOpts} value={pdfIds} onChange={setPdfIds} />
          </Field>
        </div>

        <div>
          <div className="row" style={{ alignItems: "center", justifyContent: "space-between", marginBottom: 10 }}>
            <span style={{ fontSize: 12, fontWeight: 600, color: "var(--text-2)" }}>단원</span>
            {units.length > 0 && <Button size="sm" variant="ghost" onClick={() => setUnits([])}>선택 해제</Button>}
          </div>
          {cb_units.length === 0 ? (
            <Banner kind="warn">{subject} 의 taxonomy 단원을 찾을 수 없어요.</Banner>
          ) : (
            <div style={{ display: "grid", gap: 8, gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))" }}>
              {cb_units.map(u => {
                const c = counts[u] || 0;
                return (
                  <label key={u} className="checkbox" style={{ opacity: c ? 1 : 0.4 }}>
                    <input type="checkbox" disabled={!c} checked={units.includes(u)} onChange={() => toggleUnit(u)} />
                    <span>{u} <span className="muted small">({c}{c ? "" : " — 라벨된 문제 없음"})</span></span>
                  </label>
                );
              })}
            </div>
          )}
        </div>

        {both ? (
          <>
            <div className="muted small">
              풀 — MCQ <span className="navy" style={{ fontWeight: 700 }}>{poolMcq}</span>
              {"  ·  "}FRQ <span className="navy" style={{ fontWeight: 700 }}>{poolFrq}</span>
              {"  "}({units.length ? "선택 단원" : "과목 전체"})
            </div>
            <div className="row" style={{ gap: 18, flexWrap: "wrap" }}>
              <Field label={<>MCQ 문제 수 — <EditableNum value={nMcq} min={0} max={Math.max(1, poolMcq)} onChange={setNMcq} /></>} className="grow">
                <input type="range" min={0} max={Math.max(1, poolMcq)} value={nMcq} onChange={e => setNMcq(+e.target.value)} className="stretch" />
              </Field>
              <Field label={<>FRQ 문제 수 — <EditableNum value={nFrq} min={0} max={Math.max(1, poolFrq)} onChange={setNFrq} /></>} className="grow">
                <input type="range" min={0} max={Math.max(1, poolFrq)} value={nFrq} onChange={e => setNFrq(+e.target.value)} className="stretch" />
              </Field>
            </div>
          </>
        ) : (
          <>
            <div className="muted small">풀 <span className="navy" style={{ fontWeight: 700 }}>{poolN}</span> ({qtype})</div>
            <Field label={<>문제 수 — <EditableNum value={n} min={3} max={Math.max(40, poolN || 40)} onChange={setN} /></>}>
              <input type="range" min={3} max={Math.max(40, poolN || 40)} value={n} onChange={e => setN(+e.target.value)} className="stretch" />
            </Field>
          </>
        )}

        <Field label="태그 (파일명에 들어감)">
          <input className="input" value={tag} onChange={e => setTag(e.target.value)} maxLength={40} />
        </Field>

        {isCalcSubject && (
          <Field label={<>계산기 <span className="muted small">(CB 표준: Part A no-calc / Part B calc)</span></>}>
            <Segmented
              options={[
                { value: "both", label: "모두" },
                { value: "no", label: "계산기 금지만" },
                { value: "yes", label: "계산기 허용만" },
              ]}
              value={calculator} onChange={setCalculator}
            />
          </Field>
        )}

        <AnswersToggle value={incAns} onChange={setIncAns} />

        {!ok && <Banner kind="error">풀 크기가 요청보다 작아요. 단원을 더 고르거나 N을 줄여보세요.</Banner>}
        <div>
          <Button variant="primary" size="lg" disabled={!ok || busy} onClick={build}>{busy ? "생성 중…" : "문제집 PDF 생성"}</Button>
        </div>
      </div>
      <BuildingOverlay open={busy} />
    </Card>
  );
}

// ---------- 직접 선택 ----------
function WbPick({ subject }) {
  const store = useStore();
  const [qtype, setQtype] = wbUseState("MCQ");
  const [filterUnits, setFilterUnits] = wbUseState([]);
  const [picks, setPicks] = wbUseState(new Set());
  const [tag, setTag] = wbUseState("pick-" + new Date().toISOString().slice(0, 10));
  const [incAns, setIncAns] = wbUseState(true);

  wbUseEffect(() => { setFilterUnits([]); setPicks(new Set()); }, [subject, qtype]);

  const cb_units = TAXONOMY[subject] || [];
  const counts = wbUseMemo(() => window.unitsFor(subject, qtype === "MCQ + FRQ" ? "MCQ" : qtype), [subject, qtype, store.problems]);
  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 = wbUseMemo(() => {
    const both = qtype === "MCQ + FRQ";
    if (both) {
      return [
        ...window.browseProblems(subject, "MCQ", filterUnits),
        ...window.browseProblems(subject, "FRQ", filterUnits),
      ];
    }
    return window.browseProblems(subject, qtype, filterUnits);
  }, [subject, qtype, filterUnits, store.problems]);

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

  const mcqs = problems.filter(p => p.qtype === "MCQ");
  const frqs = problems.filter(p => p.qtype === "FRQ");
  const names = Object.fromEntries(store.pdfs.map(p => [p.pdf_id, p.display_name || p.pdf_id]));

  const [busy, setBusy] = wbUseState(false);
  const build = async () => {
    if (picks.size === 0) return;
    const ordered = problems.filter(p => picks.has(p.id)).map(p => p.id);
    setBusy(true);
    const r = await window.storeActions.buildWorkbook({
      mode: "pick", subject, qtype,
      problem_ids: ordered, tag, answers: incAns,
    });
    setBusy(false);
    if (r) {
      window.storeActions.downloadWorkbook(r.workbook_id, r.file_name);
      setPicks(new Set());
    }
  };

  return (
    <Card title="직접 선택" actions={<Badge kind="navy">{subject}</Badge>}>
      <div className="col" style={{ gap: 16 }}>
        <div className="row" style={{ gap: 18, flexWrap: "wrap" }}>
          <Field label="문제 유형" className="grow">
            <Segmented options={["MCQ", "FRQ", "MCQ + FRQ"]} value={qtype} onChange={setQtype} />
          </Field>
          <Field label="단원 필터 (선택)" className="grow">
            <UnitMultiPicker opts={unitOpts} value={filterUnits.map(u => `${u} (${counts[u] || 0})`)}
              onChange={vals => setFilterUnits(vals.map(v => unitMap[v]).filter(Boolean))} />
          </Field>
        </div>

        <div className="row" style={{ gap: 8, alignItems: "center" }}>
          <Badge kind="navy"><span style={{ fontWeight: 700 }}>{picks.size}</span> 선택됨</Badge>
          <span className="muted small">/ {problems.length} 표시 중</span>
          {picks.size > 0 && <Button size="sm" variant="ghost" onClick={() => setPicks(new Set())}>선택 해제</Button>}
        </div>

        {(qtype === "MCQ" || qtype === "MCQ + FRQ") && mcqs.length > 0 && (
          <div>
            <h4 style={{ margin: "8px 0" }}>MCQ <span className="muted small">({mcqs.length})</span></h4>
            <div className="problem-grid">
              {mcqs.map(p => (
                <ProblemCard key={p.id} problem={p} mode="select" selected={picks.has(p.id)}
                  onToggle={() => toggle(p.id)} source={names[p.pdf_id]} />
              ))}
            </div>
          </div>
        )}
        {(qtype === "FRQ" || qtype === "MCQ + FRQ") && frqs.length > 0 && (
          <div>
            <h4 style={{ margin: "8px 0" }}>FRQ <span className="muted small">({frqs.length})</span></h4>
            <div className="problem-grid frq">
              {frqs.map(p => (
                <ProblemCard key={p.id} problem={p} mode="select" selected={picks.has(p.id)}
                  onToggle={() => toggle(p.id)} source={names[p.pdf_id]} scoringPaths={p.scoring_paths} />
              ))}
            </div>
          </div>
        )}

        <Field label="태그 (파일명에 들어감)">
          <input className="input" value={tag} onChange={e => setTag(e.target.value)} maxLength={40} />
        </Field>

        <AnswersToggle value={incAns} onChange={setIncAns} />
        <div>
          <Button variant="primary" size="lg" disabled={picks.size === 0 || busy} onClick={build}>
            {busy ? "생성 중…" : `문제집 PDF 생성 (${picks.size}문제)`}
          </Button>
        </div>
      </div>
      <BuildingOverlay open={busy} />
    </Card>
  );
}

// ---------- Mock(2026) ----------
function WbMock({ subject }) {
  const store = useStore();
  const [pdfIds, setPdfIds] = wbUseState([]);
  wbUseEffect(() => { setPdfIds([]); }, [subject]);

  const pdfOpts = store.pdfs.filter(p => p.subject === subject);
  const mcq_have = wbUseMemo(() => window.subjectPool(subject, "MCQ", pdfIds), [subject, pdfIds, store.problems]);
  const frq_have = wbUseMemo(() => window.subjectPool(subject, "FRQ", pdfIds), [subject, pdfIds, store.problems]);

  const [nMcq, setNMcq] = wbUseState(MOCK_SPEC.n_mcq);
  const [nFrq, setNFrq] = wbUseState(MOCK_SPEC.n_frq);
  const [tag, setTag] = wbUseState("mock-" + new Date().toISOString().slice(0, 10));
  const [incAns, setIncAns] = wbUseState(true);

  const ok = mcq_have >= nMcq && frq_have >= nFrq && (nMcq + nFrq) > 0;
  const gaps = [];
  if (mcq_have < MOCK_SPEC.n_mcq) gaps.push(`MCQ ${MOCK_SPEC.n_mcq - mcq_have}개 부족`);
  if (frq_have < MOCK_SPEC.n_frq) gaps.push(`FRQ ${MOCK_SPEC.n_frq - frq_have}개 부족`);

  // §122 calculator filter (Calc subjects 전용)
  const isCalcSubject = /^Calculus\b/i.test(subject || "");
  const [calculator, setCalculator] = wbUseState("both");
  wbUseEffect(() => { if (!isCalcSubject) setCalculator("both"); }, [isCalcSubject]);

  const [busy, setBusy] = wbUseState(false);
  const build = async () => {
    setBusy(true);
    const r = await window.storeActions.buildWorkbook({
      mode: "mock", subject, pdf_ids: pdfIds,
      n_mcq: nMcq, n_frq: nFrq, tag, answers: incAns,
      calculator: isCalcSubject && calculator !== "both" ? calculator : undefined,
    });
    setBusy(false);
    if (r) window.storeActions.downloadWorkbook(r.workbook_id, r.file_name);
  };

  return (
    <Card title="Mock Exam · CB 형식" actions={<Badge kind="navy">{subject}</Badge>}>
      <div className="col" style={{ gap: 16 }}>
        <Banner kind="info" title="2026 AP Physics Mock Exam 형식">
          기본 {MOCK_SPEC.n_mcq} MCQ + {MOCK_SPEC.n_frq} FRQ, {MOCK_SPEC.duration_minutes}분.
          풀이 부족하면 고급 옵션으로 수를 줄이거나, 업로드를 더 하세요.
        </Banner>

        <Field label="파일 선택 (비우면 전체 파일에서 샘플)">
          <FilePickerMulti opts={pdfOpts} value={pdfIds} onChange={setPdfIds} />
        </Field>

        <div className="metric-grid">
          <Metric label={`MCQ 풀 (필요 ${MOCK_SPEC.n_mcq})`} value={mcq_have}
            highlight={mcq_have >= MOCK_SPEC.n_mcq}
            delta={mcq_have - MOCK_SPEC.n_mcq} deltaKind={mcq_have >= MOCK_SPEC.n_mcq ? "ok" : "bad"} />
          <Metric label={`FRQ 풀 (필요 ${MOCK_SPEC.n_frq})`} value={frq_have}
            highlight={frq_have >= MOCK_SPEC.n_frq}
            delta={frq_have - MOCK_SPEC.n_frq} deltaKind={frq_have >= MOCK_SPEC.n_frq ? "ok" : "bad"} />
        </div>

        {gaps.length > 0 && (
          <Banner kind="warn">
            <strong>{subject}</strong> 의 라벨된 풀이 부족 — {gaps.join(", ")}. 더 많은 PDF를 업로드/라벨링한 뒤 재시도하세요.
          </Banner>
        )}

        <details>
          <summary className="muted small" style={{ cursor: "pointer" }}>고급 — 문항 수 override</summary>
          <div className="row" style={{ gap: 12, marginTop: 10 }}>
            <Field label="MCQ 수">
              <input className="input" type="number" min={0} max={Math.max(40, mcq_have)} value={nMcq} onChange={e => setNMcq(+e.target.value)} />
            </Field>
            <Field label="FRQ 수">
              <input className="input" type="number" min={0} max={Math.max(4, frq_have)} value={nFrq} onChange={e => setNFrq(+e.target.value)} />
            </Field>
          </div>
        </details>

        <Field label="태그">
          <input className="input" value={tag} onChange={e => setTag(e.target.value)} maxLength={40} />
        </Field>

        {isCalcSubject && (
          <Field label={<>계산기 <span className="muted small">(CB 표준: Part A no-calc / Part B calc)</span></>}>
            <Segmented
              options={[
                { value: "both", label: "모두" },
                { value: "no", label: "계산기 금지만" },
                { value: "yes", label: "계산기 허용만" },
              ]}
              value={calculator} onChange={setCalculator}
            />
          </Field>
        )}

        <AnswersToggle value={incAns} onChange={setIncAns} />
        <div>
          <Button variant="primary" size="lg" disabled={!ok || busy} onClick={build}>{busy ? "생성 중…" : "Mock Exam PDF 생성"}</Button>
        </div>
      </div>
      <BuildingOverlay open={busy} />
    </Card>
  );
}

// ---------- shared sub-components ----------
// Click the number → type it directly (slider stays the source-of-truth
// control; this is just a faster way to set an exact value). Commits on
// Enter/blur, clamped to [min,max]; Esc cancels.
function EditableNum({ value, min, max, onChange }) {
  const [editing, setEditing] = wbUseState(false);
  const [draft, setDraft] = wbUseState(String(value));
  wbUseEffect(() => { setDraft(String(value)); }, [value]);
  const commit = () => {
    let v = parseInt(draft, 10);
    if (isNaN(v)) v = value;
    v = Math.max(min, Math.min(max, v));
    onChange(v);
    setEditing(false);
  };
  if (editing) {
    return (
      <input
        className="input" type="number" min={min} max={max} autoFocus
        value={draft} onChange={e => setDraft(e.target.value)}
        onFocus={e => e.target.select()} onBlur={commit}
        onKeyDown={e => {
          if (e.key === "Enter") commit();
          else if (e.key === "Escape") { setDraft(String(value)); setEditing(false); }
        }}
        style={{ width: 64, display: "inline-block", padding: "1px 6px",
                 fontSize: 12, verticalAlign: "baseline" }}
      />
    );
  }
  return (
    <span
      onClick={() => setEditing(true)} title="클릭해서 직접 입력"
      style={{ cursor: "text", fontWeight: 700, color: "var(--navy)",
               textDecoration: "underline dotted", textUnderlineOffset: 3 }}
    >{value}</span>
  );
}

// Default = "전체 선택" (value === [] → backend samples from all files).
// The file list stays collapsed until the user opens it; once there are
// many PDFs the search box keeps it usable.
function FilePickerMulti({ opts, value, onChange }) {
  const [open, setOpen] = wbUseState(false);
  const [q, setQ] = wbUseState("");
  if (opts.length === 0) {
    return <div className="muted small" style={{ padding: "8px 0" }}>이 과목으로 처리된 PDF가 아직 없어요.</div>;
  }
  const allMode = value.length === 0;
  const nameOf = p => p.display_name || p.pdf_id;
  const needle = q.trim().toLowerCase();
  const filtered = needle ? opts.filter(p => nameOf(p).toLowerCase().includes(needle)) : opts;
  const toggle = id => onChange(value.includes(id) ? value.filter(x => x !== id) : [...value, id]);

  return (
    <div className="filepick">
      <button type="button" className="filepick-toggle" onClick={() => setOpen(o => !o)}>
        {open ? <Icon.ChevDown /> : <Icon.ChevRight />}
        <span className="filepick-summary">
          {allMode
            ? <><strong>전체 선택</strong> <span className="muted small">· {opts.length}개 파일 전체에서 샘플</span></>
            : <><strong>{value.length}개 선택됨</strong> <span className="muted small">/ {opts.length}</span></>}
        </span>
        <span className="muted small">{open ? "접기" : "파일 직접 선택"}</span>
      </button>
      {open && (
        <div className="filepick-panel">
          <div className="filepick-search">
            <Icon.Search className="ic-sm" />
            <input className="input" placeholder="파일 이름 검색…" value={q}
                   onChange={e => setQ(e.target.value)} />
          </div>
          <label className="checkbox filepick-row">
            <input type="checkbox" checked={allMode}
                   onChange={() => onChange([])} disabled={allMode} />
            <span><strong>전체 선택</strong> <span className="muted small">(모든 파일에서 샘플)</span></span>
          </label>
          <div className="filepick-list">
            {filtered.length === 0 ? (
              <div className="muted small" style={{ padding: "8px 4px" }}>검색 결과 없음</div>
            ) : filtered.map(p => (
              <label key={p.pdf_id} className="checkbox filepick-row">
                <input type="checkbox" checked={value.includes(p.pdf_id)}
                       onChange={() => toggle(p.pdf_id)} />
                <span>{nameOf(p)}</span>
              </label>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

function UnitMultiPicker({ opts, value, onChange }) {
  if (opts.length === 0) {
    return <div className="muted small" style={{ padding: "8px 0" }}>라벨된 단원이 아직 없어요.</div>;
  }
  return (
    <select className="select" multiple size={Math.min(6, Math.max(2, opts.length))}
            value={value} onChange={e => onChange(Array.from(e.target.selectedOptions).map(o => o.value))}>
      {opts.map(o => <option key={o} value={o}>{o}</option>)}
    </select>
  );
}

Object.assign(window, { WorkbookPage });
