/* ────────────────────────────────────────────────────────────
   slidepreview.jsx — "레이아웃이 적용된 실제 슬라이드 미리보기" (앱 핵심 가치)
   슬라이드 데이터(title·content[]·narration·layout_suggestion·steps_style·
   card_count·diagram_subtype·content_density·illustration_*)를 받아
   14종 레이아웃별로 16:9 슬라이드처럼 HTML/CSS 로 렌더한다. 프론트 전용.

   정본 시각 구조: pptx-service/renderers/*.py 의 개념만 차용(픽셀 일치 불필요).
     - TITLE 중앙 큰 제목 / CONCEPT 정의·예시 박스 / STEPS 번호원+화살표
     - COMPARE 좌우 2색 / CASE_STUDY 장면·원인·예방 3색 / CHECKLIST 체크박스
     - HUB diagram_subtype(hub/flow/tree/cycle/cause) SVG 도식 / EMPHASIS 큰 강조
     - SUMMARY 번호 요점 / TEXT_ONLY 불릿 / TEXT_IMAGE 좌텍스트+우이미지
     - TABLE 행 표 / CARD card_count 카드그리드 / QUIZ 문제·보기·정답
   미지정/누락 → TEXT_ONLY 폴백. 빈 슬라이드도 깨지지 않게.

   토큰(tokens.css 변수)만 사용. 작은(카드)·큰(패널) 크기 모두 읽히게
   container-query 로 폰트/여백을 컨테이너 비례(cqw)로. 넘치면 clamp/말줄임.
   해요체·이모지 없음·플랫. React.createElement·window 노출 패턴.
   ──────────────────────────────────────────────────────────── */

const h = React.createElement;

/* CASE_STUDY 3색 (palette.py 도메인 고정색 차용) — 장면/원인/예방 */
const SP_CASE = [
  { label: "장면", bg: "#f59e0b" },
  { label: "원인", bg: "#ef4444" },
  { label: "예방", bg: "#10b981" },
];
/* COMPARE 2색 (palette.py COMPARE_L/R 차용) — 파랑/주황 */
const SP_COMPARE = [
  { bg: "var(--primary)", tx: "#fff" },
  { bg: "#f59e0b", tx: "#fff" },
];

/* content 를 안전 배열로 */
function spList(content) {
  return Array.isArray(content) ? content.filter((x) => x != null && String(x).trim() !== "").map(String) : [];
}

/* clamp 멀티라인 말줄임 스타일 */
function spClamp(n) {
  return { display: "-webkit-box", WebkitLineClamp: n, WebkitBoxOrient: "vertical", overflow: "hidden" };
}

/* 슬라이드 본문 제목바(상단) — 대부분 레이아웃 공통 */
function SPTitleBar(title, color) {
  return h("div", { style: { display: "flex", alignItems: "center", gap: "1.2cqw", marginBottom: "2.2cqh", flexShrink: 0 } },
    h("span", { style: { width: "0.9cqw", minWidth: 3, alignSelf: "stretch", borderRadius: 2, background: color || "var(--primary)" } }),
    h("span", {
      style: { fontSize: "5cqw", fontWeight: 800, color: "var(--label-normal)", lineHeight: 1.2, ...spClamp(2) },
    }, title || h("span", { style: { color: "var(--label-assist)" } }, "제목 없음")));
}

/* 빈 콘텐츠 안내(슬라이드 깨짐 방지) */
function SPEmpty(msg) {
  return h("div", {
    style: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", color: "var(--label-assist)", fontSize: "3.6cqw", fontWeight: 600, textAlign: "center", padding: "0 4cqw" },
  }, msg || "내용을 입력하면 여기에 미리보기가 보여요.");
}

/* ── 레이아웃별 본문 렌더 ── */

function SPTitle(slide) {
  const sub = spList(slide.content);
  return h("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "2.5cqh", textAlign: "center", padding: "0 6cqw" } },
    h("div", { style: { fontSize: "8.5cqw", fontWeight: 800, color: "var(--label-normal)", lineHeight: 1.18, ...spClamp(3) } }, slide.title || "제목 없음"),
    sub.length
      ? h("div", { style: { display: "flex", flexWrap: "wrap", gap: "1.4cqw", justifyContent: "center" } },
          sub.slice(0, 4).map((c, i) => h("span", {
            key: i, style: { fontSize: "3.8cqw", fontWeight: 700, color: "var(--primary)", background: "var(--primary-light)", padding: "0.8cqh 2.4cqw", borderRadius: "var(--r-full)" },
          }, c)))
      : null);
}

function SPConcept(slide) {
  const list = spList(slide.content);
  const def = list[0];
  const examples = list.slice(1);
  return h("div", { style: { flex: 1, display: "flex", flexDirection: "column", minHeight: 0 } },
    SPTitleBar(slide.title, "var(--primary)"),
    def == null
      ? SPEmpty("개념 정의를 입력해요.")
      : h("div", { style: { flex: 1, display: "flex", flexDirection: "column", gap: "1.6cqh", minHeight: 0 } },
          h("div", { style: { background: "var(--label-normal)", color: "#fff", borderRadius: "var(--r-md)", padding: "2.6cqh 3cqw", flex: examples.length ? "0 0 auto" : 1, display: "flex", flexDirection: "column", justifyContent: "center", gap: "0.8cqh" } },
            h("span", { style: { fontSize: "3cqw", fontWeight: 800, opacity: 0.7, letterSpacing: "0.04em" } }, "정의"),
            h("span", { style: { fontSize: "4.4cqw", fontWeight: 700, lineHeight: 1.4, ...spClamp(3) } }, def)),
          examples.length
            ? h("div", { style: { flex: 1, background: "var(--bg-alt)", border: "1px solid var(--border)", borderRadius: "var(--r-md)", padding: "2cqh 3cqw", display: "flex", flexDirection: "column", gap: "0.8cqh", minHeight: 0, overflow: "hidden" } },
                h("span", { style: { fontSize: "3cqw", fontWeight: 800, color: "var(--label-alt)", letterSpacing: "0.04em" } }, "예시"),
                examples.slice(0, 4).map((e, i) => h("span", { key: i, style: { fontSize: "3.8cqw", color: "var(--label-neutral)", lineHeight: 1.4, ...spClamp(1) } }, "· " + e)))
            : null));
}

function SPSteps(slide) {
  const steps = spList(slide.content);
  const numbered = (slide.steps_style || "arrow") === "numbered";
  return h("div", { style: { flex: 1, display: "flex", flexDirection: "column", minHeight: 0 } },
    SPTitleBar(slide.title, "var(--primary)"),
    steps.length === 0
      ? SPEmpty("단계 내용을 입력해요.")
      : h("div", { style: { flex: 1, display: "flex", alignItems: "stretch", justifyContent: "center", gap: numbered ? "2cqw" : "0.6cqw", minHeight: 0 } },
          steps.slice(0, 5).map((st, i, arr) =>
            h(React.Fragment, { key: i },
              h("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: "1.2cqh", minWidth: 0 } },
                h("span", { style: { width: "8.5cqw", height: "8.5cqw", minWidth: 22, borderRadius: "50%", background: "var(--primary)", color: "#fff", fontSize: "4.4cqw", fontWeight: 800, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 } }, String(i + 1)),
                h("span", { style: { fontSize: "3.4cqw", fontWeight: 600, color: "var(--label-neutral)", textAlign: "center", lineHeight: 1.35, ...spClamp(3) } }, st)),
              !numbered && i < arr.length - 1
                ? h("span", { style: { alignSelf: "flex-start", marginTop: "2.6cqh", color: "var(--label-disable)", fontSize: "5cqw", fontWeight: 700, flexShrink: 0 } }, "→")
                : null))));
}

function SPCompare(slide) {
  const list = spList(slide.content);
  let leftItems, rightItems;
  if (list.length <= 1) { leftItems = list; rightItems = []; }
  else { const mid = Math.ceil(list.length / 2); leftItems = list.slice(0, mid); rightItems = list.slice(mid); }
  const col = (items, c) => h("div", { style: { flex: 1, background: c.bg, color: c.tx, borderRadius: "var(--r-md)", padding: "2.2cqh 2.6cqw", display: "flex", flexDirection: "column", gap: "1cqh", minWidth: 0, overflow: "hidden" } },
    items.length === 0
      ? h("span", { style: { margin: "auto", fontSize: "5cqw", opacity: 0.8 } }, "—")
      : items.slice(0, 5).map((x, i) => h("span", { key: i, style: { fontSize: i === 0 ? "4.2cqw" : "3.6cqw", fontWeight: i === 0 ? 800 : 600, lineHeight: 1.35, ...spClamp(2) } }, (i === 0 ? "" : "· ") + x)));
  return h("div", { style: { flex: 1, display: "flex", flexDirection: "column", minHeight: 0 } },
    SPTitleBar(slide.title, "var(--violet)"),
    list.length === 0
      ? SPEmpty("비교할 두 항목을 입력해요.")
      : h("div", { style: { flex: 1, display: "flex", gap: "2cqw", minHeight: 0 } }, col(leftItems, SP_COMPARE[0]), col(rightItems, SP_COMPARE[1])));
}

function SPCaseStudy(slide) {
  const list = spList(slide.content);
  const buckets = [[], [], []];
  list.forEach((item, i) => buckets[Math.min(i, 2)].push(item));
  return h("div", { style: { flex: 1, display: "flex", flexDirection: "column", minHeight: 0 } },
    SPTitleBar(slide.title, "#ef4444"),
    h("div", { style: { flex: 1, display: "flex", gap: "1.6cqw", minHeight: 0 } },
      SP_CASE.map((sec, i) => h("div", { key: i, style: { flex: 1, background: sec.bg, color: "#fff", borderRadius: "var(--r-md)", padding: "1.8cqh 2cqw", display: "flex", flexDirection: "column", gap: "0.8cqh", minWidth: 0, overflow: "hidden" } },
        h("span", { style: { fontSize: "3.8cqw", fontWeight: 800 } }, sec.label),
        buckets[i].slice(0, 4).map((it, j) => h("span", { key: j, style: { fontSize: "3.2cqw", fontWeight: 500, lineHeight: 1.3, ...spClamp(2) } }, "· " + it))))));
}

function SPChecklist(slide) {
  const items = spList(slide.content);
  return h("div", { style: { flex: 1, display: "flex", flexDirection: "column", minHeight: 0 } },
    SPTitleBar(slide.title, "var(--positive)"),
    items.length === 0
      ? SPEmpty("점검 항목을 입력해요.")
      : h("div", { style: { flex: 1, display: "flex", flexDirection: "column", gap: "1.2cqh", minHeight: 0, overflow: "hidden" } },
          items.slice(0, 6).map((it, i) => h("div", { key: i, style: { display: "flex", alignItems: "center", gap: "2cqw" } },
            h("span", { style: { width: "5cqw", height: "5cqw", minWidth: 16, borderRadius: "var(--r-xs)", border: "0.6cqw solid var(--positive)", flexShrink: 0 } }),
            h("span", { style: { fontSize: "3.8cqw", color: "var(--label-normal)", fontWeight: 600, lineHeight: 1.35, ...spClamp(1) } }, it)))));
}

/* HUB — diagram_subtype 별 SVG 도식 (hub/flow/tree/cycle/cause) */
function SPHub(slide) {
  const nodes = spList(slide.content);
  const subtype = (slide.diagram_subtype || "hub").toLowerCase();
  return h("div", { style: { flex: 1, display: "flex", flexDirection: "column", minHeight: 0 } },
    SPTitleBar(slide.title, "var(--cyan)"),
    nodes.length === 0
      ? SPEmpty("도식 노드를 입력해요.")
      : h("div", { style: { flex: 1, minHeight: 0 } }, h(SPDiagramSVG, { nodes, subtype })));
}

/* ── SVG 도식 — viewBox 100x62(16:9 비례). 네이티브 SVG 도형/커넥터로
   "그럴듯한" 도식을 그린다. (diagrams.py 개념 차용 — hub/flow/tree/cycle/cause)
   둥근 박스 노드 + 라벨 + 화살표 마커. 작은 썸네일에서도 형태가 읽히고,
   큰 중앙뷰(cqw 스케일)에서는 라벨까지 보이게 한다. */

/* 도식 색 토큰 (palette.py 의 DIAG/HUB/CASE 도메인색 개념 차용) */
const SP_DIAG = {
  node: "#2563eb",      // 채운 노드(파랑) = HUB_CENTER · DIAG_NODE
  nodeLt: "#eef2fb",    // 연한 노드 = HUB_SAT · DIAG_NODE_LT
  nodeLtStroke: "#c3d2ef",
  ink: "#1f2937",       // INK
  line: "#94a3b8",      // 커넥터(회색) = HUB_CONNECT · DIAG_LINE
  cause: "#ef4444",     // 원인(빨강) = CASE_CAUSE
  causeLt: "#fee2e2",
  effect: "#10b981",    // 결과·예방(초록) = CASE_PREVENT
  effectLt: "#d1fae5",
};

/* 라벨 줄임 — 큰 뷰에선 더 길게(maxCh), 작은 썸네일에선 짧게 보이도록 폰트로 조절. */
function spTrim(text, maxCh) {
  const t = String(text == null ? "" : text);
  return t.length > maxCh ? t.slice(0, maxCh) + "…" : t;
}

/* SVG 도식 본체. 노드=둥근 사각형(rx)+가운데 텍스트, 연결선=화살표 마커. */
function SPDiagramSVG({ nodes, subtype }) {
  const W = 100, H = 62;
  const C = SP_DIAG;

  /* 둥근 박스 노드(살짝 그림자 느낌의 외곽선) */
  const node = (cx, cy, w, ht, text, opt) => {
    const o = opt || {};
    const fill = o.fill || C.node;
    const tcolor = o.color || "#fff";
    const stroke = o.stroke || (fill === C.nodeLt ? C.nodeLtStroke : "none");
    const fs = o.fs || (o.bold ? 3.6 : 3.3);
    return h("g", { key: o.key },
      h("rect", { x: cx - w / 2, y: cy - ht / 2, width: w, height: ht, rx: Math.min(3, ht / 3), fill, stroke, strokeWidth: stroke === "none" ? 0 : 0.5 }),
      h("text", { x: cx, y: cy + 0.2, fill: tcolor, fontSize: fs, fontWeight: o.bold ? 800 : 650, textAnchor: "middle", dominantBaseline: "central" },
        spTrim(text, o.maxCh || 8)));
  };
  /* 연결선(직선 + 선택적 화살표). dashed 옵션. */
  const line = (x1, y1, x2, y2, key, opt) => {
    const o = opt || {};
    return h("line", {
      key, x1, y1, x2, y2,
      stroke: o.stroke || C.line, strokeWidth: o.w || 0.7,
      strokeDasharray: o.dash ? "1.6 1.6" : undefined,
      strokeLinecap: "round",
      markerEnd: o.arrow ? "url(#sp-arrow)" : undefined,
    });
  };
  /* 곡선 커넥터(cycle 회전 화살표용) */
  const curve = (x1, y1, x2, y2, bow, key) => {
    const mx = (x1 + x2) / 2, my = (y1 + y2) / 2;
    const dx = x2 - x1, dy = y2 - y1, len = Math.hypot(dx, dy) || 1;
    const nx = -dy / len, ny = dx / len;
    const cxp = mx + nx * bow, cyp = my + ny * bow;
    return h("path", { key, d: `M${x1},${y1} Q${cxp},${cyp} ${x2},${y2}`, fill: "none", stroke: C.line, strokeWidth: 0.7, strokeLinecap: "round", markerEnd: "url(#sp-arrow)" });
  };

  const els = [];

  if (subtype === "flow") {
    /* 좌→우 박스 + 화살표 (순서) */
    const k = Math.min(nodes.length, 5);
    const pad = 4, nh = 15;
    const nw = Math.min(20, (W - pad * 2 - 7 * (k - 1)) / Math.max(1, k));
    const gap = k > 1 ? (W - pad * 2 - nw * k) / (k - 1) : 0;
    let x = pad + nw / 2;
    for (let i = 0; i < k; i++) {
      if (i > 0) els.push(line(x - nw / 2 - gap + 0.6, H / 2, x - nw / 2 - 0.6, H / 2, "fl" + i, { arrow: true, w: 0.8 }));
      els.push(node(x, H / 2, nw, nh, nodes[i], { key: "fn" + i, fill: i === 0 ? C.node : C.nodeLt, color: i === 0 ? "#fff" : C.ink, maxCh: 7 }));
      x += nw + gap;
    }
  } else if (subtype === "tree") {
    /* 루트 → 자식 분기 (계층) */
    const root = nodes[0], children = nodes.slice(1, 5);
    const rootCx = W / 2, rootCy = 13, k = children.length;
    const span = W - 12, cell = span / Math.max(1, k), childCy = 48;
    const cws = Math.min(22, cell - 3);
    children.forEach((c, i) => {
      const cx = 6 + cell * i + cell / 2;
      els.push(h("path", { key: "te" + i, d: `M${rootCx},${rootCy + 7} C${rootCx},${(rootCy + childCy) / 2} ${cx},${(rootCy + childCy) / 2} ${cx},${childCy - 6}`, fill: "none", stroke: C.line, strokeWidth: 0.7 }));
    });
    children.forEach((c, i) => {
      const cx = 6 + cell * i + cell / 2;
      els.push(node(cx, childCy, cws, 13, c, { key: "tn" + i, fill: C.nodeLt, color: C.ink, maxCh: 6 }));
    });
    els.push(node(rootCx, rootCy, 30, 14, root, { key: "tr", fill: C.node, color: "#fff", bold: true, maxCh: 8 }));
  } else if (subtype === "cycle") {
    /* 원형 배치 + 회전 화살표 (순환) */
    const k = Math.min(nodes.length, 6), cx = W / 2, cy = H / 2 + 1, rx = 33, ry = 19, nw = 19, nh = 12;
    const pts = [];
    for (let i = 0; i < k; i++) { const a = -Math.PI / 2 + (2 * Math.PI * i) / Math.max(1, k); pts.push([cx + rx * Math.cos(a), cy + ry * Math.sin(a)]); }
    /* 곡선 회전 화살표(노드 사이) */
    if (k > 1) for (let i = 0; i < k; i++) {
      const a = pts[i], b = pts[(i + 1) % k];
      const ax = a[0] + (b[0] - a[0]) * 0.28, ay = a[1] + (b[1] - a[1]) * 0.28;
      const bx = a[0] + (b[0] - a[0]) * 0.72, by = a[1] + (b[1] - a[1]) * 0.72;
      els.push(curve(ax, ay, bx, by, 3.2, "cc" + i));
    }
    pts.forEach((p, i) => els.push(node(p[0], p[1], nw, nh, nodes[i], { key: "cn" + i, fill: i === 0 ? C.node : C.nodeLt, color: i === 0 ? "#fff" : C.ink, bold: i === 0, maxCh: 6 })));
  } else if (subtype === "cause") {
    /* 원인들 → 결과 수렴 화살표 */
    const causes = nodes.length >= 2 ? nodes.slice(0, -1) : nodes;
    const effect = nodes.length >= 2 ? nodes[nodes.length - 1] : null;
    const k = Math.min(causes.length, 4);
    const nh = Math.min(12, (H - 6 - 3.5 * (k - 1)) / Math.max(1, k));
    const ex = W - 15, ey = H / 2, ew = 22, eh = 16, cw = 22, clx = 13;
    const startY = (H - (nh * k + 3.5 * (k - 1))) / 2;
    causes.slice(0, k).forEach((c, i) => {
      const cy = startY + i * (nh + 3.5) + nh / 2;
      if (effect) els.push(line(clx + cw / 2 + 0.6, cy, ex - ew / 2 - 0.6, ey, "ul" + i, { arrow: true, stroke: C.cause, w: 0.7 }));
    });
    causes.slice(0, k).forEach((c, i) => {
      const cy = startY + i * (nh + 3.5) + nh / 2;
      els.push(node(clx, cy, cw, nh, c, { key: "uc" + i, fill: C.causeLt, color: "#7f1d1d", stroke: "#fca5a5", maxCh: 6 }));
    });
    if (effect) els.push(node(ex, ey, ew, eh, effect, { key: "ue", fill: C.effect, color: "#fff", bold: true, maxCh: 7 }));
  } else {
    /* hub(방사형): 중앙 노드 + 방사 위성 + 연결선 */
    const center = nodes[0], sats = nodes.slice(1, 7);
    const cx = W / 2, cy = H / 2, rx = 33, ry = 20, k = sats.length;
    const pts = sats.map((_, i) => { const a = -Math.PI / 2 + (2 * Math.PI * i) / Math.max(1, k); return [cx + rx * Math.cos(a), cy + ry * Math.sin(a)]; });
    pts.forEach((p, i) => els.push(line(cx, cy, p[0], p[1], "hl" + i, { w: 0.7 })));
    pts.forEach((p, i) => els.push(node(p[0], p[1], 19, 11, sats[i], { key: "hs" + i, fill: C.nodeLt, color: C.ink, maxCh: 6 })));
    els.push(node(cx, cy, 24, 13, center, { key: "hc", fill: C.node, color: "#fff", bold: true, maxCh: 7 }));
  }

  return h("svg", { viewBox: `0 0 ${W} ${H}`, preserveAspectRatio: "xMidYMid meet", style: { width: "100%", height: "100%", display: "block" } },
    h("defs", null,
      h("marker", { id: "sp-arrow", markerWidth: 6, markerHeight: 6, refX: 4.4, refY: 2.5, orient: "auto", markerUnits: "strokeWidth" },
        h("path", { d: "M0,0.4 L5,2.5 L0,4.6 z", fill: C.line }))),
    els);
}

function SPEmphasis(slide) {
  const list = spList(slide.content);
  const headline = list[0] || slide.title || "";
  const badges = list.slice(1);
  return h("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "3cqh", padding: "0 5cqw", textAlign: "center" } },
    headline
      ? h("div", { style: { fontSize: "7.5cqw", fontWeight: 800, color: "var(--primary)", lineHeight: 1.25, ...spClamp(3) } }, headline)
      : SPEmpty("강조할 한 문장을 입력해요."),
    badges.length
      ? h("div", { style: { display: "flex", flexWrap: "wrap", gap: "1.6cqw", justifyContent: "center" } },
          badges.slice(0, 4).map((b, i) => h("span", { key: i, style: { fontSize: "3.6cqw", fontWeight: 700, color: "#fff", background: "var(--primary)", padding: "1cqh 2.6cqw", borderRadius: "var(--r-sm)" } }, b)))
      : null);
}

function SPSummary(slide) {
  const items = spList(slide.content);
  return h("div", { style: { flex: 1, display: "flex", flexDirection: "column", minHeight: 0 } },
    SPTitleBar(slide.title, "var(--primary)"),
    items.length === 0
      ? SPEmpty("요약 항목을 입력해요.")
      : h("div", { style: { flex: 1, display: "flex", flexDirection: "column", gap: "1.2cqh", minHeight: 0, overflow: "hidden" } },
          items.slice(0, 6).map((it, i) => h("div", { key: i, style: { display: "flex", alignItems: "center", gap: "2cqw", background: "var(--bg-alt)", borderRadius: "var(--r-sm)", padding: "1.2cqh 2cqw" } },
            h("span", { style: { width: "6cqw", height: "6cqw", minWidth: 18, borderRadius: "50%", background: "var(--primary)", color: "#fff", fontSize: "3.4cqw", fontWeight: 800, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 } }, String(i + 1)),
            h("span", { style: { fontSize: "3.7cqw", color: "var(--label-normal)", fontWeight: 600, lineHeight: 1.35, ...spClamp(1) } }, it)))));
}

function SPTextOnly(slide) {
  const items = spList(slide.content);
  return h("div", { style: { flex: 1, display: "flex", flexDirection: "column", minHeight: 0 } },
    SPTitleBar(slide.title, "var(--label-alt)"),
    items.length === 0
      ? SPEmpty("내용을 입력하면 불릿으로 보여요.")
      : h("div", { style: { flex: 1, display: "flex", flexDirection: "column", gap: "1.4cqh", minHeight: 0, overflow: "hidden" } },
          items.slice(0, 6).map((it, i) => h("div", { key: i, style: { display: "flex", gap: "1.6cqw", alignItems: "flex-start" } },
            h("span", { style: { width: "1.6cqw", height: "1.6cqw", minWidth: 6, borderRadius: "50%", background: "var(--primary)", marginTop: "1.6cqh", flexShrink: 0 } }),
            h("span", { style: { fontSize: "4cqw", color: "var(--label-neutral)", fontWeight: 500, lineHeight: 1.45, ...spClamp(2) } }, it)))));
}

function SPTextImage(slide) {
  const items = spList(slide.content);
  return h("div", { style: { flex: 1, display: "flex", flexDirection: "column", minHeight: 0 } },
    SPTitleBar(slide.title, "var(--cyan)"),
    h("div", { style: { flex: 1, display: "flex", gap: "2.4cqw", minHeight: 0 } },
      h("div", { style: { flex: "0 0 52%", display: "flex", flexDirection: "column", gap: "1.2cqh", minWidth: 0, overflow: "hidden" } },
        items.length === 0
          ? h("span", { style: { color: "var(--label-assist)", fontSize: "3.6cqw", fontWeight: 600, margin: "auto 0" } }, "키워드를 입력해요.")
          : items.slice(0, 5).map((it, i) => h("span", { key: i, style: { fontSize: "3.8cqw", color: "var(--label-neutral)", fontWeight: 500, lineHeight: 1.4, ...spClamp(2) } }, "•  " + it))),
      h("div", { style: { flex: 1, borderRadius: "var(--r-md)", border: "1px solid var(--border)", background: "var(--bg-alt)", display: "flex", alignItems: "center", justifyContent: "center", overflow: "hidden", minWidth: 0 } },
        slide.illustration_url
          ? h("img", { src: slide.illustration_url, alt: "", style: { width: "100%", height: "100%", objectFit: "cover" }, onError: (e) => { e.target.style.display = "none"; } })
          : h("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: "1cqh", color: "var(--label-assist)" } },
              h("svg", { width: "14%", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.8, style: { minWidth: 20, maxWidth: 40 } },
                h("rect", { x: 3, y: 3, width: 18, height: 18, rx: 2 }), h("circle", { cx: 8.5, cy: 8.5, r: 1.5 }), h("path", { d: "M21 15l-5-5L5 21" })),
              h("span", { style: { fontSize: "3cqw", fontWeight: 700 } }, "삽화 영역")))));
}

function SPTable(slide) {
  const rows = spList(slide.content).map((item) => {
    const txt = String(item);
    const idx = txt.indexOf(":");
    if (idx > 0 && idx <= 20) return [txt.slice(0, idx).trim(), txt.slice(idx + 1).trim()];
    return [txt];
  });
  const ncols = Math.max(1, ...rows.map((r) => r.length));
  return h("div", { style: { flex: 1, display: "flex", flexDirection: "column", minHeight: 0 } },
    SPTitleBar(slide.title, "var(--label-neutral)"),
    rows.length === 0
      ? SPEmpty("표 항목을 'key: value' 로 입력해요.")
      : h("div", { style: { flex: 1, border: "1px solid var(--border)", borderRadius: "var(--r-sm)", overflow: "hidden", display: "flex", flexDirection: "column" } },
          rows.slice(0, 6).map((r, i) => h("div", { key: i, style: { display: "flex", background: i % 2 ? "var(--bg-alt)" : "var(--bg-normal)", borderBottom: i < Math.min(rows.length, 6) - 1 ? "1px solid var(--line-alt)" : "none" } },
            Array.from({ length: ncols }).map((_, c) => h("div", { key: c, style: { flex: c === 0 ? "0 0 38%" : 1, padding: "1.4cqh 2cqw", fontSize: "3.4cqw", fontWeight: c === 0 ? 700 : 500, color: c === 0 ? "var(--label-normal)" : "var(--label-neutral)", borderLeft: c ? "1px solid var(--line-alt)" : "none", lineHeight: 1.3, ...spClamp(1) } }, r[c] || ""))))));
}

function SPCard(slide) {
  const list = spList(slide.content);
  let count = typeof slide.card_count === "number" && slide.card_count > 0 ? slide.card_count : list.length;
  if (count <= 0) count = list.length || 0;
  count = Math.min(count, 6);
  const cols = count <= 1 ? 1 : (count <= 4 ? 2 : 3);
  return h("div", { style: { flex: 1, display: "flex", flexDirection: "column", minHeight: 0 } },
    SPTitleBar(slide.title, "var(--violet)"),
    count === 0
      ? SPEmpty("카드 개수와 내용을 정해요.")
      : h("div", { style: { flex: 1, display: "grid", gridTemplateColumns: `repeat(${cols}, 1fr)`, gap: "1.6cqw", minHeight: 0 } },
          Array.from({ length: count }).map((_, i) => h("div", { key: i, style: { background: "var(--bg-alt)", borderRadius: "var(--r-sm)", overflow: "hidden", display: "flex", flexDirection: "column", border: "1px solid var(--border)" } },
            h("div", { style: { height: "1.4cqh", minHeight: 4, background: "var(--violet)" } }),
            h("div", { style: { flex: 1, padding: "1.4cqh 1.8cqw", fontSize: "3.4cqw", fontWeight: 600, color: "var(--label-neutral)", lineHeight: 1.35, ...spClamp(3) } }, list[i] != null ? list[i] : `카드 ${i + 1}`)))));
}

function SPQuiz(slide) {
  const list = spList(slide.content);
  let question, options, answer;
  if (list.length) { question = list[0]; const rest = list.slice(1); answer = rest.length ? rest[rest.length - 1] : null; options = rest.length ? rest.slice(0, -1) : []; }
  else { question = slide.title || "문제"; options = []; answer = null; }
  const marks = "①②③④⑤⑥⑦⑧⑨";
  return h("div", { style: { flex: 1, display: "flex", flexDirection: "column", minHeight: 0, gap: "1.4cqh" } },
    h("div", { style: { background: "var(--label-normal)", color: "#fff", borderRadius: "var(--r-md)", padding: "1.8cqh 2.6cqw", fontSize: "4cqw", fontWeight: 700, lineHeight: 1.35, flexShrink: 0, ...spClamp(2) } }, "Q. " + question),
    h("div", { style: { flex: 1, display: "flex", flexDirection: "column", gap: "1cqh", minHeight: 0, overflow: "hidden" } },
      options.slice(0, 4).map((o, i) => h("div", { key: i, style: { background: "var(--bg-alt)", borderRadius: "var(--r-sm)", padding: "1.2cqh 2cqw", fontSize: "3.6cqw", fontWeight: 600, color: "var(--label-normal)", lineHeight: 1.3, ...spClamp(1) } }, (marks[i] || (i + 1) + ")") + " " + o))),
    answer != null
      ? h("div", { style: { background: "var(--positive)", color: "#fff", borderRadius: "var(--r-sm)", padding: "1.4cqh 2.6cqw", fontSize: "3.6cqw", fontWeight: 800, flexShrink: 0, ...spClamp(1) } }, "정답: " + answer)
      : null);
}

/* 레이아웃 → 본문 렌더러 디스패치 */
const SP_RENDERERS = {
  TITLE: SPTitle, CONCEPT: SPConcept, STEPS: SPSteps, COMPARE: SPCompare,
  CASE_STUDY: SPCaseStudy, CHECKLIST: SPChecklist, HUB: SPHub, EMPHASIS: SPEmphasis,
  SUMMARY: SPSummary, TEXT_ONLY: SPTextOnly, TEXT_IMAGE: SPTextImage,
  TABLE: SPTable, CARD: SPCard, QUIZ: SPQuiz,
};

/* ── SlidePreview — 16:9 슬라이드처럼 렌더 ──
   props:
     slide : 정규화된 슬라이드 객체(없으면 빈 슬라이드 방어)
     scale : (선택) 컨테이너 폭 힌트 — 현재는 cqw 기반이라 외형 영향 없음. 미지정 가능. */
function SlidePreview({ slide, scale }) {
  const s = slide || {};
  const layout = (s.layout_suggestion && SP_RENDERERS[s.layout_suggestion]) ? s.layout_suggestion : "TEXT_ONLY";
  const Renderer = SP_RENDERERS[layout];
  let body;
  try {
    body = Renderer(s);
  } catch (e) {
    body = SPEmpty("미리보기를 그릴 수 없어요.");
  }
  return h("div", {
    style: {
      containerType: "size",            // cqw/cqh 기준 = 이 박스
      aspectRatio: "16 / 9", width: "100%", background: "#fff", color: "var(--label-normal)",
      borderRadius: "var(--r-sm)", border: "1px solid var(--border)", overflow: "hidden",
      display: "flex", flexDirection: "column", position: "relative",
    },
  },
    h("div", { style: { flex: 1, display: "flex", flexDirection: "column", padding: "5cqh 5cqw", minHeight: 0 } }, body));
}

Object.assign(window, { SlidePreview });
