/* ────────────────────────────────────────────────────────────
   versions.jsx — 버전 스냅샷(저장/목록/복원) + AI 파싱 트리거
   Wave C. CONTRACT:
     §3.9 POST /api/projects/:id/versions               {label?}  -> 201 {version:{id,label,createdAt}}
          GET  /api/projects/:id/versions                          -> 200 {items:[{id,label,createdAt}]}
          POST /api/projects/:id/versions/:versionId/restore {}    -> 200 {ok:true, slides:[...]}
     §3.6 POST /api/projects/:id/ai/parse  {assetId?}              -> 202 {job:{id,type:"parse_pdf",status,projectId}}
     §3.7 GET  /api/jobs/:id                                       -> 200 {job:{id,type,status,progress,result,error}}
            실패 시 status:"failed", error:"<해요체 안내>"  (예: AI 키 미등록)
            성공 시 status:"succeeded", result:{slideCount}
   pollJob/ProgressBar 는 export.jsx 의 것을 재사용(window).
   AI 파싱 실패(키 미등록 등)는 job.error 를 화면이 그대로(verbatim) 보여줘요. 백지로 만들지 않아요.
   해요체·이모지 없음·플랫·tokens 변수만.
   ──────────────────────────────────────────────────────────── */

/* ── 버전 관리 모달 (§3.9) ──
   props: project, onRestored(slides)=>void, onClose */
function VersionsModal({ project, onRestored, onClose }) {
  const [items, setItems] = useState(null);       // null=로딩
  const [label, setLabel] = useState("");
  const [saving, setSaving] = useState(false);
  const [restoringId, setRestoringId] = useState(null);
  const [err, setErr] = useState("");
  const [note, setNote] = useState("");

  const load = useCallback(async () => {
    setErr("");
    try {
      const data = await api(`/projects/${project.id}/versions`);   // GET versions
      setItems(Array.isArray(data?.items) ? data.items : []);
    } catch (ex) {
      setItems([]);
      setErr(ex.message || "버전 목록을 불러오지 못했어요.");
    }
  }, [project.id]);
  useEffect(() => { load(); }, [load]);

  async function saveSnapshot() {
    setErr(""); setNote(""); setSaving(true);
    try {
      // POST versions {label}
      const body = label.trim() ? { label: label.trim() } : {};
      await api(`/projects/${project.id}/versions`, { method: "POST", body });
      setLabel("");
      setNote("현재 상태를 버전으로 저장했어요.");
      load();
    } catch (ex) {
      setErr(ex.message || "버전을 저장하지 못했어요.");
    } finally {
      setSaving(false);
    }
  }

  async function restore(v) {
    if (restoringId) return;
    setErr(""); setNote(""); setRestoringId(v.id);
    try {
      // POST versions/:versionId/restore {} -> {ok, slides}
      const data = await api(`/projects/${project.id}/versions/${v.id}/restore`, { method: "POST", body: {} });
      const slides = Array.isArray(data?.slides) ? data.slides : null;
      setNote("이 버전으로 되돌렸어요.");
      if (typeof onRestored === "function") onRestored(slides);
    } catch (ex) {
      setErr(ex.message || "버전을 복원하지 못했어요.");
    } finally {
      setRestoringId(null);
    }
  }

  const busy = saving || !!restoringId;

  return React.createElement(ModalShell, { title: "버전 관리", desc: "지금 상태를 버전으로 저장하거나, 이전 버전으로 되돌릴 수 있어요.", onClose: busy ? undefined : onClose, busy, maxWidth: 520 },
    /* 저장 */
    React.createElement("div", { style: { display: "flex", gap: 8 } },
      React.createElement("input", {
        value: label,
        placeholder: "버전 이름 (선택) · 예: 검토본",
        disabled: saving,
        onChange: (e) => setLabel(e.target.value),
        onKeyDown: (e) => { if (e.key === "Enter") saveSnapshot(); },
        style: { flex: 1, height: 40, padding: "0 13px", borderRadius: "var(--r-sm)", border: "1px solid var(--border)", background: "var(--bg-normal)", fontSize: 14, color: "var(--label-normal)", fontFamily: "var(--font)", outline: "none" },
      }),
      React.createElement(Button, { onClick: saveSnapshot, disabled: saving },
        saving ? React.createElement(React.Fragment, null, React.createElement(Spinner, { s: 14, color: "#fff" }), "저장 중") : "버전 저장")),

    note ? React.createElement("div", { className: "t-label-2", style: { color: "var(--positive)", background: "var(--positive-bg)", borderRadius: "var(--r-sm)", padding: "9px 13px" } }, note) : null,
    err ? React.createElement("div", { className: "t-label-2", style: { color: "var(--negative)", background: "var(--negative-bg)", borderRadius: "var(--r-sm)", padding: "9px 13px" } }, err) : null,

    /* 목록 */
    React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 8 } },
      React.createElement("span", { className: "t-label-1", style: { color: "var(--label-neutral)" } }, "저장된 버전"),
      items === null
        ? React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "6px 2px" } }, React.createElement(Spinner, { s: 15 }), React.createElement("span", { className: "t-caption-1", style: { color: "var(--label-alt)" } }, "불러오는 중이에요"))
        : items.length === 0
          ? React.createElement("span", { className: "t-caption-1", style: { color: "var(--label-assist)" } }, "아직 저장된 버전이 없어요.")
          : React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6, maxHeight: 320, overflowY: "auto" } },
              items.map((v) => React.createElement("div", {
                key: v.id,
                style: { display: "flex", alignItems: "center", gap: 10, padding: "10px 13px", borderRadius: "var(--r-sm)", background: "var(--bg-neutral)", border: "1px solid var(--line-alt)" },
              },
                React.createElement("span", { style: { width: 28, height: 28, borderRadius: "var(--r-xs)", background: "var(--primary-light)", color: "var(--primary)", display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0 } },
                  React.createElement("svg", { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }, React.createElement("path", { d: "M12 8v4l3 3" }), React.createElement("circle", { cx: 12, cy: 12, r: 9 }))),
                React.createElement("div", { style: { minWidth: 0, display: "flex", flexDirection: "column", gap: 1, flex: 1 } },
                  React.createElement("span", { className: "t-label-2", style: { color: "var(--label-normal)", fontWeight: 700, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, v.label || "이름 없는 버전"),
                  React.createElement("span", { className: "t-caption-1 tnum", style: { color: "var(--label-assist)" } }, fmtDate(v.createdAt))),
                React.createElement(Button, {
                  kind: "outline", size: "sm", disabled: busy,
                  onClick: () => restore(v),
                }, restoringId === v.id ? React.createElement(React.Fragment, null, React.createElement(Spinner, { s: 13 }), "복원 중") : "이 버전으로 되돌리기"))))),

    React.createElement("p", { className: "t-caption-1", style: { color: "var(--label-alt)", margin: 0, lineHeight: 1.55 } }, "되돌리면 현재 슬라이드가 선택한 버전의 내용으로 교체돼요. 되돌리기 전에 지금 상태도 버전으로 저장해 두면 안전해요.")
  );
}

/* ── AI 파싱 트리거 모달 (§3.6 + §3.7) ──
   원고 PDF 선택(업로드)을 모달에 내장해 패널 이동 없이 한 번에 원고 올리고 바로 AI 생성까지 끝내요.
   업로드는 export.jsx 의 SourceUploader(window) 를 재사용 — §3.4 POST /upload, PDF/20MB 검증·해요체 에러 동일.
   props: project, sourceFileName, onParsed()=>void(슬라이드 reload),
          onUploaded(name, asset)=>void(에디터 메타 sourceFileName 동기화), onClose */
function AiParseModal({ project, sourceFileName, onParsed, onUploaded, onClose }) {
  const [phase, setPhase] = useState("idle");   // idle | running | done | error
  const [job, setJob] = useState(null);
  const [err, setErr] = useState("");           // job.error 를 그대로(verbatim) 담아요
  const [result, setResult] = useState(null);
  // 모달 로컬 원고 상태 — 방금 올린 파일명을 즉시 반영(에디터 prop 갱신 전에도 버튼 활성)
  const [sourceName, setSourceName] = useState(sourceFileName || null);
  const pollRef = useRef(null);
  useEffect(() => () => { if (pollRef.current) pollRef.current.cancel(); }, []);
  // prop 이 갱신되면(에디터 loadProject 등) 로컬 상태도 따라가요
  useEffect(() => { if (sourceFileName) setSourceName(sourceFileName); }, [sourceFileName]);

  // 인라인 업로드 성공 — 로컬 원고 상태 즉시 갱신 + 에디터 메타 동기화 콜백
  function handleUploaded(name, asset) {
    setSourceName(name || null);
    if (phase === "error") { setErr(""); setPhase("idle"); }   // 새 원고를 올리면 이전 실패 안내는 비워요
    if (typeof onUploaded === "function") onUploaded(name, asset);
  }

  async function runParse() {
    setErr(""); setResult(null); setJob(null); setPhase("running");
    try {
      // §3.6 POST ai/parse {assetId?} -> 202 {job}  (assetId 생략 시 서버가 최신 source_pdf 사용)
      const data = await api(`/projects/${project.id}/ai/parse`, { method: "POST", body: {} });
      const j = data && data.job;
      if (!j || !j.id) throw new Error("AI 분석 작업을 시작하지 못했어요.");
      setJob(j);
      // pollJob: export.jsx 재사용. 실패 시 reject(e) 의 e.message = job.error
      const poll = pollJob(j.id, { onProgress: (pj) => setJob(pj), timeoutMs: 300000 });
      pollRef.current = poll;
      const finished = await poll;          // succeeded
      setResult(finished?.result || null);
      setPhase("done");
      if (typeof onParsed === "function") onParsed();
    } catch (ex) {
      // ★ 핵심: 실패(예: AI 키 미등록) 시 job 의 해요체 error 를 그대로 노출. 화면 백지 금지.
      setErr((ex && ex.message) || "AI 분석에 실패했어요.");
      setPhase("error");
    }
  }

  const running = phase === "running";
  const slideCount = result && (typeof result.slideCount === "number" ? result.slideCount : null);

  return React.createElement(ModalShell, {
    title: "AI로 스토리보드 생성", desc: "원고 PDF를 올리면 분석해 슬라이드를 자동으로 만들어요. 나레이션 원문은 그대로 보존돼요.",
    onClose: running ? undefined : onClose, busy: running, maxWidth: 520,
  },
    /* 원고 상태 + 인라인 업로드 — 패널 이동 없이 이 모달에서 바로 올려요 */
    React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 10, padding: "12px 14px", borderRadius: "var(--r-md)", background: "var(--bg-neutral)", border: "1px solid var(--line-alt)" } },
      React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } },
        React.createElement("span", { className: "t-label-2", style: { color: "var(--label-alt)", fontWeight: 700 } }, "원고"),
        sourceName
          ? React.createElement("span", { className: "t-label-2", style: { color: "var(--label-neutral)", display: "inline-flex", alignItems: "center", gap: 6, minWidth: 0 } },
              React.createElement("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "var(--positive)", strokeWidth: 2.4, strokeLinecap: "round", strokeLinejoin: "round" }, React.createElement("path", { d: "M20 6 9 17l-5-5" })),
              React.createElement("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, sourceName))
          : React.createElement(Pill, { color: "var(--caution)", bg: "var(--caution-bg)" }, "원고 없음")),
      /* SourceUploader(export.jsx, window 재사용) — PDF/20MB 검증·해요체 에러·업로드 중 표시 내장 */
      React.createElement(SourceUploader, { project, sourceFileName: sourceName, onUploaded: handleUploaded })),

    !sourceName ? React.createElement("div", { className: "t-caption-1", style: { color: "var(--label-alt)", background: "var(--caution-bg)", borderRadius: "var(--r-sm)", padding: "9px 12px" } },
      "먼저 원고 PDF를 올려 주세요. 원고가 있어야 AI가 슬라이드를 만들 수 있어요.") : null,

    /* 실행 버튼 */
    React.createElement(Button, { size: "lg", disabled: running || !sourceName, onClick: runParse, style: { width: "100%" } },
      running
        ? React.createElement(React.Fragment, null, React.createElement(Spinner, { s: 16, color: "#fff" }), "분석 중…")
        : React.createElement(React.Fragment, null,
            React.createElement("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }, React.createElement("path", { d: "M12 3v3M12 18v3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M3 12h3M18 12h3M5.6 18.4l2.1-2.1M16.3 7.7l2.1-2.1" })),
            "AI로 스토리보드 생성")),

    running ? React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } },
      React.createElement(ProgressBar, { value: job?.progress }),
      React.createElement("span", { className: "t-caption-1", style: { color: "var(--label-alt)", textAlign: "center" } },
        job?.status === "queued" ? "작업을 준비하고 있어요" : "원고를 분석해 슬라이드를 만드는 중이에요. 잠시만 기다려 주세요.")) : null,

    phase === "done" ? React.createElement("div", { className: "t-label-2", style: { color: "var(--positive)", background: "var(--positive-bg)", borderRadius: "var(--r-sm)", padding: "10px 14px", display: "flex", alignItems: "center", gap: 8 } },
      React.createElement("svg", { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.4, strokeLinecap: "round", strokeLinejoin: "round" }, React.createElement("path", { d: "M20 6 9 17l-5-5" })),
      slideCount !== null ? `슬라이드 ${slideCount}장을 만들었어요. 편집기에 반영했어요.` : "분석이 끝났어요. 편집기에 반영했어요.") : null,

    /* ★ 실패: job.error 를 그대로(verbatim) 노출. AI 키 미등록 등 안내를 백지 없이 보여줘요. */
    phase === "error" ? React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 8, padding: "12px 14px", borderRadius: "var(--r-sm)", background: "var(--negative-bg)", border: "1px solid var(--negative)" } },
      React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 7 } },
        React.createElement("svg", { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "var(--negative)", strokeWidth: 2.2, strokeLinecap: "round", strokeLinejoin: "round" }, React.createElement("circle", { cx: 12, cy: 12, r: 10 }), React.createElement("path", { d: "M12 8v4M12 16h.01" })),
        React.createElement("span", { className: "t-label-1", style: { color: "var(--negative)" } }, "AI 분석을 끝내지 못했어요")),
      React.createElement("p", { className: "t-label-2", style: { color: "var(--label-neutral)", margin: 0, lineHeight: 1.6, whiteSpace: "pre-wrap" } }, err),
      React.createElement(Button, { kind: "outline", size: "sm", onClick: runParse, disabled: !sourceName, style: { alignSelf: "flex-start" } }, "다시 시도")) : null
  );
}

/* ── 공용 모달 셸 ── */
function ModalShell({ title, desc, onClose, busy, maxWidth = 540, children }) {
  return React.createElement("div", {
    onClick: busy ? undefined : onClose,
    style: { position: "fixed", inset: 0, background: "rgba(20,25,30,0.46)", display: "flex", alignItems: "flex-start", justifyContent: "center", zIndex: 120, padding: "40px 24px", overflow: "auto" },
  },
    React.createElement("div", {
      onClick: (e) => e.stopPropagation(),
      style: { width: "100%", maxWidth, background: "var(--bg-normal)", borderRadius: "var(--r-lg)", boxShadow: "var(--sh-3)", padding: "26px 26px 24px", display: "flex", flexDirection: "column", gap: 16 },
    },
      React.createElement("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 12 } },
        React.createElement("div", null,
          React.createElement("div", { className: "t-heading-1", style: { marginBottom: 4 } }, title),
          desc ? React.createElement("p", { className: "t-body-2", style: { color: "var(--label-alt)", margin: 0 } }, desc) : null),
        onClose ? React.createElement("button", { onClick: onClose, disabled: busy, "aria-label": "닫기", style: { ...btnStyle("ghost", "sm"), padding: "0 8px", opacity: busy ? 0.5 : 1 } },
          React.createElement("svg", { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round" }, React.createElement("path", { d: "M18 6 6 18M6 6l12 12" }))) : null),
      children)
  );
}

Object.assign(window, { VersionsModal, AiParseModal, ModalShell });
