/* ────────────────────────────────────────────────────────────
   core.jsx — 공유 기반(ErrorBoundary·API 클라이언트·프리미티브·도메인 상수)
   모든 화면이 의존. window 노출 패턴(빌드툴 없음).
   CONTRACT §3 REST 호출 shape만 사용.
   ──────────────────────────────────────────────────────────── */
const { useState, useEffect, useRef, useCallback, useMemo } = React;

/* ── API 클라이언트 (쿠키 세션, credentials:'include') ──
   응답 봉투: 리소스 단수 키 {project}/{slide}, 목록 {items}, 에러 {error}(해요체). */
const API_BASE = "/api";
async function api(path, { method = "GET", body, headers, raw } = {}) {
  const opts = {
    method,
    credentials: "include",          // 쿠키 세션(sb_session) 왕복
    headers: { ...(headers || {}) },
  };
  if (body !== undefined) {
    if (body instanceof FormData) {
      opts.body = body;              // multipart — Content-Type 자동
    } else {
      opts.headers["Content-Type"] = "application/json";
      opts.body = JSON.stringify(body);
    }
  }
  let res;
  try {
    res = await fetch(API_BASE + path, opts);
  } catch (e) {
    const err = new Error("서버에 연결할 수 없어요. 잠시 후 다시 시도해 주세요.");
    err.network = true;
    throw err;
  }
  if (raw) return res;                // 바이너리/다운로드용
  let data = null;
  try { data = await res.json(); } catch (e) { data = null; }
  if (!res.ok) {
    const err = new Error((data && data.error) || "요청을 처리하지 못했어요.");
    err.status = res.status;
    err.code = data && data.code;
    err.payload = data;
    throw err;
  }
  return data;
}

/* ── ErrorBoundary — 데이터 결함으로 화면 전체가 백지되지 않게 ── */
class ErrorBoundary extends React.Component {
  constructor(p) { super(p); this.state = { error: null }; }
  static getDerivedStateFromError(error) { return { error }; }
  componentDidCatch(error, info) { console.error("[Storyboard] render error:", error, info); }
  render() {
    if (this.state.error) {
      return React.createElement("div", {
        style: {
          minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center",
          padding: 24, background: "var(--bg-canvas)",
        },
      }, React.createElement("div", {
        style: {
          maxWidth: 440, width: "100%", background: "var(--bg-normal)", border: "1px solid var(--border)",
          borderRadius: "var(--r-lg)", padding: "32px 28px", boxShadow: "var(--sh-2)", textAlign: "center",
        },
      },
        React.createElement("div", { className: "t-heading-2", style: { marginBottom: 10, color: "var(--label-normal)" } }, "화면을 표시하는 중 문제가 생겼어요"),
        React.createElement("p", { className: "t-body-2", style: { color: "var(--label-alt)", margin: "0 0 20px" } }, "데이터를 불러오는 중 오류가 발생했어요. 새로고침하면 대부분 해결돼요."),
        React.createElement("button", {
          className: "sb-btn",
          onClick: () => { this.setState({ error: null }); location.reload(); },
          style: btnStyle("primary"),
        }, "새로고침")
      ));
    }
    return this.props.children;
  }
}

/* ── 버튼 스타일 토큰 ── */
function btnStyle(kind = "primary", size = "md") {
  const sizes = {
    sm: { height: 34, padding: "0 13px", fontSize: 13, radius: "var(--r-sm)" },
    md: { height: 40, padding: "0 18px", fontSize: 14, radius: "var(--r-sm)" },
    lg: { height: 46, padding: "0 22px", fontSize: 15, radius: "var(--r-md)" },
  };
  const s = sizes[size] || sizes.md;
  const kinds = {
    primary: { background: "var(--primary)", color: "#fff", border: "1px solid var(--primary)" },
    outline: { background: "var(--bg-normal)", color: "var(--label-neutral)", border: "1px solid var(--border)" },
    ghost:   { background: "transparent", color: "var(--label-neutral)", border: "1px solid transparent" },
    danger:  { background: "var(--negative)", color: "#fff", border: "1px solid var(--negative)" },
  };
  return {
    display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 7,
    height: s.height, padding: s.padding, fontSize: s.fontSize, fontWeight: 700,
    borderRadius: s.radius, cursor: "pointer", whiteSpace: "nowrap",
    transition: "background var(--t-fast), border-color var(--t-fast), filter var(--t-fast)",
    ...kinds[kind],
  };
}

/* ── 프리미티브 ── */
function Button({ kind = "primary", size = "md", style, children, ...rest }) {
  // rest 를 먼저 펼치고 className/style 을 나중에 덮어쓴다. Babel standalone 의
  // _objectWithoutProperties 가 `style` 을 rest 에서 제외하지 못해, 순서가 반대면
  // rest 의 잔여 style 이 병합된 btnStyle 을 덮어써 버튼이 네이티브로 렌더되던 버그 회피.
  return React.createElement("button", Object.assign({}, rest, {
    className: "sb-btn", "data-kind": kind,
    style: Object.assign({}, btnStyle(kind, size), style || {}),
  }), children);
}

function Pill({ color = "var(--label-neutral)", bg = "var(--bg-alt)", style, children }) {
  return React.createElement("span", {
    style: {
      display: "inline-flex", alignItems: "center", gap: 5, height: 22, padding: "0 9px",
      borderRadius: "var(--r-full)", background: bg, color, fontSize: 11.5, fontWeight: 700,
      lineHeight: 1, ...(style || {}),
    },
  }, children);
}

function Spinner({ s = 18, color = "var(--primary)" }) {
  return React.createElement("span", {
    className: "sb-spin",
    style: {
      display: "inline-block", width: s, height: s, borderRadius: "50%",
      border: `2px solid ${color}`, borderTopColor: "transparent",
    },
  });
}

/* 빈/오류/로딩 상태 (해요체 폴백) */
function StateMsg({ icon, title, desc, action }) {
  return React.createElement("div", {
    style: {
      display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center",
      padding: "60px 24px", textAlign: "center", gap: 6,
    },
  },
    icon || null,
    React.createElement("div", { className: "t-headline-1", style: { color: "var(--label-normal)" } }, title),
    desc ? React.createElement("p", { className: "t-body-2", style: { color: "var(--label-alt)", margin: "2px 0 14px", maxWidth: 360 } }, desc) : null,
    action || null
  );
}

/* ── 도메인 상수 (storyboard-domain 정본 인용; 라벨/색은 tokens 변수) ── */

/* 레이아웃 14종 (CONTRACT §2 enum — 글자 그대로) */
const LAYOUTS = {
  TITLE:      { label: "표지",      color: "var(--label-neutral)" },
  CONCEPT:    { label: "개념",      color: "var(--primary)" },
  STEPS:      { label: "단계",      color: "var(--primary)" },
  COMPARE:    { label: "비교",      color: "var(--violet)" },
  CASE_STUDY: { label: "사례",      color: "var(--negative)" },
  CHECKLIST:  { label: "체크리스트", color: "var(--positive)" },
  HUB:        { label: "도식",      color: "var(--cyan)" },
  EMPHASIS:   { label: "강조",      color: "var(--caution)" },
  SUMMARY:    { label: "요약",      color: "var(--primary)" },
  TEXT_ONLY:  { label: "텍스트",    color: "var(--label-alt)" },
  TEXT_IMAGE: { label: "텍스트+이미지", color: "var(--cyan)" },
  TABLE:      { label: "표",        color: "var(--label-neutral)" },
  CARD:       { label: "카드",      color: "var(--violet)" },
  QUIZ:       { label: "퀴즈",      color: "var(--caution)" },
};
const LAYOUT_KEYS = Object.keys(LAYOUTS);
function layoutMeta(k) { return LAYOUTS[k] || { label: k || "텍스트", color: "var(--label-alt)" }; }

/* 표현형식 (data.jsx EXPR 정본) */
const EXPR = {
  lecturer: { key: "lecturer", label: "강사+아나운서", color: "var(--primary)", bg: "var(--primary-light)" },
  content:  { key: "content",  label: "내용만",        color: "var(--label-neutral)", bg: "var(--bg-alt)" },
  board:    { key: "board",    label: "전자칠판",      color: "var(--cyan)", bg: "var(--cyan-bg)" },
  talk:     { key: "talk",     label: "대담",          color: "var(--violet)", bg: "var(--violet-bg)" },
};

/* 프로젝트 진행 상태 (CONTRACT §1: draft|parsing|editing|exporting|done) */
const PROJECT_STATUS = {
  draft:     { label: "작성 전", color: "var(--label-alt)", bg: "var(--bg-alt)", dot: "var(--label-assist)" },
  parsing:   { label: "AI 분석중", color: "var(--info)", bg: "var(--info-bg)", dot: "var(--info)" },
  editing:   { label: "편집중", color: "var(--primary)", bg: "var(--primary-light)", dot: "var(--primary)" },
  exporting: { label: "내보내는 중", color: "var(--caution)", bg: "var(--caution-bg)", dot: "var(--caution)" },
  done:      { label: "완료", color: "var(--positive)", bg: "var(--positive-bg)", dot: "var(--positive)" },
};
function projStatusMeta(k) { return PROJECT_STATUS[k] || PROJECT_STATUS.draft; }

/* 코너(그룹) — 도입/본문/정리 (data.jsx CORNERS 정본) */
const CORNERS = {
  intro: { label: "도입", color: "var(--cyan)" },
  body1: { label: "본문 1", color: "var(--primary)" },
  body2: { label: "본문 2", color: "var(--violet)" },
  outro: { label: "정리", color: "var(--label-alt)" },
};

/* ── 슬라이드 정규화 — 데이터 결함 방어(옵셔널 체이닝 보강) ──
   API 슬라이드 객체는 §2 v3 필드 보존. 누락 필드는 안전 기본값. */
function normSlide(s, i) {
  s = s || {};
  return {
    slideId: s.slideId || s.id_uuid || null,        // UUID(API 경로용)
    id: typeof s.id === "number" ? s.id : (i + 1),   // = order_idx(1부터)
    title: s.title || "",
    content: Array.isArray(s.content) ? s.content : [],
    narration: s.narration || "",
    layout_suggestion: s.layout_suggestion || "TEXT_ONLY",
    illustration_needed: !!s.illustration_needed,
    illustration_prompt: s.illustration_prompt || "",
    illustration_url: s.illustration_url || null,
    confirmed: !!s.confirmed,
    notes: s.notes || "",
    content_density: s.content_density || "normal",
    diagram_subtype: s.diagram_subtype || null,
    steps_style: s.steps_style || null,
    card_count: typeof s.card_count === "number" ? s.card_count : null,
    objects: Array.isArray(s.objects) ? s.objects : [],   // 자유배치 캔버스 오브젝트(2A)
    _expr: s._expr || "content",   // 편집 UI용(서버 비저장, Wave C에서 매핑)
  };
}

/* recommendLayout — 간이판(키워드 기반). 정본 로직은 Wave C에서 data.jsx 승계. */
function recommendLayout(slide) {
  const t = ((slide && slide.title) || "") + " " + (((slide && slide.content) || []).join(" "));
  if (/사례|재해|사고|case/i.test(t)) return "CASE_STUDY";
  if (/예방|대책|점검|checklist|체크/i.test(t)) return "CHECKLIST";
  if (/단계|절차|순서|step/i.test(t)) return "STEPS";
  if (/비교|대비|차이|vs/i.test(t)) return "COMPARE";
  if (/퀴즈|문제|quiz/i.test(t)) return "QUIZ";
  if (/표지|title|개요/i.test(t)) return "TITLE";
  return slide && slide.layout_suggestion ? slide.layout_suggestion : "TEXT_ONLY";
}

Object.assign(window, {
  api, ErrorBoundary, Button, Pill, Spinner, StateMsg, btnStyle,
  LAYOUTS, LAYOUT_KEYS, layoutMeta, EXPR, PROJECT_STATUS, projStatusMeta, CORNERS,
  normSlide, recommendLayout,
  useState, useEffect, useRef, useCallback, useMemo,
});
