/* ────────────────────────────────────────────────────────────
   export.jsx — 원고 업로드 + 내보내기(PPTX) 패널 + 마스터 교체 재내보내기(리스킨)
   CONTRACT:
     §3.4 POST /api/projects/:id/upload   multipart field `file`=원고.pdf
            -> 201 {asset:{id,kind:"source_pdf",bytes,mime}, sourceFileName}
     §3.8 POST /api/projects/:id/export   {masterId?}  -> 202 {job:{id,type:"pptx_export",status}}
          GET  /api/projects/:id/exports  -> 200 {items:[{id,format,masterId,bytes,createdAt}]}
          GET  /api/exports/:exportId/download -> 200 attachment 바이너리
     §3.7 GET  /api/jobs/:id  -> 200 {job:{id,type,status,progress,result,error}}
                                 status: queued|running|succeeded|failed
                                 succeeded 시 result:{exportId}
     §3.2 PATCH /api/projects/:id {masterId} (리스킨 — 마스터만 교체)
   해요체·이모지 없음·플랫·tokens 변수만.
   ──────────────────────────────────────────────────────────── */

/* ── job 폴링 헬퍼 — GET /api/jobs/:id 를 1.2s 간격으로, 종료 상태까지 ──
   onProgress(job) 콜백으로 진행률 전달. cancelled ref 로 언마운트 시 중단. */
function pollJob(jobId, { onProgress, intervalMs = 1200, timeoutMs = 180000 } = {}) {
  const startedAt = Date.now();
  let cancelled = false;
  const promise = new Promise((resolve, reject) => {
    async function tick() {
      if (cancelled) return;
      let job;
      try {
        const data = await api(`/jobs/${jobId}`);     // GET /api/jobs/:id
        job = data && data.job;
      } catch (ex) {
        return reject(ex);
      }
      if (cancelled) return;
      if (job && typeof onProgress === "function") onProgress(job);
      const status = job && job.status;
      if (status === "succeeded") return resolve(job);
      if (status === "failed") {
        const e = new Error((job && job.error) || "내보내기에 실패했어요.");
        e.job = job;
        return reject(e);
      }
      if (Date.now() - startedAt > timeoutMs) {
        return reject(new Error("내보내기가 너무 오래 걸려요. 잠시 후 다시 시도해 주세요."));
      }
      setTimeout(tick, intervalMs);                    // 1~2초 폴링
    }
    tick();
  });
  promise.cancel = () => { cancelled = true; };
  return promise;
}

/* exportId → 실제 파일 다운로드 트리거 (real <a download> / location).
   §3.8 GET /api/exports/:exportId/download (Content-Disposition attachment). */
function triggerExportDownload(exportId) {
  if (!exportId) return;
  const url = `/api/exports/${exportId}/download`;
  const a = document.createElement("a");
  a.href = url;
  a.download = "";                                     // 서버 Content-Disposition filename 사용
  a.rel = "noopener";
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
}

/* 진행률 막대 */
function ProgressBar({ value }) {
  const pct = Math.max(0, Math.min(100, typeof value === "number" ? value : 0));
  return React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 5, width: "100%" } },
    React.createElement("div", { style: { height: 8, borderRadius: "var(--r-full)", background: "var(--bg-alt)", overflow: "hidden" } },
      React.createElement("div", { style: { width: `${pct}%`, height: "100%", borderRadius: "var(--r-full)", background: "var(--primary)", transition: "width var(--t-base)" } })),
    React.createElement("span", { className: "t-caption-1 tnum", style: { color: "var(--label-alt)", alignSelf: "flex-end" } }, `${pct}%`));
}

function bytesLabel(b) {
  if (typeof b !== "number" || !isFinite(b)) return "";
  if (b < 1024) return `${b} B`;
  if (b < 1024 * 1024) return `${(b / 1024).toFixed(0)} KB`;
  return `${(b / 1024 / 1024).toFixed(1)} MB`;
}

/* ── 원고 PDF 업로드 (§3.4) ── */
function SourceUploader({ project, sourceFileName, onUploaded }) {
  const inputRef = useRef(null);
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState("");

  async function doUpload(file) {
    if (!file) return;
    if (!/pdf$/i.test(file.name || "") && file.type !== "application/pdf") {
      setErr("원고는 PDF 파일만 올릴 수 있어요.");
      return;
    }
    if (file.size > 20 * 1024 * 1024) {
      setErr("원고 PDF는 20MB 이하만 올릴 수 있어요.");
      return;
    }
    setErr(""); setBusy(true);
    try {
      const fd = new FormData();
      fd.append("file", file);                          // §3.4 field 이름 = file
      const data = await api(`/projects/${project.id}/upload`, { method: "POST", body: fd });
      onUploaded(data?.sourceFileName || file.name, data?.asset || null);
    } catch (ex) {
      setErr(ex.message || "원고를 업로드하지 못했어요.");
    } finally {
      setBusy(false);
      if (inputRef.current) inputRef.current.value = "";
    }
  }

  return React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 8 } },
    React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" } },
      React.createElement("label", { style: { ...btnStyle("outline", "sm"), cursor: busy ? "default" : "pointer" } },
        React.createElement("input", {
          ref: inputRef, type: "file", accept: "application/pdf,.pdf", disabled: busy,
          onChange: (e) => doUpload(e.target.files && e.target.files[0]), style: { display: "none" },
        }),
        busy ? React.createElement(Spinner, { s: 14 }) : 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: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12" })),
        busy ? "업로드 중" : (sourceFileName ? "원고 다시 올리기" : "원고 PDF 올리기")),
      sourceFileName
        ? React.createElement("span", { className: "t-label-2", style: { color: "var(--label-neutral)", display: "inline-flex", alignItems: "center", gap: 6 } },
            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" })),
            sourceFileName)
        : React.createElement("span", { className: "t-caption-1", style: { color: "var(--label-assist)" } }, "아직 원고가 없어요 · PDF 20MB 이하")),
    err ? React.createElement("div", { className: "t-label-2", style: { color: "var(--negative)", background: "var(--negative-bg)", borderRadius: "var(--r-sm)", padding: "8px 12px" } }, err) : null
  );
}

/* 내보내기 이력 행 — 다시 다운로드 */
function ExportHistoryRow({ item, masterName }) {
  return React.createElement("div", {
    style: { display: "flex", alignItems: "center", gap: 10, padding: "9px 12px", borderRadius: "var(--r-sm)", background: "var(--bg-neutral)", border: "1px solid var(--line-alt)" },
  },
    React.createElement("span", { style: { width: 26, height: 26, borderRadius: "var(--r-xs)", background: "var(--primary-light)", color: "var(--primary)", display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0 } },
      React.createElement("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }, React.createElement("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), React.createElement("path", { d: "M14 2v6h6" }))),
    React.createElement("div", { style: { minWidth: 0, display: "flex", flexDirection: "column", gap: 1 } },
      React.createElement("span", { className: "t-label-2", style: { color: "var(--label-normal)", fontWeight: 700 } },
        (item.format || "pptx").toUpperCase(),
        masterName ? React.createElement("span", { style: { color: "var(--label-alt)", fontWeight: 500 } }, ` · ${masterName}`) : (item.masterId ? React.createElement("span", { style: { color: "var(--label-alt)", fontWeight: 500 } }, " · 마스터 적용") : React.createElement("span", { style: { color: "var(--label-assist)", fontWeight: 500 } }, " · 기본 템플릿"))),
      React.createElement("span", { className: "t-caption-1 tnum", style: { color: "var(--label-assist)" } }, [fmtDate(item.createdAt), bytesLabel(item.bytes)].filter(Boolean).join(" · "))),
    React.createElement(Button, {
      kind: "ghost", size: "sm", style: { marginLeft: "auto" },
      onClick: () => triggerExportDownload(item.id),
    },
      React.createElement("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }, React.createElement("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3" })),
      "다운로드")
  );
}

/* ── 내보내기 패널 모달 ──
   props: project, masterActive({id,name}|null), masters([{id,name,...}]|null),
          sourceFileName, onSourceUploaded, onMasterChanged(masterId, projectUpdated), onOpenMasters, onClose */
function ExportPanelModal({ project, masterActive, masters, sourceFileName, onSourceUploaded, onMasterChanged, onOpenMasters, onClose }) {
  const [exports, setExports] = useState(null);          // null=로딩 (GET /exports)
  const [err, setErr] = useState("");
  const [phase, setPhase] = useState("idle");            // idle | running | done | error
  const [job, setJob] = useState(null);
  const [lastExportId, setLastExportId] = useState(null);
  const [reskinMasterId, setReskinMasterId] = useState(masterActive?.id || null);
  const pollRef = useRef(null);

  const loadExports = useCallback(async () => {
    try {
      const data = await api(`/projects/${project.id}/exports`);   // GET /api/projects/:id/exports
      setExports(Array.isArray(data?.items) ? data.items : []);
    } catch (ex) {
      setExports([]);
      // 이력 로드 실패는 치명적 아님 — 패널은 계속 사용 가능
    }
  }, [project.id]);
  useEffect(() => { loadExports(); }, [loadExports]);
  useEffect(() => { setReskinMasterId(masterActive?.id || null); }, [masterActive]);
  useEffect(() => () => { if (pollRef.current) pollRef.current.cancel(); }, []);

  const masterName = useCallback((id) => {
    if (!id) return null;
    if (masterActive && masterActive.id === id) return masterActive.name;
    const m = (masters || []).find((x) => x.id === id);
    return m ? m.name : null;
  }, [masters, masterActive]);

  // 핵심 내보내기 실행 — optionalMasterId 가 주어지면 그 마스터로(리스킨), 없으면 프로젝트 master
  async function runExport(optionalMasterId) {
    setErr(""); setPhase("running"); setJob(null); setLastExportId(null);
    try {
      // §3.8 POST /export {masterId?} -> 202 {job}
      const body = (optionalMasterId !== undefined) ? { masterId: optionalMasterId } : {};
      const data = await api(`/projects/${project.id}/export`, { method: "POST", body });
      const j = data && data.job;
      if (!j || !j.id) throw new Error("내보내기 작업을 시작하지 못했어요.");
      setJob(j);
      const poll = pollJob(j.id, { onProgress: (pj) => setJob(pj) });
      pollRef.current = poll;
      const finished = await poll;                          // succeeded
      const exportId = finished?.result?.exportId;
      setLastExportId(exportId || null);
      setPhase("done");
      if (exportId) triggerExportDownload(exportId);        // 자동 다운로드
      loadExports();
    } catch (ex) {
      setErr(ex.message || "내보내기에 실패했어요.");
      setPhase("error");
    }
  }

  // 마스터 교체 후 재내보내기 — PATCH masterId (§3.2) → POST /export (§3.8)
  async function reskinExport() {
    const targetId = reskinMasterId || null;
    setErr(""); setPhase("running"); setJob(null); setLastExportId(null);
    try {
      // 현재 프로젝트 마스터와 다르면 PATCH 로 교체(리스킨의 핵심)
      if (targetId !== (masterActive?.id || null)) {
        const { project: updated } = await api(`/projects/${project.id}`, { method: "PATCH", body: { masterId: targetId } });
        if (typeof onMasterChanged === "function") onMasterChanged(targetId, updated);
      }
      await runExport(targetId);
    } catch (ex) {
      setErr(ex.message || "마스터를 교체하지 못했어요.");
      setPhase("error");
    }
  }

  const running = phase === "running";
  const masterChoices = Array.isArray(masters) ? masters : [];
  const reskinChanged = (reskinMasterId || null) !== (masterActive?.id || null);

  return React.createElement("div", {
    onClick: running ? 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: 600, background: "var(--bg-normal)", borderRadius: "var(--r-lg)", boxShadow: "var(--sh-3)", padding: "26px 26px 24px", display: "flex", flexDirection: "column", gap: 18 },
    },
      /* 헤더 */
      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 } }, "PPTX 내보내기"),
          React.createElement("p", { className: "t-body-2", style: { color: "var(--label-alt)", margin: 0 } }, "지정한 마스터로 편집 가능한 PowerPoint를 만들어요. 마스터를 바꿔 다시 내보내면 같은 내용이 새 디자인으로 입혀져요.")),
        React.createElement("button", { onClick: onClose, disabled: running, "aria-label": "닫기", style: { ...btnStyle("ghost", "sm"), padding: "0 8px", opacity: running ? 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" })))),

      /* 원고 업로드(§3.4) */
      React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 8 } },
        React.createElement("span", { className: "t-label-1", style: { color: "var(--label-neutral)" } }, "원고"),
        React.createElement(SourceUploader, { project, sourceFileName, onUploaded: onSourceUploaded })),

      /* 현재 마스터 상태 */
      React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "12px 14px", borderRadius: "var(--r-md)", background: "var(--bg-neutral)", border: "1px solid var(--line-alt)" } },
        React.createElement("span", { className: "t-label-2", style: { color: "var(--label-alt)", fontWeight: 700 } }, "적용 마스터"),
        masterActive
          ? React.createElement(Pill, { color: "var(--primary)", bg: "var(--primary-light)" },
              React.createElement("span", { style: { width: 6, height: 6, borderRadius: "50%", background: "var(--primary)" } }), masterActive.name || "마스터")
          : React.createElement(Pill, { color: "var(--label-alt)", bg: "var(--bg-alt)" }, "기본 템플릿"),
        React.createElement(Button, { kind: "ghost", size: "sm", onClick: onOpenMasters, disabled: running, style: { marginLeft: "auto" } }, "마스터 관리")),

      /* 마스터 미지정 안내 */
      !masterActive ? React.createElement("div", { className: "t-caption-1", style: { color: "var(--label-alt)", background: "var(--caution-bg)", borderRadius: "var(--r-sm)", padding: "9px 12px" } },
        "마스터를 지정하지 않으면 기본 템플릿으로 내보내요. 마스터를 적용하면 테마와 레이아웃이 반영돼요.") : null,

      /* 기본 내보내기 버튼 + 진행률 */
      React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } },
        React.createElement(Button, { size: "lg", disabled: running, onClick: () => runExport(undefined), 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: 17, height: 17, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }, React.createElement("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3" })),
                "PPTX 내보내기")),

        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" ? "작업을 준비하고 있어요" : "PowerPoint를 만드는 중이에요. 잠시만 기다려 주세요.")) : 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, justifyContent: "space-between" } },
          React.createElement("span", { style: { display: "inline-flex", alignItems: "center", gap: 7 } },
            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" })),
            "내보내기가 끝났어요. 다운로드가 시작돼요."),
          lastExportId ? React.createElement("a", { href: `/api/exports/${lastExportId}/download`, style: { color: "var(--primary)", fontWeight: 700, textDecoration: "underline" } }, "다시 받기") : null) : null,

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

      /* ── 마스터만 바꿔 다시 내보내기 (리스킨 — 핵심 affordance) ── */
      React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 10, padding: "14px 16px", borderRadius: "var(--r-md)", background: "var(--blue-95)", border: "1px solid var(--primary-assist)" } },
        React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } },
          React.createElement("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "var(--primary)", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }, React.createElement("path", { d: "M3 2v6h6M21 12A9 9 0 0 0 6 5.3L3 8M21 22v-6h-6M3 12a9 9 0 0 0 15 6.7l3-2.7" })),
          React.createElement("span", { className: "t-label-1", style: { color: "var(--primary)" } }, "마스터만 바꿔 다시 내보내기")),
        React.createElement("p", { className: "t-caption-1", style: { color: "var(--label-neutral)", margin: 0, lineHeight: 1.55 } }, "내용은 그대로 두고 디자인(마스터)만 바꿔 새 PPTX를 만들어요. 편집을 다시 할 필요가 없어요."),
        React.createElement("div", { style: { display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center" } },
          React.createElement("select", {
            value: reskinMasterId || "",
            disabled: running,
            onChange: (e) => setReskinMasterId(e.target.value || null),
            style: { flex: 1, minWidth: 180, height: 38, padding: "0 12px", borderRadius: "var(--r-sm)", border: "1px solid var(--border)", background: "var(--bg-normal)", color: "var(--label-normal)", fontSize: 14, fontFamily: "var(--font)", outline: "none", cursor: running ? "default" : "pointer" },
          },
            React.createElement("option", { value: "" }, "기본 템플릿(마스터 없음)"),
            masterChoices.map((m) => React.createElement("option", { key: m.id, value: m.id }, m.name || "제목 없는 마스터"))),
          React.createElement(Button, {
            kind: "primary", disabled: running, onClick: reskinExport,
          }, reskinChanged ? "교체하고 내보내기" : "이 마스터로 내보내기")),
        masterChoices.length === 0
          ? React.createElement("span", { className: "t-caption-1", style: { color: "var(--label-alt)" } },
              "교체할 마스터가 없어요. ",
              React.createElement("a", { href: "#", onClick: (e) => { e.preventDefault(); onOpenMasters && onOpenMasters(); }, style: { color: "var(--primary)", fontWeight: 700 } }, "마스터 올리기"))
          : null),

      /* 내보내기 이력(§3.8) */
      React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 8 } },
        React.createElement("span", { className: "t-label-1", style: { color: "var(--label-neutral)" } }, "내보내기 이력"),
        exports === null
          ? React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "8px 2px" } }, React.createElement(Spinner, { s: 15 }), React.createElement("span", { className: "t-caption-1", style: { color: "var(--label-alt)" } }, "불러오는 중이에요"))
          : exports.length === 0
            ? React.createElement("span", { className: "t-caption-1", style: { color: "var(--label-assist)" } }, "아직 내보낸 파일이 없어요.")
            : React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } },
                exports.map((it) => React.createElement(ExportHistoryRow, { key: it.id, item: it, masterName: masterName(it.masterId) })))
      )
    )
  );
}

Object.assign(window, {
  ExportPanelModal, SourceUploader, ExportHistoryRow, ProgressBar,
  pollJob, triggerExportDownload, bytesLabel,
});
