/* ============================================================================
 * 서명전에 — Auth & API client (Toss anonymous hash + FastAPI)
 *
 * 책임:
 *   1) /client-config 1회 조회 → auth_mode / 토스 / 광고 설정 받아옴
 *   2) auth_mode 분기:
 *        - toss-anonymous : TossAdapter.auth.getAnonymousKey() 한 번 호출 →
 *          synthetic session 생성 → 이후 모든 API 호출에
 *          Authorization: Bearer toss-hash:<hash> 로 인증.
 *        - supabase-jwt (레거시): 기존 Supabase Auth 경로 유지. 마이그레이션
 *          기간 동안만. G-9 검증 끝나면 .env 의 AUTH_MODE 가 toss-anonymous 로
 *          전환되고, 이 경로는 죽은 코드가 됨 → G-10 정리 단계에서 제거.
 *   3) /account/profile 자동 조회 → 크레딧 동기화
 *   4) /account/history 자동 조회 + 로컬 캐시 병합
 *   5) useAuth() 훅으로 React 컴포넌트에 전역 상태 전파
 *
 * 토스 익명 hash 인증의 핵심:
 *   - hash 자체가 user_id 역할을 한다 (PII 없음, 미니앱별 고유).
 *   - 별도의 JWT 교환이 없어서 session.access_token = "toss-hash:<hash>" 로
 *     접두를 붙여 그대로 Authorization 헤더에 실어 보낸다.
 *   - 재진입 시 토스 SDK 가 같은 hash 를 돌려주므로 자동 재로그인이 무료.
 * ========================================================================== */

(() => {
  const { useState: useStateAuth, useEffect: useEffectAuth } = React;

  // --- 로컬 캐시 (Supabase analysis_history 테이블이 아직 없을 때 대비) -------
  // 서버가 히스토리 테이블을 갖고 있지 않아 저장이 실패해도, 최소한 같은
  // 브라우저에서 분석한 기록은 최근 리스트에 남겨야 한다. 사용자가 "방금
  // 분석한 게 기록에 왜 안 뜨지?" 하는 상황을 여기서 막는다.
  const LOCAL_HISTORY_KEY_PREFIX = "seomyungjeon:history:v1:";
  const LOCAL_HISTORY_MAX = 20;

  /** 저장소 어댑터. 앱인토스에서는 localStorage 접근이 화이트아웃을 일으킬 수 있어
   *  TossAdapter.storage(→ Toss Storage SDK) 를 통해 접근한다. 웹에서는 동일
   *  어댑터가 내부적으로 localStorage 로 폴백한다. 로드 순서 때문에 어댑터가
   *  아직 안 떴을 때만 최후의 수단으로 localStorage 를 직접 쓴다. */
  function tossStore() {
    const a = (typeof window !== "undefined" && window.TossAdapter) || null;
    return a?.storage || null;
  }

  function localHistoryKey(userId) {
    return `${LOCAL_HISTORY_KEY_PREFIX}${userId || "guest"}`;
  }

  async function readLocalHistory(userId) {
    try {
      let raw = null;
      const s = tossStore();
      if (s?.getItem) {
        raw = await s.getItem(localHistoryKey(userId));
      } else if (typeof window !== "undefined" && window.localStorage) {
        raw = window.localStorage.getItem(localHistoryKey(userId));
      }
      if (!raw) return [];
      const parsed = JSON.parse(raw);
      return Array.isArray(parsed) ? parsed : [];
    } catch (_) {
      return [];
    }
  }

  async function writeLocalHistory(userId, items) {
    const payload = JSON.stringify(items.slice(0, LOCAL_HISTORY_MAX));
    try {
      const s = tossStore();
      if (s?.setItem) {
        await s.setItem(localHistoryKey(userId), payload);
      } else if (typeof window !== "undefined" && window.localStorage) {
        window.localStorage.setItem(localHistoryKey(userId), payload);
      }
    } catch (err) {
      console.warn("[auth] localHistory write failed:", err);
    }
  }

  function mergeHistory(serverItems, localItems) {
    // 서버 우선 + 로컬 보조. 서버에 이미 있는 분석은 로컬 캐시에서 빼서
    // "같은 파일이 두 번 뜨는" 문제를 방지한다.
    //   1) id(history_id) / local_id 로 직접 중복 제거
    //   2) 로컬 항목이 서버 항목과 "같은 파일 + 5분 이내" 면 같은 분석으로 간주
    const out = [];
    const seenIds = new Set();
    const serverBuckets = new Map(); // filename -> [created_at_ms]
    const server = Array.isArray(serverItems) ? serverItems : [];
    const local = Array.isArray(localItems) ? localItems : [];

    for (const it of server) {
      if (!it) continue;
      const key = it.history_id || `srv:${it.filename}:${it.created_at}`;
      if (seenIds.has(key)) continue;
      seenIds.add(key);
      out.push(it);
      const bucket = serverBuckets.get(it.filename || "") || [];
      bucket.push(Date.parse(it.created_at || 0) || 0);
      serverBuckets.set(it.filename || "", bucket);
    }

    const WINDOW_MS = 5 * 60 * 1000; // 5분
    for (const it of local) {
      if (!it) continue;
      const key = it.history_id || it.local_id || `loc:${it.filename}:${it.created_at}`;
      if (seenIds.has(key)) continue;
      // 서버에 같은 파일명이 최근에 들어왔으면 로컬은 버린다 (서버가 진실)
      const bucket = serverBuckets.get(it.filename || "");
      if (bucket) {
        const localTs = Date.parse(it.created_at || 0) || 0;
        const near = bucket.some((ts) => Math.abs(ts - localTs) <= WINDOW_MS);
        if (near) continue;
      }
      seenIds.add(key);
      out.push(it);
    }

    out.sort((a, b) => {
      const ta = Date.parse(a.created_at || 0) || 0;
      const tb = Date.parse(b.created_at || 0) || 0;
      return tb - ta;
    });
    return out;
  }

  // --- 전역 스토어 (간단한 pub/sub) -----------------------------------------
  const store = {
    state: {
      ready: false,               // /client-config 로딩 끝났나
      configError: null,
      // 인증 모드. /client-config 응답에서 받아옴. UI 가 로그인 폼을 띄울지
      // (supabase-jwt) vs. 무화면 hash 부트스트랩만 할지(toss-anonymous) 분기.
      authMode: "toss-anonymous",
      authEnabled: false,         // (레거시) supabase-jwt 모드에서 supabase 클라가 떴는지
      paymentsEnabled: false,
      tossClientKey: null,
      // Task #117 — 앱인토스 인앱광고 스캐폴드. /client-config 에서 내려오는 값.
      // ID 가 비어 있으면 UI 상의 배너·보상형 CTA 는 렌더 안 함(정책 위반 방지).
      adsBannerUnitId: null,
      adsRewardedUnitId: null,
      adsRewardedGrantUnits: 0,
      // Task #197 — 앱인토스 IAP 상품 ID 매핑 (콘솔 등록값).
      // 값이 null 이면 BillingSheet 가 결제 버튼을 비활성화하고 "결제 준비 중" 표시.
      iapProductIdSingleText: null,
      iapProductIdSingleOcr: null,
      iapProductIdPack: null,
      supabase: null,             // Supabase 클라이언트 (toss-anonymous 모드에선 null)
      session: null,              // { access_token, user, expires_at, ... } | null
      // toss-anonymous 모드에선 user = { id: hash, email: "" } 형태 — 다운스트림
      // 코드(useAuth().user?.id)가 그대로 동작하도록 같은 인터페이스 유지.
      user: null,
      account: null,              // { email, free_count, paid_credits, ... } | null
      accountLoading: false,
      authBusy: false,            // 로그인/가입 진행 중 (supabase-jwt 한정)
      authError: null,            // 마지막 에러 메시지
      // toss-anonymous 모드에서 hash 발급 실패한 경우의 사용자 노출 메시지.
      // null 이면 정상. 값이 있으면 진입 차단 + 안내 화면.
      tossBootstrapError: null,
      history: [],                // AnalysisHistoryItem[] — 최근 분석 목록 (서버+로컬 병합)
      historyLoading: false,
      historyError: null,
      historySource: "empty",     // "server" | "local" | "merged" | "empty"
    },
    listeners: new Set(),
    set(patch) {
      this.state = { ...this.state, ...patch };
      this.listeners.forEach((fn) => fn());
    },
    subscribe(fn) {
      this.listeners.add(fn);
      return () => this.listeners.delete(fn);
    },
  };

  // --- API 호출 헬퍼 -------------------------------------------------------
  async function refreshAccount(accessToken) {
    if (!accessToken) return;
    store.set({ accountLoading: true });
    try {
      const res = await fetch("/account/profile", {
        headers: { Authorization: `Bearer ${accessToken}` },
      });
      if (!res.ok) {
        // 401이면 세션이 만료된 것. 일단 account만 비워두고 로그아웃은 onAuthStateChange 흐름에 맡김
        if (res.status === 401) {
          store.set({ account: null, accountLoading: false });
          return;
        }
        throw new Error(`계정 정보를 불러오지 못했어요 (HTTP ${res.status})`);
      }
      const data = await res.json();
      store.set({ account: data, accountLoading: false });
    } catch (err) {
      console.warn("[auth] refreshAccount failed:", err);
      store.set({ accountLoading: false });
    }
  }

  async function refreshHistory(accessToken) {
    if (!accessToken) return;
    const userId = store.state.user?.id || store.state.account?.user_id || null;
    const local = await readLocalHistory(userId);
    store.set({ historyLoading: true, historyError: null });
    try {
      const res = await fetch("/account/history", {
        headers: { Authorization: `Bearer ${accessToken}` },
      });
      if (!res.ok) {
        if (res.status === 401) {
          store.set({ history: local, historyLoading: false, historySource: local.length ? "local" : "empty" });
          return;
        }
        throw new Error(`기록을 불러오지 못했어요 (HTTP ${res.status})`);
      }
      const data = await res.json();
      const serverItems = Array.isArray(data?.items) ? data.items : [];
      const merged = mergeHistory(serverItems, local);
      const source =
        serverItems.length && local.length ? "merged" :
        serverItems.length ? "server" :
        local.length ? "local" :
        "empty";
      if (!serverItems.length && local.length) {
        // 서버가 비었다는 건 Supabase `analysis_history` 테이블이 없거나
        // RLS로 조회가 안 되는 상태일 가능성이 높다. 콘솔에 힌트를 남겨
        // 디버깅을 쉽게 한다.
        console.info(
          "[auth] 서버 히스토리가 비어서 로컬 캐시에서 불러왔어요. " +
          "Supabase에 analysis_history 테이블이 없거나 RLS 정책이 조회를 막고 있을 수 있어요."
        );
      }
      store.set({ history: merged, historyLoading: false, historySource: source });
    } catch (err) {
      console.warn("[auth] refreshHistory failed:", err);
      // 네트워크 실패 시에도 로컬 기록은 살려 둔다.
      store.set({
        history: local,
        historyLoading: false,
        historyError: err?.message || String(err),
        historySource: local.length ? "local" : "empty",
      });
    }
  }

  /* 분석이 막 끝났을 때 호출. /analyze 응답을 즉시 로컬 히스토리에 넣어
   * 서버 저장 여부와 무관하게 기록 탭에 곧바로 떠 있게 한다.
   * (내부적으로 async — 호출부는 awaited 여부와 관계없이 동작) */
  async function rememberAnalysis(result) {
    if (!result || typeof result !== "object") return;
    const userId = store.state.user?.id || store.state.account?.user_id || null;
    const existing = await readLocalHistory(userId);
    const now = new Date().toISOString();
    const item = {
      // server history_id가 생기면 refreshHistory 쪽이 교체한다. 그 전까지는
      // local_id로 유일하게 식별한다.
      history_id: "",
      local_id: `local-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
      filename: result.filename || "분석한 계약서",
      contract_type_label: result.contract_type_label || "계약서",
      analysis_mode: result.analysis_mode || "llm",
      summary: (result.summary || "").trim(),
      issue_count: Array.isArray(result.issues) ? result.issues.length : 0,
      missing_count: Array.isArray(result.missing_items) ? result.missing_items.length : 0,
      page_count: result.page_count || 1,
      created_at: now,
      result,
    };
    // 동일 파일명이면 최신 것으로 교체
    const dedup = existing.filter((it) => (it.filename || "") !== item.filename);
    const next = [item, ...dedup].slice(0, LOCAL_HISTORY_MAX);
    await writeLocalHistory(userId, next);
    // 스토어에도 즉시 반영 (서버 refresh가 늦게 도착해도 UI에 바로 보이게)
    const serverOnly = (store.state.history || []).filter((it) => !it.local_id);
    store.set({
      history: mergeHistory(serverOnly, next),
      historySource: "local",
    });
  }

  /* 히스토리 한 건 삭제.
   *  - 서버 저장분(history_id 있음) → DELETE /account/history/{id} 호출
   *  - 로컬 전용(local_id 만 있음) → 로컬 캐시에서만 제거
   *  어느 쪽이든 스토어에서는 즉시 사라지게 하고, 로컬 캐시는 동기화한다.
   *  반환값: { ok: true } | { ok: false, error } */
  async function deleteHistory(target) {
    if (!target) return { ok: false, error: new Error("삭제할 기록이 없어요") };
    const accessToken = store.state.session?.access_token || null;
    const userId = store.state.user?.id || store.state.account?.user_id || null;
    const hid = typeof target === "string" ? target : (target.history_id || "");
    const localId = typeof target === "string" ? "" : (target.local_id || "");

    // 1) 서버에 저장된 건이면 DELETE 호출
    if (hid && accessToken) {
      try {
        const res = await fetch(`/account/history/${encodeURIComponent(hid)}`, {
          method: "DELETE",
          headers: { Authorization: `Bearer ${accessToken}` },
        });
        if (!res.ok && res.status !== 404) {
          // 404는 이미 사라진 상태 — 성공과 동일하게 취급한다.
          const text = await res.text().catch(() => "");
          throw new Error(text || `삭제하지 못했어요 (HTTP ${res.status})`);
        }
      } catch (err) {
        console.warn("[auth] deleteHistory server call failed:", err);
        return { ok: false, error: err };
      }
    }

    // 2) 스토어에서 제거
    const nextHistory = (store.state.history || []).filter((it) => {
      if (hid && it.history_id === hid) return false;
      if (localId && it.local_id === localId) return false;
      return true;
    });
    store.set({ history: nextHistory });

    // 3) 로컬 캐시 동기화
    try {
      const local = await readLocalHistory(userId);
      const nextLocal = local.filter((it) => {
        if (hid && it.history_id === hid) return false;
        if (localId && it.local_id === localId) return false;
        return true;
      });
      if (nextLocal.length !== local.length) {
        await writeLocalHistory(userId, nextLocal);
      }
    } catch (err) {
      console.warn("[auth] deleteHistory local cleanup failed:", err);
      // 로컬 정리는 실패해도 서버/스토어 상태는 이미 정리됨 — 그대로 성공 처리
    }

    return { ok: true };
  }

  // --- 초기화 --------------------------------------------------------------
  let _initPromise = null;
  async function init() {
    if (_initPromise) return _initPromise;
    _initPromise = (async () => {
      try {
        const res = await fetch("/client-config");
        if (!res.ok) throw new Error(`client-config HTTP ${res.status}`);
        const cfg = await res.json();

        // 공통 환경값 — auth_mode 와 무관하게 먼저 스토어에 반영.
        const commonState = {
          authMode: cfg.auth_mode || "toss-anonymous",
          paymentsEnabled: !!cfg.payments_enabled,
          tossClientKey: cfg.toss_client_key || null,
          adsBannerUnitId: cfg.ads_banner_unit_id || null,
          adsRewardedUnitId: cfg.ads_rewarded_unit_id || null,
          adsRewardedGrantUnits: Number(cfg.ads_rewarded_grant_units) || 0,
          // IAP 상품 ID (Task #197) — 콘솔 등록 전엔 null.
          iapProductIdSingleText: cfg.iap_product_id_single_text || null,
          iapProductIdSingleOcr: cfg.iap_product_id_single_ocr || null,
          iapProductIdPack: cfg.iap_product_id_pack || null,
        };

        if (cfg.auth_mode === "toss-login") {
          await _initTossLogin(commonState);
        } else if (cfg.auth_mode === "toss-anonymous") {
          // [DEPRECATED G-11] 서버가 실수로 이 모드로 설정된 경우의 fallback.
          // 사용자에게 재시작 안내.
          store.set({
            ...commonState,
            ready: true,
            tossBootstrapError: "앱이 업데이트됐어요. 앱을 종료한 뒤 다시 열어 주세요.",
          });
        } else {
          await _initSupabaseLegacy(cfg, commonState);
        }
      } catch (err) {
        console.error("[auth] init failed:", err);
        store.set({
          ready: true,
          configError: err?.message || String(err),
        });
      }
    })();
    return _initPromise;
  }

  /* G-11: 토스 로그인(OAuth) 부트스트랩.
   *  1) TossAdapter.auth.appLogin() → { authorizationCode, referrer }
   *  2) POST /auth/toss-login/exchange → { accessToken, refreshToken, userKey, ... }
   *  3) synthetic session 합성 + localStorage 에 refreshToken 보관 (재진입 용도)
   *
   * 재진입 흐름:
   *  - localStorage 에 refreshToken 이 있으면 먼저 POST /auth/toss-login/refresh 시도
   *  - 실패하면 appLogin() 다시 호출 (약관 이미 동의했으면 UI 없이 바로 code 반환)
   */
  const TOSS_LOGIN_REFRESH_KEY = "toss_login_refresh_token_v1";

  async function _readRefreshToken() {
    try {
      const s = tossStore();
      if (s?.getItem) {
        return await s.getItem(TOSS_LOGIN_REFRESH_KEY);
      }
      if (typeof window !== "undefined" && window.localStorage) {
        return window.localStorage.getItem(TOSS_LOGIN_REFRESH_KEY);
      }
    } catch (_) { /* noop */ }
    return null;
  }

  async function _writeRefreshToken(token) {
    try {
      const s = tossStore();
      if (s?.setItem) {
        await s.setItem(TOSS_LOGIN_REFRESH_KEY, token || "");
        return;
      }
      if (typeof window !== "undefined" && window.localStorage) {
        if (token) window.localStorage.setItem(TOSS_LOGIN_REFRESH_KEY, token);
        else window.localStorage.removeItem(TOSS_LOGIN_REFRESH_KEY);
      }
    } catch (_) { /* noop */ }
  }

  async function _initTossLogin(commonState) {
    const adapter = (typeof window !== "undefined" && window.TossAdapter) || null;

    // 1) refresh 시도 — 재진입 시 토스 로그인 UI 없이 바로 세션 복원
    const savedRefresh = await _readRefreshToken();
    if (savedRefresh) {
      const refreshOk = await _attemptTossLoginRefresh(savedRefresh, commonState);
      if (refreshOk) return;
      // 실패했으면 refresh token 무효 → 새 로그인 유도
      await _writeRefreshToken(null);
    }

    // 2) 새 로그인 — appLogin() 호출
    if (!adapter || !adapter.auth || typeof adapter.auth.appLogin !== "function") {
      store.set({
        ...commonState,
        ready: true,
        tossBootstrapError:
          "앱이 아직 준비 중이에요. 잠시 뒤 다시 열어 주세요. (TossAdapter 미부착)",
      });
      return;
    }

    let loginResult = null;
    try {
      loginResult = await adapter.auth.appLogin();
    } catch (err) {
      console.error("[auth] appLogin threw:", err);
      loginResult = { authorizationCode: null, referrer: null, error: "ERROR" };
    }

    if (!loginResult || !loginResult.authorizationCode) {
      const code = loginResult?.error || "ERROR";
      const msg =
        code === "USER_CANCELLED"
          ? "로그인을 취소했어요. 다시 시도해 주세요."
          : "토스 로그인을 완료하지 못했어요. 잠시 뒤 다시 열어 주세요.";
      console.warn("[auth] appLogin rejected:", code);
      store.set({ ...commonState, ready: true, tossBootstrapError: msg });
      return;
    }

    // 3) 백엔드에 인가 코드 교환 요청
    try {
      const res = await fetch("/auth/toss-login/exchange", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          authorizationCode: loginResult.authorizationCode,
          referrer: loginResult.referrer || "DEFAULT",
        }),
      });
      if (!res.ok) {
        const body = await res.json().catch(() => ({}));
        const msg = body?.detail || `로그인 교환 실패 (HTTP ${res.status})`;
        console.warn("[auth] exchange failed:", msg);
        store.set({ ...commonState, ready: true, tossBootstrapError: msg });
        return;
      }
      const data = await res.json();
      await _applyTossLoginSession(data, commonState);
    } catch (err) {
      console.error("[auth] exchange network error:", err);
      store.set({
        ...commonState,
        ready: true,
        tossBootstrapError: "네트워크 오류로 로그인을 마치지 못했어요. 잠시 뒤 다시 시도해 주세요.",
      });
    }
  }

  async function _attemptTossLoginRefresh(refreshToken, commonState) {
    try {
      const res = await fetch("/auth/toss-login/refresh", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ refreshToken }),
      });
      if (!res.ok) return false;
      const data = await res.json();
      await _applyTossLoginSession(data, commonState);
      return true;
    } catch (_) {
      return false;
    }
  }

  async function _applyTossLoginSession(data, commonState) {
    // 백엔드 응답 → frontend session 객체
    const accessToken = data.access_token;
    const expiresAtSec = data.expires_in
      ? Math.floor(Date.now() / 1000) + Number(data.expires_in)
      : Math.floor(Date.now() / 1000) + 3600;
    const session = {
      access_token: accessToken,
      expires_at: expiresAtSec,
      user: {
        id: data.user_key,
        email: data.email || "",
        // 디버그: 서버에서 내려준 scope 확인용
        toss_scope: data.scope || "",
      },
    };
    await _writeRefreshToken(data.refresh_token || "");

    store.set({
      ...commonState,
      ready: true,
      supabase: null,
      authEnabled: false,
      session,
      user: session.user,
      tossBootstrapError: null,
      account: {
        email: data.email || "",
        free_uses_remaining: Number(data.free_uses_remaining || 0),
        credits: Number(data.credits || 0),
        unlimited: !!data.unlimited,
      },
    });

    const cached = await readLocalHistory(session.user.id);
    if (cached.length) {
      store.set({ history: cached, historySource: "local" });
    }
    refreshAccount(accessToken);
    refreshHistory(accessToken);
  }

  /* 레거시: Supabase 이메일/패스워드 경로. AUTH_MODE=supabase-jwt 일 때만 진입.
   * G-9 검증 끝나면 .env 에서 toss-anonymous 로 전환되어 더 이상 호출되지 않음. */
  async function _initSupabaseLegacy(cfg, commonState) {
    if (!cfg.auth_enabled || !cfg.supabase_url || !cfg.supabase_anon_key) {
      store.set({
        ...commonState,
        ready: true,
        configError: "로그인 설정이 빠져 있어요. 관리자에게 환경 변수(.env)를 확인해 달라고 해 주세요.",
      });
      return;
    }

    if (!window.supabase || typeof window.supabase.createClient !== "function") {
      store.set({
        ...commonState,
        ready: true,
        configError: "로그인에 필요한 라이브러리가 아직 안 떴어요. 잠시 뒤 새로고침해 주세요.",
      });
      return;
    }

    // Supabase 세션 저장소를 TossAdapter.storage 로 라우팅한다.
    // 앱인토스에서는 Toss Storage SDK(install 후), 웹에서는 localStorage.
    // Supabase v2 는 async storage 를 공식 지원한다.
    const adapterStore = tossStore();
    const supabaseStorage = adapterStore
      ? {
          getItem: (k) => adapterStore.getItem(k),
          setItem: (k, v) => adapterStore.setItem(k, v),
          removeItem: (k) => adapterStore.removeItem(k),
        }
      : undefined; // 어댑터가 없으면 Supabase 기본값(localStorage) 사용
    const client = window.supabase.createClient(cfg.supabase_url, cfg.supabase_anon_key, {
      auth: {
        persistSession: true,
        autoRefreshToken: true,
        detectSessionInUrl: true,
        ...(supabaseStorage ? { storage: supabaseStorage } : {}),
      },
    });

    // 세션 변경 구독 (로그인/로그아웃/토큰 갱신 등)
    client.auth.onAuthStateChange(async (_event, nextSession) => {
      store.set({
        session: nextSession,
        user: nextSession?.user ?? null,
      });
      if (nextSession?.access_token) {
        const cached = await readLocalHistory(nextSession.user?.id || null);
        if (cached.length) {
          store.set({ history: cached, historySource: "local" });
        }
        refreshAccount(nextSession.access_token);
        refreshHistory(nextSession.access_token);
      } else {
        store.set({ account: null, history: [], historySource: "empty" });
      }
    });

    // 기존 세션 복원 (새로고침 시)
    const { data: { session } } = await client.auth.getSession();

    store.set({
      ...commonState,
      ready: true,
      supabase: client,
      authEnabled: true,
      session,
      user: session?.user ?? null,
    });

    if (session?.access_token) {
      const cached = await readLocalHistory(session.user?.id || null);
      if (cached.length) {
        store.set({ history: cached, historySource: "local" });
      }
      refreshAccount(session.access_token);
      refreshHistory(session.access_token);
    }
  }

  // --- 로그인/가입/로그아웃 ------------------------------------------------
  // toss-anonymous 모드에서는 signIn / signUp 이 의미 없는 호출 — UI 가
  // 이메일 폼을 띄우지 않으므로 호출 자체가 사라져야 정상. 안전망으로 명확한
  // 에러를 돌려서 잘못된 호출을 디버깅 단계에서 잡는다.
  async function signIn(email, password) {
    if (store.state.authMode === "toss-anonymous") {
      const err = new Error("이 빌드는 토스 로그인만 지원해요. 이메일 로그인은 더 이상 사용하지 않아요.");
      console.warn("[auth] signIn called in toss-anonymous mode — ignoring");
      return { ok: false, error: err };
    }
    const client = store.state.supabase;
    if (!client) return { ok: false, error: new Error("로그인 준비가 아직 안 됐어요") };
    store.set({ authBusy: true, authError: null });
    try {
      const { data, error } = await client.auth.signInWithPassword({ email, password });
      if (error) throw error;
      store.set({ authBusy: false });
      return { ok: true, data };
    } catch (err) {
      const msg = err?.message || "로그인하지 못했어요. 잠시 뒤 다시 시도해 주세요.";
      store.set({ authBusy: false, authError: msg });
      return { ok: false, error: err };
    }
  }

  async function signUp(email, password) {
    if (store.state.authMode === "toss-anonymous") {
      const err = new Error("이 빌드는 가입 절차가 없어요. 토스로 자동 로그인돼요.");
      console.warn("[auth] signUp called in toss-anonymous mode — ignoring");
      return { ok: false, error: err };
    }
    const client = store.state.supabase;
    if (!client) return { ok: false, error: new Error("가입 준비가 아직 안 됐어요") };
    store.set({ authBusy: true, authError: null });
    try {
      const { data, error } = await client.auth.signUp({ email, password });
      if (error) throw error;
      store.set({ authBusy: false });
      return { ok: true, data };
    } catch (err) {
      const msg = err?.message || "가입하지 못했어요. 잠시 뒤 다시 시도해 주세요.";
      store.set({ authBusy: false, authError: msg });
      return { ok: false, error: err };
    }
  }

  /* 토스 익명 hash 모드의 "로그아웃" 정의:
   * - 백엔드 세션이 없어서 logout API 호출은 없음.
   * - 같은 hash 가 토스 SDK 캐시에 남아 있으므로 단순 setState 만으로는 의미가
   *   없다 (다음 새로고침에 똑같은 hash 가 다시 발급됨).
   * - 그래서 우리는 (a) web-fallback hash 캐시를 비우고 (b) 로컬 스토어를
   *   초기화한 뒤 (c) 새 hash 로 부트스트랩을 다시 돌린다.
   * - 결과적으로 "베타 데이터 리셋" 같은 사용자 경험을 만들 수 있다.
   *   (실제 토스 환경에서는 SDK 가 같은 hash 를 줄 가능성이 높아 진짜 로그아웃은
   *    아니지만, 사용자 시점에서 "내 흔적을 지웠다" 는 신호 역할을 한다.) */
  async function signOut() {
    if (store.state.authMode === "toss-anonymous") {
      try {
        // (a) 웹 폴백 hash 캐시 제거 — 토스 SDK 캐시는 우리가 못 건드림.
        try { window.localStorage.removeItem("toss_anon_hash_web_fallback_v1"); } catch (_) { /* private mode */ }

        // (b) 스토어 초기화 — UI 가 즉시 "로그아웃 직후" 상태로 보이게.
        store.set({
          session: null,
          user: null,
          account: null,
          history: [],
          historySource: "empty",
        });

        // (c) 새 hash 로 다시 부트스트랩. _initPromise 를 비워서 init() 을 처음부터 돌림.
        _initPromise = null;
        await init();
      } catch (err) {
        console.warn("[auth] toss signOut/rebootstrap failed:", err);
      }
      return;
    }
    const client = store.state.supabase;
    if (!client) return;
    try {
      await client.auth.signOut();
      // onAuthStateChange가 session=null로 발생시키면 자동 정리됨
    } catch (err) {
      console.warn("[auth] signOut failed:", err);
    }
  }

  function clearAuthError() {
    if (store.state.authError) store.set({ authError: null });
  }

  // --- 인증된 API fetch 래퍼 -----------------------------------------------
  // 이후 단계(업로드/결제/히스토리)에서 공용으로 쓸 helper.
  async function apiFetch(path, options = {}) {
    const s = store.state.session;
    const headers = new Headers(options.headers || {});
    if (s?.access_token) headers.set("Authorization", `Bearer ${s.access_token}`);
    if (!headers.has("Accept")) headers.set("Accept", "application/json");
    const res = await fetch(path, { ...options, headers });
    return res;
  }

  // --- React 훅 ------------------------------------------------------------
  function useAuth() {
    const [, force] = useStateAuth({});
    useEffectAuth(() => store.subscribe(() => force({})), []);
    return {
      ...store.state,
      signIn,
      signUp,
      signOut,
      clearAuthError,
      refreshAccount: () => {
        const t = store.state.session?.access_token;
        if (t) return refreshAccount(t);
      },
      refreshHistory: () => {
        const t = store.state.session?.access_token;
        if (t) return refreshHistory(t);
      },
      rememberAnalysis,
      deleteHistory,
    };
  }

  // 전역 노출
  window.__authStore = store;
  window.useAuth = useAuth;
  window.initAuth = init;
  window.apiFetch = apiFetch;
  window.rememberAnalysis = rememberAnalysis;

  // 자동 초기화
  init();
})();
