/* Denky — 앱 셸 / 라우터 / Tweaks */
const { useState: useS, useEffect: useE, useRef: useR } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "heroStyle": "스플릿",
  "filterLayout": "좌측 사이드바",
  "gridCols": "4열",
  "cardInfo": true,
  "pointColor": "#6D28D9",
  "cardRadius": 16
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);

  // 라우팅: { name, product, params }
  const [route, setRoute] = useS({ name: "home" });
  const [current, setCurrent] = useS(null); // active product
  const [query, setQuery] = useS("");
  const [cart, setCart] = useS([]);
  const [toasts, setToasts] = useS([]);
  const [fit, setFit] = useS({ base: null, worn: [], baseHistoryId: null }); // 가상피팅 겹쳐입기 세션
  const scrollRef = useR(null);
  // 카탈로그 '뒤로가기 복원'용 — 스마트앱처럼 재검색 없이 그 자리로 돌아가게 한다.
  // navSeqRef: catalog 진입(go)마다 새 번호를 매겨, catalogCacheRef 에 그 인스턴스의 결과/스크롤을 stash.
  // 뒤로가기(popstate)로 같은 navId 가 돌아오면 CatalogScreen 이 캐시를 읽어 재요청을 건너뛴다.
  const navSeqRef = useR(0);
  const catalogCacheRef = useR({});

  // 실제 백엔드 인증 상태 (api.js)
  const [auth, setAuth] = useS({ loggedIn: false, hasPhoto: false, photoUrl: null, daily: 0, credit: 0, name: null, email: null, phone: null, gender: null, height: null, weight: null, zipcode: null, address: null, addressDetail: null });
  const [dataReady, setDataReady] = useS(false); // 실제 상품 로드 완료 시 재렌더용

  // 내 정보를 새로고침해 로그인/사진 보유 상태를 갱신합니다.
  async function refreshMe() {
    try {
      const me = await API.me();
      // 성별 토글 기본값을 회원 성별로 — 추천·검색·목록이 회원 성별을 디폴트로 보이게.
      // (사용자가 이번 세션에 직접 바꿨으면 sessionStorage 플래그로 존중)
      try {
        const mapped = me.gender === "male" ? "남성" : (me.gender === "female" ? "여성" : null);
        if (mapped && sessionStorage.getItem("gender_explicit") !== "1") localStorage.setItem("denky-gender", mapped);
      } catch (e) {}
      setAuth({ loggedIn: true, isEmailVerified: me.is_email_verified === true, hasPhoto: !!me.front_photo_url, photoUrl: me.front_photo_url, sidePhotoUrl: me.side_photo_url, daily: me.daily_tryon_count || 0, credit: me.credit_balance || 0, name: me.name, email: me.email, phone: me.phone, gender: me.gender, birthdate: me.birthdate, marketingConsent: me.marketing_consent === true, oauthProvider: me.oauth_provider || null, height: me.height, weight: me.weight, chest: me.chest, waist: me.waist, hip: me.hip, shoulder: me.shoulder, torsoLength: me.torso_length, legLength: me.leg_length, armLength: me.arm_length, usualTopSize: me.usual_top_size, usualBottomSize: me.usual_bottom_size, estChest: me.est_chest, estWaist: me.est_waist, estHip: me.est_hip, estTorsoLength: me.est_torso_length, estLegLength: me.est_leg_length, estArmLength: me.est_arm_length, estShoulder: me.est_shoulder, estUsualTop: me.est_usual_top, estUsualBottom: me.est_usual_bottom, zipcode: me.zipcode, address: me.address, addressDetail: me.address_detail, avatarUrl: me.avatar_url, fittingSource: me.fitting_source || "photo", avatarGenCount: me.avatar_gen_count || 0, avatarCandidateUrl: me.avatar_candidate_url || null, avatarStatus: me.avatar_status || "none", avatarFreeLimit: me.avatar_free_limit || 3, avatarExtraFitCost: me.avatar_extra_fit_cost || 1, fitCouponBalance: me.fit_coupon_balance || 0, lifetimeTryonCount: me.lifetime_tryon_count || 0, lifetimeFreeLimit: me.lifetime_free_limit || 60, weeklyTryonLimit: me.weekly_tryon_limit || 10, freeFitBalance: me.free_fit_balance || 0, freeFitBalanceCap: me.free_fit_balance_cap || 40, lifetimeFreeGranted: me.lifetime_free_granted || 0 });
      API.loadWishIds(); // 하트(찜) 표시용 id 캐시 갱신 — 기다릴 필요 없음
      return me;
    } catch (e) {
      API.logout();
      API.clearWishIds();
      setAuth({ loggedIn: false, hasPhoto: false, photoUrl: null, daily: 0, credit: 0, name: null, email: null, phone: null, gender: null, height: null, weight: null, zipcode: null, address: null, addressDetail: null });
      return null;
    }
  }
  // 화면들이 쓰는 auth 인터페이스. (가상피팅 게이트/로그인에 사용)
  // 가상피팅 베이스 = '아바타 전용'. 아바타가 있으면 항상 아바타로 피팅한다.
  // (사진을 등록하면 아바타가 자동 생성되므로, 생성 전에만 방금 올린 사진을 임시 베이스로 쓴다)
  const baseImage = auth.avatarUrl || auth.photoUrl;
  const authApi = {
    loggedIn: auth.loggedIn, hasPhoto: auth.hasPhoto, photoUrl: auth.photoUrl, sidePhotoUrl: auth.sidePhotoUrl, daily: auth.daily, credit: auth.credit,
    isEmailVerified: auth.isEmailVerified, // false 면 마이페이지에 인증 배너 표시 (미로그인·미확인은 undefined)
    name: auth.name, email: auth.email, phone: auth.phone, gender: auth.gender, birthdate: auth.birthdate, height: auth.height, weight: auth.weight,
    chest: auth.chest, waist: auth.waist, hip: auth.hip, shoulder: auth.shoulder,
    torsoLength: auth.torsoLength, legLength: auth.legLength, armLength: auth.armLength,
    usualTopSize: auth.usualTopSize, usualBottomSize: auth.usualBottomSize,
    estChest: auth.estChest, estWaist: auth.estWaist, estHip: auth.estHip,
    estTorsoLength: auth.estTorsoLength, estLegLength: auth.estLegLength, estArmLength: auth.estArmLength,
    estUsualTop: auth.estUsualTop, estUsualBottom: auth.estUsualBottom,
    zipcode: auth.zipcode, address: auth.address, addressDetail: auth.addressDetail,
    marketingConsent: auth.marketingConsent === true, // 마케팅 수신 동의 (마이페이지 토글)
    oauthProvider: auth.oauthProvider || null,        // 소셜 연결 (kakao/naver, 이메일 가입이면 null)
    avatarUrl: auth.avatarUrl, fittingSource: auth.fittingSource || "photo",
    // 아바타 생성 한도(무료 3회) 관련 — 서버 설정값과 누적 횟수, 교체 대기 중인 후보
    avatarGenCount: auth.avatarGenCount || 0, avatarCandidateUrl: auth.avatarCandidateUrl || null,
    avatarStatus: auth.avatarStatus || "none",
    avatarFreeLimit: auth.avatarFreeLimit || 3, avatarExtraFitCost: auth.avatarExtraFitCost || 1,
    // 피팅 횟수·쿠폰 (충전금 대체)
    // ★화면에 보여줄 '남은 횟수' = freeFitBalance + fitCouponBalance★
    //   lifetimeFreeLimit/lifetimeFreeGranted 는 "무료 충전이 언제까지 오는지" 안내용이고
    //   남은 횟수가 아닙니다. (무료 잔고는 매주 충전되고 안 쓰면 이월됩니다)
    fitCouponBalance: auth.fitCouponBalance || 0, lifetimeTryonCount: auth.lifetimeTryonCount || 0,
    lifetimeFreeLimit: auth.lifetimeFreeLimit || 60, weeklyTryonLimit: auth.weeklyTryonLimit || 10,
    freeFitBalance: auth.freeFitBalance || 0, freeFitBalanceCap: auth.freeFitBalanceCap || 40,
    lifetimeFreeGranted: auth.lifetimeFreeGranted || 0,
    // 구매확정 / 쿠폰함 / 쿠폰 전환 — 처리 후 내 정보를 새로고침해 잔고를 반영
    confirmOrder: async (orderId) => { const o = await API.confirmOrder(orderId); await refreshMe(); return o; },
    coupons: async () => API.coupons(),
    convertCoupon: async (couponId) => { const r = await API.convertCoupon(couponId); await refreshMe(); return r; },
    baseImage, hasBase: !!baseImage,   // 가상피팅이 쓸 베이스(사진/아바타) + 보유 여부
    refresh: async () => { await refreshMe(); },
    createAvatar: async () => { const me = await API.createAvatar(); await refreshMe(); return me; },
    // 새 아바타(후보) 바꾸기/취소 — accept: true=바꾸기, false=기존 유지
    confirmAvatar: async (accept) => { await API.confirmAvatar(accept); await refreshMe(); },
    setFittingSource: async (src) => { await API.updateProfile({ fitting_source: src }); await refreshMe(); },
    login: async (email, password) => { await API.login(email, password); await refreshMe(); },
    // 가입 — 자동 로그인하지 않고 결과({verification_required, dev_verification_code})를 화면에 돌려준다.
    // (화면이 '이메일 인증' 단계를 띄우고, 인증하거나 '나중에 하기'로 로그인한다)
    signup: async (payload) => API.signup(payload),
    uploadPhoto: async (dataUrl) => { const me = await API.updateProfile({ front_photo: dataUrl }); await refreshMe(); return me && me.front_photo_url; },
    uploadSidePhoto: async (dataUrl) => { const me = await API.updateProfile({ side_photo: dataUrl }); await refreshMe(); return me && me.side_photo_url; },
    updateProfile: async (payload) => { await API.updateProfile(payload); await refreshMe(); },
    logout: () => { API.logout(); API.clearWishIds(); setAuth({ loggedIn: false, hasPhoto: false, photoUrl: null, daily: 0, credit: 0, name: null, email: null, phone: null, gender: null, height: null, weight: null, zipcode: null, address: null, addressDetail: null }); go("home"); },
    // 회원 탈퇴 — 서버에서 계정·기록을 삭제한 뒤 로그아웃과 동일하게 상태를 정리하고 홈으로
    deleteAccount: async () => { await API.deleteMe(); authApi.logout(); },
  };

  // 공개 플래그 — 소프트 오픈 기간 '결제 잠금' 여부 (기본 열림, 서버가 최종 방어)
  const [flags, setFlags] = useS({ checkout_enabled: true });

  // 시작: 실제 상품을 불러와 화면 데이터(DENKY.products)를 교체하고, 로그인 상태를 복원합니다.
  useE(() => {
    API.flags().then((f) => { if (f) setFlags(f); }).catch(() => {});
    (async () => {
      try {
        // 판매자 등록 상품을 우선 노출하기 위해 seller_first 정렬로 불러옵니다. (홈 '지금 인기'에 반영)
        const items = await API.products({ limit: 100, sort: "seller_first" });
        if (items.length) DENKY.products = items;
      } catch (e) {
        toast("상품을 불러오지 못했어요. 백엔드(localhost:8000)가 켜져 있는지 확인해 주세요.");
      }
      // 저장된 토큰이 있으면 로그인 상태를 복원합니다 — 새로고침(Ctrl+Shift+R 포함)에도 로그인 유지.
      // (토큰이 만료·무효면 refreshMe 가 알아서 로그아웃 상태로 정리합니다)
      if (API.loggedIn) await refreshMe();
      setDataReady(true);
    })();
  }, []);

  function toast(msg) {
    const id = Math.random().toString(36).slice(2);
    setToasts((ts) => [...ts, { id, msg }]);
    setTimeout(() => setToasts((ts) => ts.filter((x) => x.id !== id)), 2400);
  }
  // 상품 카드(ProductCard)의 하트처럼 props 를 못 받는 곳에서 쓰는 전역 토스트
  window.denkyToast = toast;

  // 화면 전환의 공통 처리: 상태 반영 + 스크롤 맨 위로.
  function applyNav(routeObj, cur) {
    if (cur !== undefined) setCurrent(cur);
    setRoute(routeObj);
    if (scrollRef.current) scrollRef.current.scrollTo({ top: 0 });
  }
  function go(name, payload) {
    // 화면 이름/페이로드로 라우트 객체와 활성 상품을 정합니다.
    let routeObj, cur;
    if (name === "detail" && payload) { cur = payload; routeObj = { name: "detail" }; }
    else if (name === "detail") { routeObj = { name: "catalog", params: null, navId: ++navSeqRef.current }; }  // 상품 없이 상세 요청 → 유령 상세 대신 카탈로그로
    else if (name === "tryon") { if (payload) cur = payload; routeObj = { name: "tryon", key: Date.now() }; }
    else if (name === "catalog") { routeObj = { name: "catalog", params: payload || null, navId: ++navSeqRef.current }; }
    else if (name === "checkout") { routeObj = { name: "checkout", params: payload || null }; }
    else if (name === "mypage") { routeObj = { name: "mypage", params: payload || null }; }  // 탭 지정(예: {tab:"profile"})
    else if (name === "login" || name === "signup" || name === "reset") { routeObj = { name, params: payload || null }; }  // returnTo 등 (reset=비밀번호 재설정)
    else { routeObj = { name }; }
    applyNav(routeObj, cur);
    // 브라우저 '뒤로가기'가 동작하도록 history 에 현재 화면을 기록합니다.
    window.history.pushState({ route: routeObj, current: cur !== undefined ? cur : current }, "");
  }
  const openProduct = (p) => go("detail", p);
  // 가상피팅 '겹쳐입기' 세션: base=이전 합성 결과 이미지(없으면 프로필 사진부터), worn=지금까지 입은 옷.
  const layeringRef = useR(false); // 다음 '입어보기'가 겹쳐입기인지
  // 일반 '입어보기' → 새 피팅(프로필 사진부터). 결과에서 '더 입어보기'로 왔으면 겹쳐입기.
  function tryProduct(p, optionMap) {
    // 피팅 미지원 상품(상세에 온전한 옷 컷 없음 판정) — 홈/카탈로그/상세/마이 모든 진입점 공통 차단.
    if (p && p.fitting_supported === false) {
      toast("이 상품은 상세 이미지에 온전한 옷 컷이 없어 가상피팅을 지원하지 않아요");
      return;
    }
    if (!layeringRef.current) setFit({ base: null, worn: [], baseHistoryId: null });
    layeringRef.current = false;
    // optionMap = 상세에서 고른 옵션 {"색상":"화이트","사이즈":"L"}.
    //  _fitColor(백엔드 색추출용 공백join) · _fitOptions(카드 표시용 가운뎃점) · _fitOptionMap(결과 페이지 담기/구매에 그대로 재사용).
    const map = (optionMap && typeof optionMap === "object" && !Array.isArray(optionMap)) ? optionMap : null;
    const vals = map ? Object.values(map).filter(Boolean) : [];
    go("tryon", vals.length ? { ...p, _fitColor: vals.join(" "), _fitOptions: vals.join(" · "), _fitOptionMap: map } : p);
  }
  // 결과 화면 '이 위에 다른 옷 더 입어보기' → 겹쳐입기 모드로 옷 고르러 가기.
  function layerMore() { layeringRef.current = true; go("catalog"); }
  // 한 벌 합성이 끝나면 세션에 누적합니다. (다음 옷은 이 결과 위에 입혀짐)
  // historyId = 방금 생성된 기록 id — 다음 겹쳐입기가 이걸 base 로 참조해 옷을 이어 남긴다.
  function onFitted(product, resultUrl, historyId) {
    setFit((f) => ({ base: resultUrl, worn: [...f.worn, product], baseHistoryId: historyId != null ? historyId : f.baseHistoryId }));
  }
  // 피팅 기록의 결과물 위에 이어서 겹쳐입기 — 그 결과 이미지/입은 옷/기록id를 세션에 싣고 옷 고르러 갑니다.
  function continueFromHistory(rec) {
    const worn = (rec.steps || []).map((s) => (s.product ? API.normalize(s.product) : null)).filter(Boolean);
    setFit({ base: rec.result_url, worn, baseHistoryId: rec.id });
    layeringRef.current = true;
    toast("이 위에 겹쳐 입을 옷을 골라주세요");
    go("catalog");
  }

  // 옵션(색상·사이즈 등)이 있는 상품은 '전부' 골라야 담기/구매된다 — 어느 경로(상세·피팅·향후 카드)든
  // 옵션 미선택 add 를 막는 최종 방어선. 값 있는 옵션 축 중 안 고른 것들을 돌려준다(없으면 빈 배열).
  function missingOptionAxes(p, opts) {
    const o = p && p.options;
    if (!o || typeof o !== "object") return [];
    return Object.keys(o)
      .filter((k) => Array.isArray(o[k]) && o[k].length)   // 값 있는 축만
      .filter((axis) => !(opts && opts[axis]));            // 아직 안 고른 축
  }

  // 구매하기 — 모든 상품은 앱 내 직접 결제입니다. pending 주문을 만들어 결제 화면으로 보냅니다.
  async function startCheckout(product, opts) {
    const miss = missingOptionAxes(product, opts);
    if (miss.length) { toast(`${miss.join("·")} 옵션을 선택해 주세요`); return; }
    try {
      const intent = await API.checkoutIntent(product.id, opts);
      // 앱 내 직접 결제 — pending 주문을 결제 화면으로 (옵션·상품id도 넘겨 표기/수량변경에 사용)
      go("checkout", { payOrderId: intent.order_id, amount: intent.amount, productName: product.name, selectedOptions: opts || null, productId: product.id });
    } catch (e) {
      if (e.status === 401) { toast("로그인이 필요해요"); go("login"); }
      else toast(e.message || "구매를 시작할 수 없어요");
    }
  }

  // 브라우저 뒤로/앞으로 가기 → 저장해 둔 화면으로 복원합니다.
  useE(() => {
    window.history.replaceState({ route: { name: "home" }, current: null }, "");
    const onPop = (e) => {
      const s = e.state;
      if (s && s.route) { setCurrent(s.current || null); setRoute(s.route); }
      else { setCurrent(null); setRoute({ name: "home" }); }
      if (scrollRef.current) scrollRef.current.scrollTo({ top: 0 });
    };
    window.addEventListener("popstate", onPop);
    return () => window.removeEventListener("popstate", onPop);
  }, []);

  // opts: 선택한 옵션 {색상,사이즈}. 같은 상품도 옵션이 다르면 별도 줄로 담습니다.
  function addToCart(p, opts, buyNow) {
    // 옵션 미선택이면 담지 않는다 — 옵션 있는 상품은 색상·사이즈를 다 골라야 장바구니에 들어간다.
    const miss = missingOptionAxes(p, opts);
    if (miss.length) { toast(`${miss.join("·")} 옵션을 선택해 주세요`); return; }
    const optKey = opts ? JSON.stringify(opts) : "";
    setCart((c) => {
      const ex = c.find((x) => x.id === p.id && (x._optKey || "") === optKey);
      if (ex) return c.map((x) => x === ex ? { ...x, qty: x.qty + 1 } : x);
      return [...c, { ...p, qty: 1, selected_options: opts || null, _optKey: optKey }];
    });
    if (buyNow) { go("cart"); }
    else toast(`장바구니에 담았어요`);
  }
  // 옵션까지 같아야 같은 줄로 보고 수량/삭제를 처리합니다(_optKey).
  const setQty = (id, q, optKey) => setCart((c) => c.map((x) => (x.id === id && (x._optKey || "") === (optKey || "")) ? { ...x, qty: Math.max(1, q) } : x));
  const removeItem = (id, optKey) => setCart((c) => c.filter((x) => !(x.id === id && (x._optKey || "") === (optKey || ""))));
  const clearCart = () => setCart([]);
  const cartCount = cart.reduce((s, x) => s + x.qty, 0);

  function onSearch(q) {
    const term = (typeof q === "string" && q) ? q : query;
    setQuery(term);
    go("catalog", term ? { q: term } : null);
    if (term) toast(`"${term}" 검색했어요`);
  }

  // point color 적용
  useE(() => {
    const r = document.documentElement;
    r.style.setProperty("--primary", t.pointColor);
    // 살짝 어둡게 + soft 자동 보정
    r.style.setProperty("--primary-dark", shade(t.pointColor, -0.18));
    r.style.setProperty("--accent-soft", tint(t.pointColor, 0.86));
    r.style.setProperty("--r-card", t.cardRadius + "px");
  }, [t.pointColor, t.cardRadius]);

  const isAuthScreen = route.name === "login" || route.name === "signup" || route.name === "reset";

  let screen;
  switch (route.name) {
    case "home": screen = <HomeScreen go={go} openProduct={openProduct} tryProduct={tryProduct} t={t} auth={authApi} />; break;
    case "catalog": screen = <CatalogScreen go={go} openProduct={openProduct} tryProduct={tryProduct} t={t} initial={route.params} navId={route.navId} cacheRef={catalogCacheRef} scrollRef={scrollRef} query={query} setQuery={setQuery} onSearch={onSearch} />; break;
    case "detail": screen = <DetailScreen p={current} go={go} openProduct={openProduct} tryProduct={tryProduct} addToCart={addToCart} startCheckout={startCheckout} t={t} auth={authApi} />; break;
    case "tryon": screen = <TryOnScreen key={route.key} p={current} go={go} addToCart={addToCart} startCheckout={startCheckout} cartCount={cartCount} auth={authApi} toast={toast} fit={fit} onFitted={onFitted} onLayerMore={layerMore} />; break;
    case "cart": screen = <CartScreen cart={cart} setQty={setQty} removeItem={removeItem} go={go} toast={toast} auth={authApi} />; break;
    case "checkout": screen = <CheckoutScreen cart={cart} setQty={setQty} go={go} toast={toast} clearCart={clearCart} intent={route.params} auth={authApi} checkoutEnabled={flags.checkout_enabled !== false} />; break;
    case "login": screen = <AuthScreen mode="login" go={go} auth={authApi} toast={toast} returnTo={route.params && route.params.returnTo} />; break;
    case "signup": screen = <AuthScreen mode="signup" go={go} auth={authApi} toast={toast} returnTo={route.params && route.params.returnTo} />; break;
    case "reset": screen = <PasswordResetScreen go={go} auth={authApi} toast={toast} returnTo={route.params && route.params.returnTo} />; break;
    case "mypage": screen = <MyPageScreen go={go} openProduct={openProduct} tryProduct={tryProduct} auth={authApi} toast={toast} onContinueFit={continueFromHistory} initialTab={route.params && route.params.tab} />; break;
    default: screen = <HomeScreen go={go} openProduct={openProduct} tryProduct={tryProduct} t={t} auth={authApi} />;
  }

  return (
    <div ref={scrollRef} className="app-scroll" style={{ height: "100vh", overflowY: "auto" }}>
      {/* 소프트 오픈 배너 — 결제가 잠긴 동안만. 방문자가 '왜 결제가 안 되지'를 헤매지 않게 먼저 알린다 */}
      {flags.checkout_enabled === false && (
        <div style={{ background: "var(--ink)", color: "#fff", textAlign: "center", padding: "9px 16px", fontSize: 13, fontWeight: 600 }}>
          지금은 시범 운영 중이에요 — 가상피팅은 자유롭게 즐기시고, 구매는 곧 열릴 예정이에요!
        </div>
      )}
      <Header route={route} go={go} cartCount={cartCount} query={query} setQuery={setQuery} onSearch={onSearch} auth={authApi} />
      <main>{screen}</main>
      {!isAuthScreen && <Footer go={go} />}
      <Toasts items={toasts} />
      {/* 디자인 탐색용 Tweaks 패널은 프로덕션에서 로드하지 않습니다.
          다시 보려면: index.html 에 <script type="text/babel" src="tweaks-panel.jsx"> 추가 후
          여기에 <TweaksPanel /> 렌더. (useTweaks 훅은 tweaks-hook.js 가 항상 로드) */}
    </div>
  );
}

/* color helpers */
function hexToRgb(h) { const n = parseInt(h.slice(1), 16); return [n >> 16 & 255, n >> 8 & 255, n & 255]; }
function rgbToHex(r, g, b) { return "#" + [r, g, b].map((x) => Math.max(0, Math.min(255, Math.round(x))).toString(16).padStart(2, "0")).join(""); }
function shade(hex, amt) { const [r, g, b] = hexToRgb(hex); const f = 1 + amt; return rgbToHex(r * f, g * f, b * f); }
function tint(hex, amt) { const [r, g, b] = hexToRgb(hex); return rgbToHex(r + (255 - r) * amt, g + (255 - g) * amt, b + (255 - b) * amt); }

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
