/* Denky — 가상피팅 (미리보기 → 합성 → 결과)
 * - 합성 전에 '미리보기(ready)' 화면에서 전모습(앞모습 사진/이전 결과)과 입을 옷을 보여주고,
 *   사진 변경 / 다른 옷 고르기 / 피팅 시작 을 고를 수 있습니다. (바로 피팅 API 를 부르지 않음)
 * - '겹쳐입기': 결과 위에 다른 옷을 더 입으면 이전 결과 이미지 위에 합성합니다. (앱의 fit 세션)
 */
// 같은 사진 위에 같은 옷을 다시 피팅하면 중복으로 피팅 API(Gemini) 를 부르지 않도록 결과를 기억해 둡니다.
const _fitCache = {};
// 캐시 상한 — 전역이라 세션 내내 무한 증가하던 것을 오래된 것부터 밀어낸다. (스마트앱과 동일 정책)
const _FIT_CACHE_MAX = 40;
// 값은 { url, id } — id(피팅 기록 id)는 '친구에게 물어보기' 공유를 만들 때 필요합니다.
function _fitCachePut(key, url, historyId) {
  _fitCache[key] = { url: url, id: historyId || null };
  const keys = Object.keys(_fitCache);           // 키는 삽입 순서 유지 → 앞쪽 = 오래된 것
  for (let i = 0; keys.length - i > _FIT_CACHE_MAX; i++) delete _fitCache[keys[i]];
}

/* ── 체형분석 계산 도우미 (백엔드 size_fit.py 규칙과 동일) ───────────────── */
const _CHEST_KEYS = ["가슴단면", "가슴", "가슴둘레", "chest", "bust"];
const _WAIST_KEYS = ["허리단면", "허리", "허리둘레", "waist"];
const _HIP_KEYS = ["엉덩이단면", "엉덩이둘레", "엉덩이", "힙", "hip"];
// 사이즈 키 '정확 일치'. ★부분일치(includes)는 'XL'이 'L'·'2XL'에 걸리는 버그가 있어 금지.
// (공백만 무시한 정확 비교) 예: rec="XL" 이면 "L"/"2XL"엔 매칭 안 되고 "XL"에만 매칭.
function _sizeEq(a, b) {
  if (a == null || b == null) return false;
  return String(a).replace(/\s+/g, "") === String(b).replace(/\s+/g, "");
}

// 단면→둘레 환산. 키 이름의 '둘레'를 맹신하지 않고 '값'으로 판단한다.
// (소스가 '엉덩이둘레'라 적어도 값이 50이면 실제론 단면 → ×2=100 이 진짜 둘레)
// 그 부위 둘레로 보기엔 너무 작은 값이면 단면으로 보고 ×2 한다.
function _circThreshold(key) {
  if (/엉덩이|힙|hip/i.test(key)) return 70;     // 엉덩이 둘레 ≥80, 단면 ≤65
  if (/허벅지|thigh/i.test(key)) return 46;       // 허벅지 둘레 ≥50, 단면 ≤40
  if (/밑단|밑위/.test(key)) return 34;           // 밑단 둘레 ≥40, 단면 ≤30
  if (/허리|waist/i.test(key)) return 56;         // 허리 둘레 ≥58, 단면 ≤50
  return 70;                                       // 가슴 둘레 ≥75, 단면 ≤62
}
function _toCirc(val, key) {
  return Math.round(val >= _circThreshold(key) ? val : val * 2);
}

// 사이즈표 한 행에서 주어진 부위 키들로 둘레 환산한 '여유(cm)'를 구한다. (body 없으면 null)
function _rowEase(m, body, keys, isBottom) {
  if (!m || !body) return null;
  for (const k of keys) {
    const v = m[k];
    if (v != null && !isNaN(parseFloat(v))) return Math.round(_toCirc(parseFloat(v), k) - body);
  }
  return null;
}

// 표시용 — 둘레 부위(가슴/허리/엉덩이/허벅지)는 '몸 둘레(cm)'로 환산해 보여주고,
// 너비·길이(어깨/총장/소매/밑단 등)는 단면 그대로 둔다.
const _CIRC_RE = /가슴|허리|엉덩이|힙|허벅지|밑단/;
function _dispColName(key) {
  if (/가슴/.test(key)) return "가슴둘레";
  if (/허리/.test(key)) return "허리둘레";
  if (/엉덩이|힙/.test(key)) return "엉덩이둘레";
  if (/허벅지/.test(key)) return "허벅지둘레";
  if (/밑단/.test(key)) return "밑단둘레";
  return key;
}
function _dispVal(key, val) {
  if (val == null) return "-";
  const n = parseFloat(val);
  if (isNaN(n)) return val;
  if (_CIRC_RE.test(key)) return _toCirc(n, key);   // 둘레 부위는 단면이면 ×2 해서 '진짜 둘레'로
  return val;
}

// 사이즈표 항목 순서를 상품과 무관하게 일정하게 — 총기장(길이)부터.
const _COL_RANK = [
  [/총장|총기장|총길이|기장|길이/, 0],
  [/어깨/, 1],
  [/가슴/, 2],
  [/소매/, 3],
  [/허리/, 4],
  [/엉덩이|힙/, 5],
  [/허벅지/, 6],
  [/밑단/, 7],
  [/밑위/, 8],
];
function _colRank(key) {
  const k = String(key);
  for (const [re, rank] of _COL_RANK) if (re.test(k)) return rank;
  return 99; // 그 외 항목은 뒤로 (원래 순서 유지)
}

// BMI + 체형 한 줄 분류
function _bmiInfo(height, weight) {
  if (!height || !weight) return null;
  const b = weight / Math.pow(height / 100, 2);
  const label = b < 18.5 ? "마른 편" : b < 23 ? "표준" : b < 25 ? "약간 통통" : "건장한 편";
  return { bmi: Math.round(b * 10) / 10, label };
}

// 옷 어깨너비와 내 어깨를 비교한 핏 (둘레가 아니라 직선 너비라 ×2 안 함).
// 내 어깨(userShoulder)가 있으면 그대로 쓰고, 없으면 키·성별로 추정한다.
function _shoulderFit(garmentShoulder, userShoulder, height, gender) {
  if (!garmentShoulder) return null;
  let mine, estimated;
  if (userShoulder) { mine = userShoulder; estimated = false; }
  else if (height) { const male = gender && /^(m|남)/i.test(gender); mine = Math.round(height * (male ? 0.245 : 0.225)); estimated = true; }
  else return null;
  const d = Math.round(garmentShoulder - mine);
  const txt = d <= -2 ? "좁을 수 있음" : d <= 2 ? "어깨 딱 맞음" : d <= 5 ? "약간 넉넉" : "넓은 편(드롭숄더)";
  return { d, txt, estimated };
}

/* ── 옷 자체의 분류 (내 몸과 무관) — 백엔드 렌더 엔진(size_fit.py)과 같은 기준 ──
   화면 문구와 실제 렌더가 다른 말을 하지 않게, 여기 숫자는 백엔드와 항상 같이 고친다. */

// 소매 분류 — s = 소매cm × 168 ÷ 내 키 (키 168 기준 환산). 값이 말이 안 되면 null(행 숨김).
function _sleeveClass(sleeveCm, height) {
  if (!sleeveCm || sleeveCm <= 0) return null;
  const s = sleeveCm * 168 / (height > 0 ? height : 168);
  if (s > 90) return null;              // 총장을 소매 칸에 적은 표 등 — 측정 오류로 보고 표시 안 함
  if (s < 12) return "민소매";
  if (s < 17) return "캡소매";
  if (s < 23) return "반팔";
  if (s < 26) return "긴 반팔";
  if (s < 40) return "팔꿈치(5부) 소매";
  if (s < 52) return "7부 소매";
  return "긴팔";
}

// 하의(바지) 기장 분류 — frac = 총장cm ÷ 내 키.
// 소매와 같은 이유로 위쪽 상한을 둔다(0.85 이상 = mm 오기·다른 줄 오독) → null 이면 기장 행을 숨긴다.
function _bottomLengthClass(totalCm, height) {
  const frac = totalCm / (height > 0 ? height : 168);
  if (frac >= 0.85) return null;        // 측정 오류로 보고 표시 안 함
  if (frac < 0.24) return "아주 짧은 반바지";
  if (frac < 0.29) return "허벅지 중간";
  if (frac < 0.35) return "무릎 위(버뮤다)";
  if (frac < 0.41) return "무릎 아래";
  if (frac < 0.47) return "종아리 중간(7부)";
  if (frac < 0.52) return "종아리 아래(8·9부)";
  if (frac < 0.58) return "발목";
  if (frac < 0.62) return "발등";
  if (frac < 0.66) return "살짝 끌림";
  return "길게 끌림";
}

/* ── 내 몸 기준 보조 문구 (실측 길이가 있을 때만) — 옷 분류를 덮어쓰지 않고 뒤에 붙는다 ── */

// 내 팔길이(어깨~손목)와 비교 — 소매 끝이 손목 근처일 때만 의미가 있어 그 밖은 null.
function _armFitNote(sleeveCm, armLength) {
  if (!armLength) return null;
  const d = Math.round(sleeveCm - armLength);
  if (d >= 4) return `약 ${d}cm 길어요 (수선 고려)`;
  if (d >= -2) return "내 손목까지 딱";
  if (d >= -8) return "내 손목 위";
  return null;                          // 반팔처럼 손목과 한참 떨어지면 옷 분류만으로 충분
}

// 내 다리길이(허리~발목)와 비교 — 밑단이 발목 근처일 때만 의미가 있어 그 밖은 null.
function _legFitNote(totalCm, legLength) {
  if (!legLength) return null;
  const d = Math.round(totalCm - legLength);
  if (d >= 4) return `약 ${d}cm 길어요 (밑단 수선)`;
  if (d >= -2) return "내 발목까지 딱";
  if (d >= -8) return "내 발목 위";
  return null;                          // 반바지처럼 발목과 한참 떨어지면 옷 분류만으로 충분
}

// 소매 문구 = 옷 분류(항상) + 내 팔 기준 보조 문구(팔길이 있을 때만). 값이 이상하면 null(행 숨김).
function _sleeveFit(garmentSleeve, armLength, height) {
  const cls = _sleeveClass(garmentSleeve, height);
  if (!cls) return null;
  const note = _armFitNote(garmentSleeve, armLength);
  return note ? `${cls} · ${note}` : cls;
}

// 의류 종류 판정 — 서버 추천 kind(top/bottom/dress) > 카테고리 키워드.
// (카테고리가 '하의' 가 아니라 '바지/청바지/팬츠' 처럼 와도 하의로 인식하게 함)
function _garmentKind(p, sizeReco) {
  const cat = String((p && (p.type || p.category)) || "");
  if ((sizeReco && sizeReco.kind === "dress") || /원피스|드레스|점프수트/.test(cat)) return "dress";
  if (/치마|스커트/.test(cat)) return "skirt";
  if ((sizeReco && sizeReco.kind === "bottom")
      || /하의|바지|팬츠|슬랙스|청바지|데님|조거|레깅스|쇼츠|반바지|슬랙|트레이닝/.test(cat)) return "pants";
  return "top";
}
// 허리 기준으로 분석할 하의(바지·스커트)인지 — 가슴/허리 분석 분기에 사용.
function _isBottom(p, sizeReco) {
  const k = _garmentKind(p, sizeReco);
  return k === "pants" || k === "skirt";
}

// 총장(기장)이 몸에 어느 정도 오는지.
// 바지는 '옷 분류(항상) + 내 다리 기준 보조 문구(실측 다리길이 있을 때만)' 순서로 붙인다.
// userLen(실측 길이: 바지=다리길이, 상의=상체길이)
function _lengthHint(total, height, kind, userLen) {
  if (!total) return null;
  if (kind === "pants") {
    if (!height) return null;
    const cls = _bottomLengthClass(total, height);
    if (!cls) return null;              // 측정 오류 — 기장 행을 숨긴다
    const note = _legFitNote(total, userLen);
    return note ? `${cls} · ${note}` : cls;
  }
  if (kind === "top" && userLen) {
    // 옷 총장 − 내 상체길이(어깨~허리) → 밑단이 허리 기준 어디에 오는지
    const d = Math.round(total - userLen);
    if (d <= -3) return "허리 위 짧은 기장";
    if (d <= 3) return "허리선 기장";
    if (d <= 13) return "엉덩이 덮는 기장";
    return "롱 기장";
  }
  if (!height) return null;
  const r = total / height;
  if (kind === "dress") return r < 0.55 ? "미니 길이" : r < 0.7 ? "무릎선" : "롱 길이";
  if (kind === "skirt") return r < 0.32 ? "미니 길이" : r < 0.48 ? "무릎선" : "롱·맥시";
  // 상의/아우터 (실측 없을 때 키 비율 추정)
  return r < 0.36 ? "허리 위 짧은 기장" : r < 0.4 ? "엉덩이 윗선" : r < 0.45 ? "엉덩이 덮는 기장" : "롱 기장";
}

/* 피팅 기록에서 결과 이미지를 골라 '앞모습(바탕)'으로 쓰는 오버레이. */
function HistoryPicker({ API, onPicked, onClose }) {
  const [items, setItems] = useState(null);
  const [err, setErr] = useState("");
  useEffect(() => {
    let alive = true;
    API.tryonHistory()
      .then((list) => { if (alive) setItems(Array.isArray(list) ? list : (list.items || [])); })
      .catch(() => { if (alive) setErr("기록을 불러오지 못했어요."); });
    return () => { alive = false; };
  }, []);
  return (
    <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,.9)", zIndex: 1000, display: "flex", flexDirection: "column", padding: 16 }}>
      <p style={{ color: "#fff", textAlign: "center", fontSize: 15, fontWeight: 600, margin: "6px 0 4px" }}>피팅 기록에서 고르기</p>
      <p style={{ color: "rgba(255,255,255,.6)", textAlign: "center", fontSize: 12, margin: "0 0 14px" }}>고른 결과 위에 이 옷을 더 입혀요</p>
      {!items && !err && <p style={{ color: "#fff", textAlign: "center" }}>불러오는 중…</p>}
      {err && <p style={{ color: "#f99", textAlign: "center" }}>{err}</p>}
      {items && items.length === 0 && <p style={{ color: "#fff", textAlign: "center" }}>아직 피팅 기록이 없어요.</p>}
      {items && items.length > 0 && (
        <div style={{ flex: 1, overflowY: "auto", display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 10, alignContent: "start", maxWidth: 520, width: "100%", margin: "0 auto" }}>
          {items.map((it) => (
            <button key={it.id} onClick={() => onPicked(it.result_url)}
              style={{ padding: 0, border: "none", borderRadius: 12, overflow: "hidden", cursor: "pointer", aspectRatio: "3/4", background: "#222" }}>
              <img src={it.result_url} alt="피팅 결과" style={{ width: "100%", height: "100%", objectFit: "cover" }} />
            </button>
          ))}
        </div>
      )}
      <div className="row" style={{ justifyContent: "center", marginTop: 16 }}>
        <Btn variant="ghost" onClick={onClose} style={{ color: "#fff" }}>닫기</Btn>
      </div>
    </div>
  );
}

/* 피팅 결과 이미지를 그 자리에서 확대/이동해 보는 뷰어 — 휠·＋－ 확대, 드래그 이동, 더블클릭 원복.
   휠은 non-passive 로 직접 달아 페이지가 안 밀리게 한다. (이미지는 틀을 항상 덮도록 pan 을 클램프) */
function ZoomView({ src }) {
  const view = useRef(null);
  const img = useRef(null);
  const st = useRef({ tx: 0, ty: 0, scale: 1, drag: null });

  function clamp() {
    const el = view.current; if (!el) return;
    const vw = el.clientWidth, vh = el.clientHeight, s = st.current.scale;
    st.current.tx = Math.min(0, Math.max(vw * (1 - s), st.current.tx));
    st.current.ty = Math.min(0, Math.max(vh * (1 - s), st.current.ty));
  }
  function apply() {
    clamp();
    if (img.current) {
      const { tx, ty, scale } = st.current;
      img.current.style.transform = `translate(${tx}px, ${ty}px) scale(${scale})`;
    }
  }
  function zoomAt(cx, cy, factor) {
    const s0 = st.current.scale;
    const s1 = Math.max(1, Math.min(5, s0 * factor));
    st.current.tx = cx - (cx - st.current.tx) * (s1 / s0);
    st.current.ty = cy - (cy - st.current.ty) * (s1 / s0);
    st.current.scale = s1;
    apply();
  }
  function down(e) {
    if (st.current.scale <= 1.001) return; // 확대했을 때만 이동
    const pt = e.touches ? e.touches[0] : e;
    st.current.drag = { x: pt.clientX, y: pt.clientY, tx: st.current.tx, ty: st.current.ty };
  }
  function move(e) {
    if (!st.current.drag) return;
    const pt = e.touches ? e.touches[0] : e;
    st.current.tx = st.current.drag.tx + (pt.clientX - st.current.drag.x);
    st.current.ty = st.current.drag.ty + (pt.clientY - st.current.drag.y);
    apply();
  }
  function up() { st.current.drag = null; }
  function reset() { st.current.scale = 1; st.current.tx = 0; st.current.ty = 0; apply(); }

  useEffect(() => {
    const el = view.current; if (!el) return;
    const onWheel = (e) => {
      e.preventDefault();
      const r = el.getBoundingClientRect();
      zoomAt(e.clientX - r.left, e.clientY - r.top, e.deltaY < 0 ? 1.15 : 0.87);
    };
    el.addEventListener("wheel", onWheel, { passive: false });
    return () => el.removeEventListener("wheel", onWheel);
  }, []);

  return (
    <div ref={view} onMouseDown={down} onMouseMove={move} onMouseUp={up} onMouseLeave={up}
      onTouchStart={down} onTouchMove={move} onTouchEnd={up} onDoubleClick={reset}
      style={{ position: "absolute", inset: 0, overflow: "hidden", touchAction: "none", overscrollBehavior: "contain", cursor: "grab", background: "#000" }}>
      <img ref={img} src={src} alt="피팅 결과" draggable={false}
        style={{ width: "100%", height: "100%", objectFit: "cover", transformOrigin: "top left", userSelect: "none", pointerEvents: "none", display: "block" }} />
      <div style={{ position: "absolute", right: 8, bottom: 8, display: "flex", flexDirection: "column", gap: 6 }}>
        {["＋", "－"].map((s, i) => (
          <button key={s} onClick={() => { const r = view.current.getBoundingClientRect(); zoomAt(r.width / 2, r.height / 2, i === 0 ? 1.3 : 0.77); }}
            style={{ width: 34, height: 34, borderRadius: "50%", border: "none", background: "rgba(255,255,255,.9)", fontSize: 18, cursor: "pointer", boxShadow: "0 1px 4px rgba(0,0,0,.3)" }}>{s}</button>
        ))}
      </div>
    </div>
  );
}

function TryOnScreen({ p, go, addToCart, startCheckout, cartCount, auth, toast, fit, onFitted, onLayerMore }) {
  const session = fit || { base: null, worn: [] };
  const isLayer = !!session.base;                  // 겹쳐입기(이전 결과 위)인지
  const [baseOverride, setBaseOverride] = useState(null); // 피팅 기록에서 고른 결과를 바탕으로
  const [histOpen, setHistOpen] = useState(false);        // 기록 선택 오버레이
  const baseImage = baseOverride || session.base || auth.baseImage; // 합성의 바탕(내 사진/아바타/이전결과)

  // phase: ready(미리보기) | loading | result | gate | error
  const [phase, setPhase] = useState(
    (auth.loggedIn && auth.hasBase && p) ? "ready" : "gate"
  );
  const [progress, setProgress] = useState(0);
  const [worn, setWorn] = useState(session.worn || []); // 이번 결과까지 입은 옷
  // 이미 입은(피팅 끝난) 상품으로 다시 들어오면 결과를 복원 — 화면 나갔다 와도 '피팅 모습'+버튼 유지.
  const [resultUrl, setResultUrl] = useState(
    () => (p && (session.worn || []).some((w) => w.id === p.id)) ? (session.base || null) : null
  );
  const [limitHit, setLimitHit] = useState(false);
  const [needCharge, setNeedCharge] = useState(false); // 충전금 부족
  const [errMsg, setErrMsg] = useState("");
  const [gateHeight, setGateHeight] = useState(auth.height != null ? String(auth.height) : ""); // 게이트에서 키 함께 받기
  const fileRef = useRef(null);
  // 입어볼 옷 이미지 = 대표 피팅컷 한 장. 색은 HEX 지시로 렌더(색상컷 이미지 안 씀). 없으면 레거시 색상컷 첫 장 → 대표이미지.
  const repCut = (p && (p.fittingImageUrl || (p.fittingImages && p.fittingImages[0] && p.fittingImages[0].url) || p.image)) || null;
  // 상세에서 고른 색·사이즈 표시용(예: "화이트 · L"). 옵션 없으면 null.
  const fitOptions = (p && p._fitOptions) || null;
  const [synthesizing, setSynthesizing] = useState(false); // 제자리 합성 진행중(스피너)
  const [sizeReco, setSizeReco] = useState(null); // 사이즈/체형 분석 결과
  // 입은 옷 구매 — 옵션(색상/사이즈) 선택 모달. optPick = {item, mode:'cart'|'buy', axes}
  const [optPick, setOptPick] = useState(null);
  const [pickOpts, setPickOpts] = useState({});
  const [pickNote, setPickNote] = useState("");

  // 사이즈/체형 분석 로드 — 상품이 바뀌거나, 로그인/체형(키·몸무게·둘레)이 바뀌면 다시 불러온다.
  // (게이트에서 키를 막 입력한 경우에도 추천이 바로 갱신되도록 auth 값을 의존성에 넣음)
  useEffect(() => {
    if (!p) { setSizeReco(null); return; }
    let alive = true;
    API.sizeRecommendation(p.id).then((r) => { if (alive) setSizeReco(r); }).catch(() => { if (alive) setSizeReco(null); });
    return () => { alive = false; };
  }, [p && p.id, auth.loggedIn, auth.height, auth.weight, auth.chest, auth.waist, auth.hip]);

  // 로딩 진행률(시각 효과) — 실제 합성과 병행해 95%까지만 채웁니다.
  useEffect(() => {
    if (phase !== "loading") return;
    setProgress(0);
    let v = 0;
    const id = setInterval(() => { v = Math.min(v + Math.random() * 8 + 3, 95); setProgress(v); }, 500);
    return () => clearInterval(id);
  }, [phase]);

  // 실제 합성 시작 (미리보기에서 '이 옷 입어보기'를 눌렀을 때)
  async function startFitting() {
    if (!p || synthesizing) return;
    const base = baseOverride || session.base || auth.baseImage;
    if (!base) { setPhase("gate"); return; }
    const cacheKey = base + "|" + p.id + "|" + (p._fitColor || "");
    const hit = _fitCache[cacheKey];
    if (hit) { // 이미 합성한 적 있으면 재호출 없이 사용
      setResultUrl(hit.url);
      setWorn((w) => w.find((x) => x.id === p.id) ? w : [...w, p]);
      // ★기록 id 를 함께 넘긴다★ — 예전엔 캐시 히트일 때 id 를 안 넘겨, 캐시로 복원한 결과 위에
      //   겹쳐입으면 '이전 기록 id'가 그대로 쓰였다(입은 옷이 엉뚱한 기록에 붙는 원인).
      if (onFitted) onFitted(p, hit.url, hit.id);
      return;
    }
    // 페이지 이동 없이 '제자리' 합성 — 내 앞모습 카드에 스피너만 돌고, 끝나면 그 카드에 결과를 띄운다.
    setSynthesizing(true); setErrMsg("");
    try {
      // 색상컷 이미지 없이 대표컷 + 고른 옵션 색으로 렌더 — 백엔드가 색만 추출해 fitting_colors HEX로 지시.
      const out = await API.tryon({ productIds: [p.id], userImage: base,
        color: p._fitColor || undefined,
        // 겹쳐입기(이전 결과 위)면 이전 기록 id를 함께 보내 '입은 옷 전체'가 기록에 남게 한다.
        baseHistoryId: isLayer ? session.baseHistoryId : undefined });
      _fitCachePut(cacheKey, out.result_url, out.id);
      setResultUrl(out.result_url);
      setWorn((w) => w.find((x) => x.id === p.id) ? w : [...w, p]);
      if (onFitted) onFitted(p, out.result_url, out.id);
    } catch (e) {
      const m = e.status === 429 ? (e.message || "피팅 횟수가 부족해요. 쿠폰을 전환하거나 다음 주에 이용해 주세요.")
        : e.status === 402 ? (e.message || "피팅 쿠폰 횟수가 부족해요.")
        : e.status === 401 ? "로그인이 필요해요. 다시 로그인해 주세요."
        : (e.message || "합성 중 문제가 생겼어요. 잠시 후 다시 시도해 주세요.");
      setErrMsg(m); if (toast) toast(m);
    } finally {
      setSynthesizing(false);
    }
  }

  // 앞모습 사진 등록/변경 → base64 업로드 → 미리보기 유지
  async function onPickPhoto(e) {
    const file = e.target.files && e.target.files[0];
    if (!file) return;
    try {
      const dataUrl = await readFileAsDataUrl(file);
      toast("사진을 등록하고 있어요…");
      // 키가 아직 없고 게이트에서 입력했으면 함께 저장 (사이즈 추천·자동측정에 사용)
      const h = parseInt(gateHeight, 10);
      if (auth.height == null && h >= 100 && h <= 250) {
        try { await auth.updateProfile({ height: h }); } catch (_) {}
      }
      await auth.uploadPhoto(dataUrl);
      toast("사진이 등록됐어요");
      setPhase("ready");
    } catch (err) { toast(err.message || "사진 등록에 실패했어요"); }
  }

  // 입은 옷 한 벌을 담기/바로구매 — 옵션(색상/사이즈) 있으면 모달 먼저, 없으면 바로 처리.
  function buyWorn(item, mode) {
    const axes = (item && item.options) ? Object.entries(item.options).filter(([, v]) => Array.isArray(v) && v.length) : [];
    if (!axes.length) {
      if (mode === "cart") addToCart(item, undefined);
      else if (startCheckout) startCheckout(item, undefined);
      return;
    }
    // 피팅 때 이미 고른 옵션이 있으면 다시 묻지 않고 그대로 적용한다.
    const picked = item._fitOptionMap;
    if (picked && axes.every(([a]) => picked[a])) {
      if (mode === "cart") addToCart(item, picked);
      else if (startCheckout) startCheckout(item, picked);
      return;
    }
    setPickOpts({}); setPickNote(""); setOptPick({ item, mode, axes });
  }
  // 옵션 모달에서 확정 → 다 골랐으면 담기/구매 실행.
  function confirmPick() {
    if (!optPick) return;
    const { item, mode, axes } = optPick;
    if (!axes.every(([a]) => pickOpts[a])) { setPickNote(axes.map(([a]) => a).join("·") + " 옵션을 선택해 주세요."); return; }
    if (mode === "cart") addToCart(item, pickOpts);
    else if (startCheckout) startCheckout(item, pickOpts);
    setOptPick(null);
  }

  const loadingMsgs = ["내 사진을 분석하고 있어요…", "옷의 형태와 색을 맞추는 중…", "자연스럽게 합성하고 있어요…", "거의 다 됐어요! 마무리 중…"];
  const msgIdx = Math.min(Math.floor(progress / 26), 3);

  /* ---------- 게이트: 로그인/사진 없음 ---------- */
  if (phase === "gate") {
    return (
      <div className="page wrap" style={{ paddingTop: 80, paddingBottom: 80, maxWidth: 540 }}>
        <input ref={fileRef} type="file" accept="image/*" hidden onChange={onPickPhoto} />
        <div className="card" style={{ padding: 40, textAlign: "center" }}>
          <div style={{ width: 80, height: 80, borderRadius: "50%", background: "var(--accent-soft)", color: "var(--primary)", display: "grid", placeItems: "center", margin: "0 auto 22px" }}>
            <Icon name="camera" size={36} stroke={1.6} />
          </div>
          <h2 className="t-h2" style={{ margin: "0 0 10px" }}>{!p ? "입어볼 옷을 먼저 골라 주세요" : "피팅하려면 앞모습 사진이 필요해요"}</h2>
          <p className="t-body t-sub" style={{ margin: "0 0 28px" }}>
            {!p ? "상품에서 '가상피팅으로 입어보기'를 눌러 시작하세요." : (auth.loggedIn ? (auth.height == null ? "정면 사진 한 장과 키만 있으면 바로 시작할 수 있어요." : "정면 사진 한 장만 등록하면 바로 시작할 수 있어요.") : "로그인하고 정면 사진을 등록하면 바로 시작할 수 있어요.")}
          </p>
          <div className="stack" style={{ gap: 12 }}>
            {!p
              ? <Btn variant="primary" size="lg" block onClick={() => go("catalog")}>옷 보러 가기</Btn>
              : !auth.loggedIn
                ? <Btn variant="primary" size="lg" block onClick={() => go("login", { returnTo: "tryon" })}>로그인하고 시작하기</Btn>
                : <>
                    {auth.height == null && (
                      <div className="field" style={{ textAlign: "left" }}>
                        <label>키 (cm) <span className="t-sub" style={{ fontWeight: 400 }}>· 사이즈 추천에 사용돼요</span></label>
                        <input className="input" type="number" min="100" max="250" placeholder="170" value={gateHeight}
                          onChange={(e) => setGateHeight(e.target.value)} />
                      </div>
                    )}
                    <Btn variant="primary" size="lg" block icon="camera" onClick={() => fileRef.current && fileRef.current.click()}>앞모습 사진 등록하고 시작</Btn>
                  </>}
            <Btn variant="ghost" block onClick={() => go("catalog")}>다른 옷 둘러보기</Btn>
          </div>
        </div>
      </div>
    );
  }

  /* ---------- 오류 ---------- */
  if (phase === "error") {
    return (
      <div className="page wrap" style={{ paddingTop: 80, paddingBottom: 80, maxWidth: 540 }}>
        <div className="card" style={{ padding: 40, textAlign: "center" }}>
          <div style={{ width: 80, height: 80, borderRadius: "50%", background: "#FEF3F2", color: "var(--error)", display: "grid", placeItems: "center", margin: "0 auto 22px" }}>
            <Icon name="shield" size={34} stroke={1.8} />
          </div>
          <h2 className="t-h2" style={{ margin: "0 0 10px" }}>{limitHit ? "피팅 횟수가 부족해요" : needCharge ? "피팅 쿠폰이 부족해요" : "합성하지 못했어요"}</h2>
          <p className="t-body t-sub" style={{ margin: "0 0 28px" }}>{errMsg}</p>
          <div className="stack" style={{ gap: 12 }}>
            {(limitHit || needCharge) && <Btn variant="primary" size="lg" block icon="sparkle" onClick={() => go("mypage", { tab: "profile" })}>쿠폰함 확인하러 가기</Btn>}
            {!limitHit && !needCharge && <Btn variant="primary" size="lg" block onClick={() => setPhase("ready")}>다시 시도</Btn>}
            <Btn variant="ghost" block onClick={() => go("catalog")}>다른 옷 둘러보기</Btn>
          </div>
        </div>
      </div>
    );
  }

  /* ---------- 미리보기(ready): 전모습 + 입을 옷 확인 후 시작 ---------- */
  if (phase === "ready") {
    return (
      <div className="page wrap" style={{ paddingTop: 18, paddingBottom: 24, maxWidth: 1120 }}>
        <input ref={fileRef} type="file" accept="image/*" hidden onChange={onPickPhoto} />
        <div className="row" style={{ gap: 10, marginBottom: 4 }}>
          <Icon name="sparkle" size={20} style={{ color: "var(--primary)" }} />
          <h1 className="t-h1" style={{ margin: 0 }}>{isLayer ? "이 위에 더 입어볼게요" : "이렇게 입어볼게요"}</h1>
        </div>
        <p className="t-body t-sub" style={{ margin: "0 0 12px" }}>합성을 시작하기 전에 확인하세요. 합성은 보통 5~20초 걸려요.</p>

        {/* 권장 안내 — 결과 품질에 직접 영향 */}
        <div style={{ display: "flex", gap: 8, alignItems: "flex-start", background: "#FFF7ED", border: "1px solid #FED7AA", borderRadius: 10, padding: "10px 12px", margin: "0 0 18px" }}>
          <Icon name="sparkle" size={16} style={{ color: "#EA580C", marginTop: 1, flexShrink: 0 }} />
          <p style={{ fontSize: 12.5, color: "#9A3412", margin: 0, lineHeight: 1.55 }}>
            <b>‘이 옷 입어보기’</b>를 누르면 <b>대표 착용컷</b>으로 바로 합성해요. 상세페이지에서 고른 색상은 그 색으로 렌더돼요.
          </p>
        </div>

        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(250px, 1fr))", gap: 16, alignItems: "start" }}>
          {/* 전모습 (앞모습 사진 또는 지금까지 입은 모습) */}
          <div>
            <p className="t-small t-sub" style={{ margin: "0 0 8px", fontWeight: 600 }}>{resultUrl ? "피팅 모습" : (isLayer ? "지금까지 입은 모습" : "내 앞모습")}</p>
            <div className="card" style={{ height: "min(56vh, 440px)", borderRadius: 16, overflow: "hidden", position: "relative" }}>
              {resultUrl
                ? <ZoomView key={resultUrl} src={resultUrl} />
                : baseImage
                  ? <img src={baseImage} alt="내 앞모습" style={{ width: "100%", height: "100%", objectFit: "cover" }} />
                  : <PhImg tone="#E5E7EB" ink="#9CA3AF" type="원피스" label="앞모습" />}
              {synthesizing && (
                <div style={{ position: "absolute", inset: 0, background: "rgba(255,255,255,.55)", display: "grid", placeItems: "center" }}>
                  <div className="spinner spinner-dark" style={{ width: 48, height: 48 }}></div>
                </div>
              )}
            </div>
            {resultUrl ? (
              <div className="stack" style={{ gap: 8, marginTop: 10 }}>
                <Btn variant="outline" block icon="layers" onClick={() => (onLayerMore ? onLayerMore() : go("catalog"))}>이 위에 다른 옷 더 입어보기</Btn>
                {worn.length > 0 && (
                  <div className="card" style={{ padding: 12 }}>
                    <p className="t-small t-sub" style={{ margin: "0 0 8px", fontWeight: 600 }}>입은 옷 {worn.length} · 골라서 담거나 바로 구매</p>
                    <div className="stack" style={{ gap: 10 }}>
                      {worn.map((w) => (
                        <div key={w.id} className="row" style={{ gap: 10, alignItems: "center" }}>
                          <div style={{ width: 40, height: 50, borderRadius: 6, overflow: "hidden", flex: "none", cursor: "pointer" }} onClick={() => go("detail", w)}>
                            {w.image
                              ? <img src={w.image} alt={w.name} style={{ width: "100%", height: "100%", objectFit: "cover" }} />
                              : <PhImg tone={w.tone} ink={w.toneInk} type={w.type} />}
                          </div>
                          <div style={{ flex: 1, minWidth: 0 }}>
                            <p style={{ fontSize: 13, fontWeight: 500, margin: "0 0 1px", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{w.name}</p>
                            <Price value={w.price} />
                          </div>
                          <Btn variant="outline" size="sm" onClick={() => buyWorn(w, "cart")}>담기</Btn>
                          <Btn variant="dark" size="sm" onClick={() => buyWorn(w, "buy")}>구매</Btn>
                        </div>
                      ))}
                    </div>
                  </div>
                )}
                {cartCount > 0 && <Btn variant="dark" block iconR="arrowR" onClick={() => go("cart")}>🛒 장바구니 {cartCount}개 · 결제하러 가기</Btn>}
                <Btn variant="ghost" block onClick={() => setResultUrl(null)}>다시 입어보기</Btn>
                <Btn variant="ghost" block onClick={() => go("mypage")}>피팅 기록에 저장됨 · 마이페이지</Btn>
              </div>
            ) : !isLayer ? (
              <>
                <Btn variant="outline" block icon="camera" style={{ marginTop: 10 }}
                  onClick={() => fileRef.current && fileRef.current.click()}>앞모습 사진 변경</Btn>
                <Btn variant="ghost" block icon="layers" style={{ marginTop: 8 }}
                  onClick={() => setHistOpen(true)}>피팅 기록에서 고르기</Btn>
                {baseOverride && (
                  <p className="t-caption t-sub row" style={{ gap: 6, justifyContent: "center", marginTop: 6 }}>
                    기록 결과 사용 중 ·
                    <button onClick={() => setBaseOverride(null)} style={{ background: "none", border: 0, color: "var(--primary)", cursor: "pointer", padding: 0, fontWeight: 600 }}>취소</button>
                  </p>
                )}
              </>
            ) : null}
          </div>

          {/* 입어볼 옷 — 대표 착용컷 한 장. 색은 HEX 지시로 렌더하고, 아래에 고른 색·사이즈를 적는다. */}
          <div>
            <p className="t-small t-sub" style={{ margin: "0 0 8px", fontWeight: 600 }}>입어볼 옷</p>
            <div className="card" style={{ height: "min(56vh, 440px)", borderRadius: 16, overflow: "hidden", position: "relative", background: "#f3f4f6" }}>
              {repCut
                ? <img src={repCut} alt={p?.name} style={{ width: "100%", height: "100%", objectFit: "cover" }} />
                : <PhImg tone={p?.tone} ink={p?.toneInk} type={p?.type} label="옷" />}
            </div>
            {fitOptions && (
              <div style={{ marginTop: 8, padding: "6px 10px", borderRadius: 10, background: "var(--accent-soft)",
                textAlign: "center", fontSize: 13, fontWeight: 600, color: "var(--primary)" }}>
                {fitOptions}
              </div>
            )}
            <p style={{ fontSize: 14, fontWeight: 500, margin: "10px 0 4px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{p?.name}</p>
            {p && <Price value={p.price} />}
            {isLayer && onLayerMore
              ? <Btn variant="ghost" block style={{ marginTop: 8 }} onClick={() => onLayerMore()}>다른 옷 고르기</Btn>
              : <Btn variant="ghost" block style={{ marginTop: 8 }} onClick={() => (p ? go("detail", p) : go("catalog"))}>← 상품으로 돌아가기</Btn>}
          </div>

          {/* 사이즈 · 체형 분석 — 참고용 */}
          <div>
            <p className="t-small t-sub" style={{ margin: "0 0 8px", fontWeight: 600 }}>사이즈 · 체형 분석</p>
            <div className="card" style={{ padding: 14, height: "min(56vh, 440px)", overflowY: "auto" }}>
              {/* 내 체형 */}
              <div style={{ fontSize: 13, marginBottom: 10 }}>
                <span style={{ fontWeight: 600 }}>내 체형</span>{" · "}
                {(auth.height || auth.weight || auth.chest || auth.waist || auth.shoulder)
                  ? `${auth.gender && /^(m|남)/i.test(auth.gender) ? "남성 " : auth.gender && /^(f|w|여)/i.test(auth.gender) ? "여성 " : ""}${auth.height ? auth.height + "cm" : ""}${auth.weight ? " · " + auth.weight + "kg" : ""}${auth.chest ? " · 가슴 " + auth.chest : ""}${auth.waist ? " · 허리 " + auth.waist : ""}${auth.shoulder ? " · 어깨 " + auth.shoulder : ""}`
                  : <span style={{ color: "var(--sub)" }}>마이 &gt; 내 정보에서 키·몸무게·둘레 등록 시 정확도↑</span>}
                {(() => { const bi = _bmiInfo(auth.height, auth.weight); return bi ? <span style={{ color: "var(--sub)" }}>{` · BMI ${bi.bmi} (${bi.label})`}</span> : null; })()}
              </div>

              {/* 추천 사이즈 */}
              {sizeReco && sizeReco.available && (
                <div style={{ background: "#EEF3FF", borderRadius: 10, padding: 12, marginBottom: 12 }}>
                  <div style={{ fontSize: 12, color: "var(--sub)" }}>이 상품 추천 사이즈</div>
                  <div style={{ fontSize: 20, fontWeight: 700, color: "var(--primary)", margin: "2px 0 4px" }}>{sizeReco.size_label || sizeReco.size}</div>
                  {/* (착용 선호 토글 삭제 — 사용자 요청. 추천은 정사이즈 기준 단일 표시, 스마트앱과 파리티) */}
                  {/* 옷 재단 안내 배지 (오버핏/슬림 컷) */}
                  {sizeReco.cut_note && (
                    <div style={{ fontSize: 12, color: "#7C3AED", background: "#F3EEFF", borderRadius: 8, padding: "5px 9px", marginBottom: 6 }}>🧵 {sizeReco.cut_note}</div>
                  )}
                  {sizeReco.reason && <div style={{ fontSize: 12, color: "var(--sub)" }}>{sizeReco.reason}</div>}
                  {/* 부위가 서로 다른 사이즈를 가리킬 때만 범위 + 부위별 추천을 보여준다(끼임 방지로 큰 쪽 선택) */}
                  {sizeReco.size_range && sizeReco.size_range.includes("~") && (
                    <div style={{ fontSize: 12, marginTop: 5 }}>
                      <span style={{ color: "var(--primary)", fontWeight: 700 }}>부위별 추천 {sizeReco.size_range}</span>
                      {sizeReco.parts && sizeReco.parts.length > 1 && (
                        <span style={{ color: "var(--sub)" }}>
                          {" · "}{sizeReco.parts.map((pt) => `${pt.part} ${pt.size}(${pt.ease >= 0 ? "+" : ""}${pt.ease})`).join(" / ")}
                        </span>
                      )}
                    </div>
                  )}
                  {/* 평소 사이즈 앵커링 — "평소 M보다 작게 나와요" (평소 사이즈 입력 시) */}
                  {sizeReco.anchor_note && (
                    <div style={{ fontSize: 12.5, marginTop: 6, fontWeight: 600, color: "var(--primary-dark, var(--primary))" }}>👕 {sizeReco.anchor_note}</div>
                  )}
                  {/* 크라우드 핏 — 비슷한 체형 구매자 후기 기반 (후기 3개 이상) */}
                  {sizeReco.crowd_note && (
                    <div style={{ fontSize: 12.5, marginTop: 5, fontWeight: 600, color: "#7C3AED" }}>👥 {sizeReco.crowd_note}</div>
                  )}
                  {sizeReco.fit_note && <div style={{ fontSize: 13, marginTop: 6, lineHeight: 1.5 }}>{sizeReco.fit_note}</div>}
                  {sizeReco.confidence && <div style={{ fontSize: 11, color: "var(--sub)", marginTop: 4 }}>신뢰도 {sizeReco.confidence}</div>}
                </div>
              )}
              {sizeReco && !sizeReco.available && (
                <p style={{ fontSize: 12.5, color: "var(--sub)", margin: "0 0 12px", lineHeight: 1.5 }}>{sizeReco.message}</p>
              )}

              {/* 부위별 핏 — 상세페이지·앱과 동일한 FitDiagram 카드(둘레 여유 "내 60 · 옷 64" 포함) */}
              <FitDiagram p={p} sizeReco={sizeReco} auth={auth} go={go} />

              {/* 실측 사이즈표 (추천 사이즈 행 강조) */}
              {(() => {
                const rows = (p && p.sizeChart) ? Object.entries(p.sizeChart) : [];
                if (!rows.length) return <p style={{ fontSize: 12, color: "var(--sub)" }}>이 상품은 실측 사이즈표가 없어요. (체형 기반 추천만 제공)</p>;
                const cols = [...new Set(rows.flatMap(([, m]) => Object.keys(m || {})))]
                  .sort((a, b) => _colRank(a) - _colRank(b));   // 총기장 → 어깨 → 가슴 … 일정 순서
                const rec = sizeReco && sizeReco.size ? String(sizeReco.size) : null;
                const isBottom = _isBottom(p, sizeReco);
                // 표가 실제로 가진 치수에 맞춰 비교 부위를 고른다. (하의: 허리 우선, 없으면 엉덩이)
                let body, part, easeKeys;
                if (!isBottom) { body = auth.chest; part = "가슴"; easeKeys = _CHEST_KEYS; }
                else if (cols.some((c) => /허리/.test(c))) { body = auth.waist; part = "허리"; easeKeys = _WAIST_KEYS; }
                else if (cols.some((c) => /엉덩이|힙/.test(c))) { body = auth.hip; part = "엉덩이"; easeKeys = _HIP_KEYS; }
                else { body = auth.waist; part = "허리"; easeKeys = _WAIST_KEYS; }
                return (
                  <>
                    <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 11.5 }}>
                      <thead>
                        <tr style={{ color: "var(--sub)", borderBottom: "1px solid var(--border)" }}>
                          <th style={{ textAlign: "left", padding: "4px 3px" }}>사이즈</th>
                          {cols.map((c) => <th key={c} style={{ textAlign: "right", padding: "4px 3px" }}>{_dispColName(c)}</th>)}
                          {body ? <th style={{ textAlign: "right", padding: "4px 3px", color: "var(--primary)" }}>{part}여유</th> : null}
                        </tr>
                      </thead>
                      <tbody>
                        {rows.map(([sz, m]) => {
                          const on = rec && _sizeEq(sz, rec);
                          const ease = body ? _rowEase(m, body, easeKeys, isBottom) : null;
                          return (
                            <tr key={sz} style={{ background: on ? "#EEF3FF" : "transparent", fontWeight: on ? 700 : 400 }}>
                              <td style={{ padding: "4px 3px" }}>{sz}{on ? " ◀ 추천" : ""}</td>
                              {cols.map((c) => <td key={c} style={{ textAlign: "right", padding: "4px 3px" }}>{_dispVal(c, m && m[c])}</td>)}
                              {body ? <td style={{ textAlign: "right", padding: "4px 3px", fontWeight: 600, color: ease == null ? "var(--sub)" : ease < 0 ? "#DC2626" : ease <= 12 ? "#059669" : "#D97706" }}>{ease == null ? "-" : (ease >= 0 ? "+" : "") + ease}</td> : null}
                            </tr>
                          );
                        })}
                      </tbody>
                    </table>
                    {body
                      ? <p style={{ fontSize: 10.5, color: "var(--sub)", marginTop: 4, lineHeight: 1.5 }}>{part}여유 = 옷 {part}둘레 − 내 {part}({body}cm) · <span style={{ color: "#059669" }}>초록=적당</span> <span style={{ color: "#D97706" }}>주황=넉넉</span> <span style={{ color: "#DC2626" }}>빨강=작음</span></p>
                      : <button type="button" onClick={() => go("mypage", { tab: "profile" })}
                          style={{ marginTop: 6, width: "100%", padding: "9px 10px", borderRadius: 8, border: "1px solid var(--primary)", background: "var(--accent-soft)", color: "var(--primary)", fontWeight: 700, fontSize: 11.5, cursor: "pointer", fontFamily: "inherit" }}>
                          📏 내 {part}둘레 넣고 <u>정확한 사이즈 추천</u> 켜기 →
                        </button>}
                  </>
                );
              })()}
              <p style={{ fontSize: 11, color: "var(--sub)", marginTop: 8, lineHeight: 1.5 }}>단위 cm · 가슴·허리·엉덩이·밑단은 <b>둘레</b>로 표기(옷 단면이면 ×2 환산) · 어깨·기장(총장)·소매는 길이 그대로. 실제 핏은 가상피팅으로 확인하세요.</p>
            </div>

            {/* 주요 CTA — 결과가 뜨면 숨기고, '내 앞모습' 카드 아래 결과 버튼으로 대체 */}
            {!resultUrl && (
              <>
                {/* '이 옷 입어보기' → 대표 착용컷으로 바로 피팅. 고른 색상은 HEX 지시로 그 색으로 렌더. */}
                <Btn variant="primary" size="lg" icon="sparkle" block style={{ marginTop: 14 }} disabled={synthesizing}
                  onClick={() => startFitting()}>
                  {synthesizing ? "합성 중…" : (isLayer ? "이 옷 겹쳐 입기" : "이 옷 입어보기")}
                </Btn>
                {errMsg && <p style={{ color: "#DC2626", fontSize: 12, marginTop: 8, textAlign: "center" }}>{errMsg}</p>}
                <p className="t-caption t-sub" style={{ marginTop: 8, textAlign: "center" }}>
                  무료 가상피팅 · 남은 {auth.freeFitBalance || 0}회
                  {(auth.fitCouponBalance || 0) > 0 ? ` · 쿠폰 ${auth.fitCouponBalance}회` : ""}
                </p>
              </>
            )}
          </div>
        </div>
        {histOpen && (
          <HistoryPicker API={API}
            onPicked={(url) => { setBaseOverride(url); setHistOpen(false); }}
            onClose={() => setHistOpen(false)} />
        )}


        {/* 입은 옷 담기/구매 — 색상·사이즈 옵션 선택 모달 (화면 이동 없이) */}
        {optPick && (
          <div className="modal-scrim" onClick={() => setOptPick(null)} style={{ display: "grid", placeItems: "center", padding: 16 }}>
            <div className="card" onClick={(e) => e.stopPropagation()} style={{ width: "min(380px, 92vw)", padding: 20, borderRadius: 16 }}>
              <h3 className="t-h3" style={{ margin: "0 0 4px" }}>{optPick.item.name}</h3>
              <p className="t-small t-sub" style={{ margin: "0 0 14px" }}>{optPick.mode === "cart" ? "장바구니에 담을" : "바로 구매할"} 옵션을 골라 주세요</p>
              {optPick.axes.map(([axis, values]) => (
                <div key={axis} style={{ marginBottom: 12 }}>
                  <p className="t-small" style={{ margin: "0 0 6px", fontWeight: 600 }}>{axis}</p>
                  <div className="row" style={{ gap: 6, flexWrap: "wrap" }}>
                    {values.map((v) => (
                      <button key={v} onClick={() => { setPickOpts((o) => ({ ...o, [axis]: v })); setPickNote(""); }}
                        style={{ padding: "7px 14px", borderRadius: 999, fontSize: 13, cursor: "pointer",
                          border: pickOpts[axis] === v ? "1.5px solid var(--primary)" : "1px solid var(--border)",
                          background: pickOpts[axis] === v ? "var(--primary)" : "#fff",
                          color: pickOpts[axis] === v ? "#fff" : "#374151", fontWeight: pickOpts[axis] === v ? 600 : 400 }}>{v}</button>
                    ))}
                  </div>
                </div>
              ))}
              {pickNote && <p style={{ color: "#DC2626", fontSize: 12.5, margin: "0 0 10px" }}>{pickNote}</p>}
              <div className="row" style={{ gap: 10, marginTop: 6 }}>
                <Btn variant="ghost" block onClick={() => setOptPick(null)}>취소</Btn>
                <Btn variant="primary" block onClick={confirmPick}>{optPick.mode === "cart" ? "장바구니 담기" : "바로구매"}</Btn>
              </div>
            </div>
          </div>
        )}

        {/* 이미 입은 옷 (겹쳐입기일 때) */}
        {isLayer && session.worn.length > 0 && (
          <div style={{ marginTop: 22 }}>
            <p className="t-small t-sub" style={{ margin: "0 0 8px" }}>이미 입은 옷 {session.worn.length} · 이 위에 겹쳐 입어요</p>
            <div className="row" style={{ gap: 8, flexWrap: "wrap" }}>
              {session.worn.map((w) => <Chip key={w.id} soft>{w.name}</Chip>)}
            </div>
          </div>
        )}

      </div>
    );
  }

  // 피팅은 ready 화면에서 '제자리'로 끝남(로딩/결과 전용 페이지 없음). 안전용 기본 반환.
  return null;
}
Object.assign(window, { TryOnScreen });
