/* blueyetutor — shared UI atoms.
 * Pure presentation. No store/data imports. Each component named for
 * grep-ability. Style objects (if any) are named with the component prefix
 * to avoid global-scope name collisions across babel scripts. */
const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ---- Tabs (controlled) ----------------------------------------------------
function Tabs({ tabs, value, onChange }) {
  return (
    <div className="tabs">
      {tabs.map(t => (
        <button
          key={t.value}
          className={value === t.value ? "active" : ""}
          onClick={() => onChange(t.value)}
        >{t.label}</button>
      ))}
    </div>
  );
}

// ---- Segmented control ----------------------------------------------------
function Segmented({ options, value, onChange, className }) {
  return (
    <div className={"segmented " + (className || "")}>
      {options.map(o => {
        const v = typeof o === "string" ? o : o.value;
        const l = typeof o === "string" ? o : o.label;
        const d = typeof o === "object" && o.disabled;
        return (
          <button key={v}
            className={v === value ? "active" : ""}
            disabled={d}
            onClick={() => !d && onChange(v)}
            style={d ? { opacity: 0.45, cursor: "not-allowed" } : undefined}>
            {l}
          </button>
        );
      })}
    </div>
  );
}

// ---- Button ---------------------------------------------------------------
function Button({ children, variant = "default", size, block, ...rest }) {
  const cls = [
    "btn",
    variant === "primary" ? "primary" : "",
    variant === "danger" ? "danger" : "",
    variant === "ghost" ? "ghost" : "",
    size === "sm" ? "sm" : "",
    size === "lg" ? "lg" : "",
    block ? "block" : "",
  ].filter(Boolean).join(" ");
  return <button className={cls} {...rest}>{children}</button>;
}

// ---- Field wrapper --------------------------------------------------------
function Field({ label, help, children, className }) {
  return (
    <div className={"field " + (className || "")}>
      {label && <label>{label}</label>}
      {children}
      {help && <div className="help">{help}</div>}
    </div>
  );
}

// ---- EmptyState -----------------------------------------------------------
function EmptyState({ title, hint, action }) {
  return (
    <div className="empty">
      <div className="title">{title}</div>
      {hint && <div>{hint}</div>}
      {action && <div style={{ marginTop: 12 }}>{action}</div>}
    </div>
  );
}

// ---- Banner ---------------------------------------------------------------
function Banner({ kind = "info", title, children }) {
  return (
    <div className={"banner " + kind}>
      <div>
        {title && <h4>{title}</h4>}
        <p>{children}</p>
      </div>
    </div>
  );
}

// ---- Badge ----------------------------------------------------------------
function Badge({ children, kind = "", mono, ...rest }) {
  return <span className={"badge " + kind + (mono ? " mono" : "")} {...rest}>{children}</span>;
}

// ---- Metric ---------------------------------------------------------------
function Metric({ label, value, delta, deltaKind, highlight }) {
  return (
    <div className={"metric" + (highlight ? " highlight" : "")}>
      <div className="label">{label}</div>
      <div className="value">{value}</div>
      {delta != null && <div className={"delta " + (deltaKind || "")}>{delta}</div>}
    </div>
  );
}

// ---- Card -----------------------------------------------------------------
function Card({ title, actions, padding = true, children, className }) {
  return (
    <div className={"card " + (className || "")}>
      {(title || actions) && (
        <div className="card-header">
          <h3>{title}</h3>
          <div className="spacer" />
          {actions}
        </div>
      )}
      <div className={padding ? "card-body" : ""}>{children}</div>
    </div>
  );
}

// ---- Modal ----------------------------------------------------------------
function Modal({ open, title, onClose, children, footer, width }) {
  useEffect(() => {
    if (!open) return;
    const onKey = e => { if (e.key === "Escape") onClose && onClose(); };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [open, onClose]);
  if (!open) return null;
  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" style={width ? { width } : undefined} onClick={e => e.stopPropagation()}>
        <div className="modal-header">
          <h3>{title}</h3>
          <div className="spacer" />
          <button className="icon-btn" onClick={onClose} aria-label="close"><Icon.Close /></button>
        </div>
        <div className="modal-body">{children}</div>
        {footer && <div className="modal-footer">{footer}</div>}
      </div>
    </div>
  );
}

// ---- Lightbox -------------------------------------------------------------
// Fullscreen image viewer. Shows the crop as large as the viewport allows;
// if the image is still bigger it scrolls inside the dark stage. Esc / click
// outside / the × closes it.
function Lightbox({ open, src, title, onClose }) {
  useEffect(() => {
    if (!open) return;
    const onKey = e => { if (e.key === "Escape") onClose && onClose(); };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [open, onClose]);
  if (!open || !src) return null;
  // Rendered inside .problem (which may have a select-toggle onClick), so the
  // root must swallow clicks: stopPropagation here keeps card selection from
  // firing, and a click anywhere off the image closes the viewer.
  return (
    <div className="lightbox-backdrop"
         onClick={e => { e.stopPropagation(); onClose && onClose(); }}>
      <div className="lightbox-bar">
        <span className="lightbox-title">{title}</span>
        <button className="icon-btn" aria-label="close"><Icon.Close /></button>
      </div>
      <div className="lightbox-stage">
        <img src={src} alt={title || "problem"} onClick={e => e.stopPropagation()} />
      </div>
    </div>
  );
}

// ---- Progress -------------------------------------------------------------
function Progress({ value, indeterminate }) {
  if (indeterminate) {
    return <div className="progress indeterminate"><span /></div>;
  }
  const pct = Math.max(0, Math.min(100, value || 0));
  return <div className="progress"><span style={{ width: pct + "%" }} /></div>;
}

// ---- CropPlaceholder ------------------------------------------------------
// Renders a striped SVG-ish placeholder labeled with the path — the live UI
// will swap this for <img src={resolved}>.
function CropPlaceholder({ kind = "mcq", label }) {
  return (
    <div className={"crop " + (kind === "frq" ? "frq" : kind === "figure" ? "figure" : "")}>
      <span>{label || "crop placeholder"}</span>
    </div>
  );
}

// ---- CropImg — real cropped-problem image (v2 wire-up) -------------------
// Authed PNG from the backend (combined figure+stem, stimulus banner where
// applicable). Falls back to the striped placeholder while loading / on 404.
function CropImg({ src, kind = "mcq", label }) {
  const [err, setErr] = useState(false);
  if (!src || err) return <CropPlaceholder kind={kind} label={label} />;
  // NOT the .crop placeholder box — that imposes aspect-ratio:4/3 + flex
  // centering which squashes a tall stimulus composite in a 1/3 grid cell.
  // The PNG is already pre-scaled (DISPLAY_SCALE) for legible on-screen text;
  // render at its true aspect, just bounded to the card width.
  return (
    <div className="crop-real">
      <img src={src} alt={label || "problem"} loading="lazy"
           onError={() => setErr(true)} />
    </div>
  );
}

// ---- ProblemHead — shared header for one problem card ---------------------
// `editable` + `onEditAnswer` / `onEditSubject` → 답 / 과목 Badge 가
// 클릭 가능한 inline editor 로 바뀐다. 그 외 호출자는 prop 안 넘김 → 표시만.
function ProblemHead({ qnum, qtype, subject, answer, units, difficulty,
                       source, dim, editable, onEditAnswer, onEditSubject,
                       calculator, sectionPart }) {
  const [editing, setEditing] = useState(false);
  const [val, setVal] = useState(answer || "");
  const [subjOpen, setSubjOpen] = useState(false);
  const [subjBusy, setSubjBusy] = useState(false);
  const [subjVal, setSubjVal] = useState(subject || "");
  const isMcq = qtype === "MCQ";
  const allSubjects = Object.keys((window.TAXONOMY) || {});
  useEffect(() => { setVal(answer || ""); }, [answer]);
  useEffect(() => { setSubjVal(subject || ""); }, [subject]);
  const commit = async () => {
    const v = (val || "").trim().toUpperCase();
    if (v === (answer || "")) { setEditing(false); return; }
    if (onEditAnswer) await onEditAnswer(v);
    setEditing(false);
  };
  const commitSubj = async () => {
    if (!subjVal || subjVal === subject) { setSubjOpen(false); return; }
    setSubjBusy(true);
    if (onEditSubject) await onEditSubject(subjVal);
    setSubjBusy(false); setSubjOpen(false);
  };
  // §124 — Calc 류 (calculator !== undefined && !== null) 일 때 Part 배지 표시.
  // 'B'/calculator=true = 계산기 허용 / 'A'/false = 금지. section_part 없으면
  // calculator 값만으로 표시 ('계산기' / 'no calc').
  const calcLabel = (() => {
    if (calculator == null) return null;
    const part = sectionPart ? `Part ${sectionPart}` : null;
    const tag = calculator ? "🖩 계산기" : "✖ no-calc";
    return part ? `${part} · ${tag}` : tag;
  })();
  return (
    <div className="head">
      <strong>Q{qnum}{qtype === "FRQ" ? " (FRQ)" : ""}</strong>
      {calcLabel && (
        <Badge kind={calculator ? "navy" : "warn"}>{calcLabel}</Badge>
      )}
      {/* 과목 Badge: editable 면 클릭해서 변경 가능 (이 문제만, PDF 전체와 별개). */}
      {editable && onEditSubject ? (
        subjOpen ? (
          <span className="row" style={{ gap: 4, alignItems: "center" }}>
            <select className="select" autoFocus value={subjVal}
              onChange={e => setSubjVal(e.target.value)}
              onBlur={commitSubj}
              onKeyDown={e => {
                if (e.key === "Enter") commitSubj();
                else if (e.key === "Escape") { setSubjVal(subject || ""); setSubjOpen(false); }
              }}
              style={{ padding: "1px 4px", fontSize: 12, height: 22 }}
              disabled={subjBusy}>
              <option value="">(미지정)</option>
              {allSubjects.map(s => <option key={s} value={s}>{s}</option>)}
            </select>
          </span>
        ) : (
          <button type="button" title="클릭해서 이 문제의 과목 변경"
            onClick={e => { e.stopPropagation(); setSubjOpen(true); }}
            style={{ border: "none", background: "transparent", padding: 0, cursor: "pointer" }}>
            {subject
              ? <Badge>{subject} ✎</Badge>
              : <Badge kind="warn">(미지정) ✎</Badge>}
          </button>
        )
      ) : (
        subject && <Badge>{subject}</Badge>
      )}
      {/* MCQ 답: editable 모드면 클릭/입력 가능, 아니면 표시만. */}
      {isMcq && editable ? (
        editing ? (
          <span className="row" style={{ gap: 4, alignItems: "center" }}>
            <input className="input" value={val} maxLength={7} autoFocus
              onChange={e => setVal(e.target.value)}
              onBlur={commit}
              onKeyDown={e => {
                if (e.key === "Enter") commit();
                else if (e.key === "Escape") { setVal(answer || ""); setEditing(false); }
              }}
              style={{ width: 60, padding: "1px 6px", fontSize: 12 }} />
          </span>
        ) : (
          <button type="button" title="클릭해서 답 수정"
            onClick={e => { e.stopPropagation(); setEditing(true); }}
            style={{ border: "none", background: "transparent", padding: 0, cursor: "pointer" }}>
            {answer
              ? <Badge mono kind="ok">{answer}</Badge>
              : <Badge kind="warn">답 미입력</Badge>}
          </button>
        )
      ) : (
        answer && <Badge mono kind="ok">{answer}</Badge>
      )}
      {difficulty != null && <span className="meta">난이도 {difficulty}</span>}
      {source && <span className="meta mono" title={source}>{dim ? "" : source}</span>}
    </div>
  );
}

// ---- UnitEditor -----------------------------------------------------------
// Click chip → modal multi-select from CB taxonomy[subject]. Save calls
// `onSave(units[])` which the page wires to `setProblemUnits` (PATCH).
function UnitEditor({ problem, onSave }) {
  const [open, setOpen] = useState(false);
  const [draft, setDraft] = useState(problem.units || []);
  useEffect(() => { setDraft(problem.units || []); }, [problem.units]);
  const subj = problem.subject || "";
  const opts = (window.TAXONOMY[subj]) || [];
  const cur = problem.units || [];
  const toggle = u => setDraft(d => d.includes(u) ? d.filter(x => x !== u) : [...d, u]);
  return (
    <>
      <span
        className={"unit-chip " + (cur.length ? "" : "empty")}
        onClick={() => setOpen(true)}
        title="단원 수정"
      >
        <span>{cur.length ? cur.join(", ") : "단원 미지정"}</span>
        <Icon.Edit className="edit-mark" />
      </span>
      <Modal
        open={open} title={`단원 수정 — Q${problem.qnum} ${problem.qtype}`}
        onClose={() => setOpen(false)}
        footer={
          <>
            <Button variant="ghost" onClick={() => setOpen(false)}>취소</Button>
            <Button variant="primary" onClick={() => { onSave(draft); setOpen(false); }} disabled={draft.length === 0}>저장</Button>
          </>
        }
      >
        <div className="muted small" style={{ marginBottom: 12 }}>
          {subj || "과목 미상"} · 복수 선택 가능 — 라벨러가 잘못 붙였으면 여기서 직접 교정
        </div>
        {opts.length === 0 ? (
          <Banner kind="warn">이 과목의 taxonomy 단원을 찾지 못했어요.</Banner>
        ) : (
          <div className="col" style={{ gap: 6 }}>
            {opts.map(u => (
              <label key={u} className="checkbox">
                <input type="checkbox" checked={draft.includes(u)} onChange={() => toggle(u)} />
                <span>{u}</span>
              </label>
            ))}
          </div>
        )}
      </Modal>
    </>
  );
}

// ---- ProblemCard ----------------------------------------------------------
// One problem in the grid. Composes head + (optional) unit editor + crop.
// `mode = "view" | "select"` — select mode adds a primary toggle button.
function ProblemCard({
  problem, mode = "view", selected = false, onToggle, showUnitEditor = false,
  onSaveUnits, source, showAnswer = true, scoringPaths,
  editable = false, onEditAnswer, onEditSubject,
}) {
  const isFrq = problem.qtype === "FRQ";
  const [zoom, setZoom] = useState(false);
  return (
    <div
      className={"problem " + (mode === "select" ? "selectable " : "") + (selected ? "selected" : "")}
      onClick={mode === "select" ? onToggle : undefined}
    >
      <button
        className="icon-btn prob-zoom" title="전체화면으로 보기"
        onClick={e => { e.stopPropagation(); setZoom(true); }}
      ><Icon.Expand /></button>
      <Lightbox
        open={zoom} src={problem.image_url}
        title={`${problem.qtype} Q${problem.qnum}${source ? " · " + source : ""}`}
        onClose={() => setZoom(false)}
      />
      <ProblemHead
        qnum={problem.qnum} qtype={problem.qtype} subject={problem.subject}
        answer={showAnswer ? problem.answer : null}
        difficulty={problem.difficulty}
        source={source}
        editable={editable}
        onEditAnswer={onEditAnswer}
        onEditSubject={onEditSubject}
        calculator={problem.calculator}
        sectionPart={problem.section_part}
      />
      {showUnitEditor && (
        <div onClick={e => e.stopPropagation()}>
          <UnitEditor problem={problem} onSave={onSaveUnits} />
        </div>
      )}
      {!showUnitEditor && problem.units && problem.units.length > 0 && (
        <div className="meta">{problem.units.join(" · ")}</div>
      )}
      {source && <div className="meta mono">{source}</div>}
      {problem._stim_first && (
        <div className="meta">↳ {problem._stim_label}</div>
      )}
      <CropImg
        src={problem.image_url}
        kind={isFrq ? "frq" : "mcq"}
        label={`${problem.qtype} Q${problem.qnum}`}
      />
      {(() => {
        const sc = problem.scoring_urls || (scoringPaths || []).map(s => s && s.crop_url).filter(Boolean);
        return sc.length > 0 && (
          <details>
            <summary className="meta">Scoring Guide ({sc.length} pages)</summary>
            <div className="col" style={{ marginTop: 8 }}>
              {sc.map((u, i) => (
                <CropImg key={i} src={u} kind="frq" label={`scoring ${i + 1}/${sc.length}`} />
              ))}
            </div>
          </details>
        );
      })()}
      {mode === "select" && (
        <div className="cluster" style={{ justifyContent: "space-between" }}>
          <span className="small" style={{ color: selected ? "var(--navy)" : "var(--text-3)", fontWeight: selected ? 600 : 400 }}>
            {selected ? "선택됨" : "선택하기"}
          </span>
        </div>
      )}
    </div>
  );
}

// ---- useStore — subscribe component to global store ----------------------
function useStore() {
  const [, force] = useState(0);
  useEffect(() => window.storeSubscribe(() => force(n => n + 1)), []);
  return window.storeSnapshot();
}

// ---- expose ---------------------------------------------------------------
Object.assign(window, {
  Tabs, Segmented, Button, Field, EmptyState, Banner, Badge, Metric, Card, Modal,
  Lightbox, Progress, CropPlaceholder, CropImg, ProblemHead, UnitEditor, ProblemCard, useStore,
});
