import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { createRoot } from "react-dom/client";
import { AnimatePresence, motion } from "framer-motion";
import {
  Anchor,
  ArrowRight,
  BadgeCheck,
  BrainCircuit,
  BriefcaseBusiness,
  CalendarClock,
  BellRing,
  ChevronRight,
  Coins,
  Compass,
  Crown,
  Pencil,
  GraduationCap,
  HeartHandshake,
  Home,
  LogIn,
  MapPinned,
  MessageCircle,
  Menu,
  MoonStar,
  Plane,
  Search,
  ShipWheel,
  Sparkles,
  Trash2,
  UserRound,
  Waves,
  X
} from "lucide-react";

const BRAND_NAME = "费曼";
const BRAND_ENGLISH = "FateMind";
const BRAND_TAGLINE = "FateMind · AI 命理人生測試平台";
const LOGO_SRC = "./src/assets/fatemind-logo-mark-512.png";

const navItems = [
  ["命理測驗", "tests"],
  ["大師在線", "master"],
  ["费曼号會員", "membership"]
];

function navLinkFor(id, isStandaloneRoute = false) {
  if (id === "membership") return isStandaloneRoute ? "/preview.html#membership" : "#membership";
  if (id === "master") return "/preview.html#master";
  return isStandaloneRoute ? `/preview.html#${id}` : `#${id}`;
}

const testEntries = [
  {
    id: "city-development",
    title: "你適合在哪個城市發展？",
    desc: "基於八字排盤、五行喜忌、出生地與目前身份，測算更適合你發展的台灣境內與海外 / 其他地區城市。",
    icon: MapPinned,
    tag: "熱門",
    category: "發展",
    basis: "以出生地、目前國籍與排盤後的五行喜忌為基準",
    method: "結合傳統排盤與盲派八字喜忌，判斷什麼樣的地方更適合你發展。",
    output: [
      "你的五行喜忌，以及什麼樣的地方更適合你",
      "以你出生地和目前國籍為基準的遷移發展判斷",
      "台灣境內適合你發展的區域和城市",
      "海外 / 其他地區適合你發展的區域和城市"
    ]
  },
  {
    id: "career-change",
    title: "今年適合換工作嗎？",
    desc: "判斷機會窗口、貴人方向與風險月份。",
    icon: BriefcaseBusiness,
    tag: "事業",
    category: "事業"
  },
  {
    id: "love-profile",
    title: "你的正緣畫像是什麼？",
    desc: "性格、年齡差、相遇場景與關係走勢。",
    icon: HeartHandshake,
    tag: "情感",
    category: "關係"
  },
  {
    id: "wealth-curve",
    title: "未來三年財富曲線",
    desc: "看財運節奏、投資傾向與存錢能力。",
    icon: Coins,
    tag: "財富",
    category: "財富"
  },
  {
    id: "migration-choice",
    title: "你適合移民還是回國？",
    desc: "比較國家、城市與長期生活適配度。",
    icon: Plane,
    tag: "遷移",
    category: "發展"
  },
  {
    id: "house-fit",
    title: "你適合住什麼樣的房子？",
    desc: "從風水、方位、戶型判斷居住能量。",
    icon: Home,
    tag: "風水",
    category: "生活"
  },
  {
    id: "metaphysics-school",
    title: "最適合你的玄學流派",
    desc: "六爻、八字、紫微、奇門的學習匹配。",
    icon: GraduationCap,
    tag: "學習",
    category: "成長"
  },
  {
    id: "stress-source",
    title: "你的年度壓力來源",
    desc: "識別壓力宮位、時間點與調節建議。",
    icon: MoonStar,
    tag: "療癒",
    category: "生活"
  },
  {
    id: "startup-window",
    title: "你什麼時候適合創業？",
    desc: "拆解啟動窗口、合夥關係與現金流承壓能力。",
    icon: BriefcaseBusiness,
    tag: "創業",
    category: "事業"
  },
  {
    id: "noble-direction",
    title: "你的貴人在哪個方向？",
    desc: "定位更適合你連結的人群、場域與城市方位。",
    icon: Compass,
    tag: "貴人",
    category: "發展"
  },
  {
    id: "annual-risk",
    title: "你今年最需要避開的風險？",
    desc: "提前看見容易踩坑的月份、關係與決策類型。",
    icon: MoonStar,
    tag: "風險",
    category: "生活"
  },
  {
    id: "marriage-years",
    title: "你的婚姻關鍵年份",
    desc: "識別相遇、確認、磨合與升級關係的時間點。",
    icon: CalendarClock,
    tag: "婚姻",
    category: "關係"
  }
];

const TESTS_CATALOG_UPDATED_EVENT = "fatemind-tests-catalog-updated";
const TESTS_CATALOG_UPDATED_STORAGE_KEY = "fatemind_tests_catalog_updated_at";
const TESTS_CATALOG_SNAPSHOT_STORAGE_KEY = "fatemind_tests_catalog_snapshot";

const testIconByCategory = {
  發展: MapPinned,
  事業: BriefcaseBusiness,
  關係: HeartHandshake,
  財富: Coins,
  生活: Home,
  成長: GraduationCap,
  綜合: Sparkles
};

const defaultTestById = Object.fromEntries(testEntries.map((item) => [item.id, item]));

function mergeServerTests(serverTests = []) {
  if (!serverTests.length) return testEntries;
  return serverTests
    .filter((item) => item.status !== "archived")
    .map((item) => {
      const defaults = defaultTestById[item.id] || {};
      const category = String(item.category || defaults.category || "未分類").trim();
      const tag = String(item.tag || "").trim() || category || defaults.tag || "測試";
      return {
        ...defaults,
        ...item,
        category,
        desc: item.desc || defaults.desc || "完成一組輕問答，生成你的專屬基礎報告。",
        tag,
        icon: defaults.icon || testIconByCategory[category] || Sparkles,
        output: Array.isArray(item.output) ? item.output : defaults.output,
        method: item.method || defaults.method,
        basis: item.basis || defaults.basis
      };
    });
}

function readTestsCatalogSnapshot() {
  try {
    const payload = JSON.parse(localStorage.getItem(TESTS_CATALOG_SNAPSHOT_STORAGE_KEY) || "null");
    return Array.isArray(payload?.tests) ? payload.tests : null;
  } catch {
    return null;
  }
}

function broadcastTestsCatalogUpdated(tests = null) {
  const updatedAt = String(Date.now());
  const detail = { updatedAt, tests: Array.isArray(tests) ? tests : null };
  try {
    if (detail.tests) {
      localStorage.setItem(TESTS_CATALOG_SNAPSHOT_STORAGE_KEY, JSON.stringify({ updatedAt, tests: detail.tests }));
    }
    localStorage.setItem(TESTS_CATALOG_UPDATED_STORAGE_KEY, updatedAt);
  } catch {
    // 跨頁同步失敗時不阻塞後台保存。
  }
  window.dispatchEvent(new CustomEvent(TESTS_CATALOG_UPDATED_EVENT, { detail }));
}

function getRecommendedTests(tests, currentTest) {
  return (tests || [])
    .filter((item) => item.status === undefined || item.status === "published")
    .filter((item) => (item.id || item.title) !== (currentTest?.id || currentTest?.title))
    .sort((a, b) => Number(b.recommendationPriority || 0) - Number(a.recommendationPriority || 0))
    .slice(0, 3);
}

function productsToText(products = []) {
  return (products || [])
    .map((product) => [
      product.id,
      product.type,
      product.name,
      product.priceCents,
      product.durationDays || "",
      product.credits || "",
      product.status || "active",
      product.providerProductId || "",
      product.providerVariantId || "",
      product.providerPriceId || "",
      product.description || ""
    ].join("｜"))
    .join("\n");
}

function productsFromText(text = "") {
  return String(text)
    .split("\n")
    .map((line) => line.trim())
    .filter(Boolean)
    .map((line) => {
      const [id, type, name, priceCents, durationDays, credits, status, providerProductId, providerVariantId, providerPriceId, description] = line.split(/[｜|]/).map((part) => part.trim());
      return {
        id,
        type: type || "one_time",
        name: name || id,
        priceCents: Number(priceCents || 0),
        durationDays: durationDays ? Number(durationDays) : undefined,
        credits: credits ? Number(credits) : undefined,
        status: status || "active",
        providerProductId: providerProductId || "",
        providerVariantId: providerVariantId || "",
        providerPriceId: providerPriceId || "",
        description: description || ""
      };
    })
    .filter((product) => product.id && product.name);
}

function paymentProvidersToText(providers = []) {
  return (providers || [])
    .map((provider) => [
      provider.id,
      provider.name,
      provider.enabled ? "on" : "off",
      provider.mode || "test",
      provider.checkoutUrl || "",
      provider.successUrl || "",
      provider.cancelUrl || "",
      provider.webhookSecret && provider.webhookSecret !== "已配置" ? provider.webhookSecret : "",
      provider.webhookToleranceSeconds || "",
      provider.notes || ""
    ].join("｜"))
    .join("\n");
}

function paymentProvidersFromText(text = "") {
  return String(text)
    .split("\n")
    .map((line) => line.trim())
    .filter(Boolean)
    .map((line) => {
      const [id, name, enabled, mode, checkoutUrl, successUrl, cancelUrl, webhookSecret, webhookToleranceSeconds, notes] = line.split(/[｜|]/).map((part) => part.trim());
      return {
        id,
        name: name || id,
        enabled: enabled === "on" || enabled === "true",
        mode: mode || "test",
        checkoutUrl: checkoutUrl || "",
        successUrl: successUrl || "",
        cancelUrl: cancelUrl || "",
        webhookSecret: webhookSecret || "",
        webhookToleranceSeconds: webhookToleranceSeconds ? Number(webhookToleranceSeconds) : undefined,
        notes: notes || ""
      };
    })
    .filter((provider) => provider.id);
}


const marketProfiles = {
  tw: {
    id: "tw",
    name: "台灣首發市場",
    locale: "zh-Hant-TW",
    launchTag: "台灣首發驗證版",
    currency: "TWD",
    currencySymbol: "NT$",
    monthlyPriceCents: 29900,
    annualPriceCents: 299000,
    annualSavingCents: 59800,
    paymentProvider: "Lemon Squeezy / Paddle 預留",
    launchNotes: ["繁體中文優先", "新台幣計價", "信用卡與跨境收款先行", "保留後續接入本地支付"]
  }
};

const activeMarket = marketProfiles.tw;

function formatMoneyCents(amountCents, currencySymbol = activeMarket.currencySymbol, locale = activeMarket.locale) {
  const amount = Math.round(Number(amountCents || 0) / 100);
  return `${currencySymbol}${amount.toLocaleString(locale)}`;
}

function currencySymbolFor(currency, fallback = activeMarket.currencySymbol) {
  if (currency === "TWD") return "NT$";
  if (currency === "CNY") return "￥";
  if (currency === "USD") return "$";
  return fallback;
}

function formatOrderMoney(order, settings = {}) {
  return formatMoneyCents(order?.amountCents || 0, order?.currencySymbol || currencySymbolFor(order?.currency || settings.currency, settings.currencySymbol), settings.locale || activeMarket.locale);
}

function orderProductLabel(order = {}) {
  if (order.productName) return order.productName;
  if (order.productId === "full-report") return "單次完整命理報告";
  if (order.plan === "annual") return "年付會員";
  if (order.plan === "monthly") return "月付會員";
  return order.productId || "费曼商品";
}

function buildMembershipPlans(settings = activeMarket) {
  const currencySymbol = settings.currencySymbol || activeMarket.currencySymbol;
  const locale = settings.locale || activeMarket.locale;
  const monthlyPriceCents = Number(settings.monthlyPriceCents || activeMarket.monthlyPriceCents);
  const annualPriceCents = Number(settings.annualPriceCents || activeMarket.annualPriceCents);
  const annualSavingCents = Math.max(monthlyPriceCents * 12 - annualPriceCents, 0);
  const annualMonthlyCents = Math.round(annualPriceCents / 12);
  return {
    monthly: {
      id: "monthly",
      name: "月付會員",
      badge: "靈活體驗",
      price: Math.round(monthlyPriceCents / 100),
      priceCents: monthlyPriceCents,
      currencySymbol,
      locale,
      period: "月",
      billing: "每月付費，當月有效",
      note: "適合先完整體驗费曼号能力",
      cta: "開通月付會員"
    },
    annual: {
      id: "annual",
      name: "年付會員",
      badge: "推薦 · 約兩個月免費",
      price: Math.round(annualPriceCents / 100),
      priceCents: annualPriceCents,
      currencySymbol,
      locale,
      period: "年",
      billing: "一次付費，全年有效",
      note: `折合 ${formatMoneyCents(annualMonthlyCents, currencySymbol, locale)}/月，比月付省 ${formatMoneyCents(annualSavingCents, currencySymbol, locale)}`,
      cta: "開通年付會員"
    }
  };
}

const membershipPlans = {
  ...buildMembershipPlans(activeMarket)
};

const memberBenefits = [
  { title: "無限人生測試", desc: "付費期間不限次數體驗所有人生測試與報告生成。", icon: Sparkles },
  { title: "完整命理報告", desc: "聚合八字、測試記錄與命盤資訊，生成系統級人生報告。", icon: BrainCircuit },
  { title: "每日玄學提醒", desc: "每天獲得當日節奏、風險提醒與開運貼士。", icon: BellRing },
  { title: "24 小時 AI 指導", desc: "圍繞報告繼續追問，獲得玄學大師式解讀。", icon: MessageCircle }
];

const accessRows = [
  ["免費測試次數", "1 次", "付費期內無限次"],
  ["保存測試報告", "不可保存", "自動歸檔到會員中心"],
  ["完整命理報告", "不可查看", "可生成並持續更新"],
  ["每日玄學提醒", "不可獲得", "每日推送提醒與開運貼士"],
  ["AI 玄學大師指導", "不可使用", "24 小時線上追問"]
];

function createDefaultCityTestForm() {
  const now = new Date();
  return {
    calendarType: "solar",
    year: String(now.getFullYear()),
    month: String(now.getMonth() + 1),
    day: String(now.getDate()),
    birthTimeMode: "known",
    hour: String(now.getHours()),
    minute: String(now.getMinutes()),
    gender: "female",
    birthPlaceType: "domestic",
    birthPlace: "台灣 台北",
    birthPlaceId: "tw-taipei",
    birthCountryCode: "TW",
    birthCountry: "台灣",
    timezone: "Asia/Taipei",
    currentNationality: "台灣",
    saveBirthInfo: true
  };
}

function getStoredSession() {
  try {
    const session = JSON.parse(localStorage.getItem("fatemind_session") || localStorage.getItem("xuanji_session") || "null");
    if (session?.user?.name === "\u7384\u673a\u7ba1\u7406\u5458") {
      session.user = { ...session.user, name: "费曼管理員" };
      localStorage.setItem("fatemind_session", JSON.stringify(session));
    }
    return session;
  } catch {
    return null;
  }
}

function setStoredSession(session) {
  if (!session) {
    localStorage.removeItem("fatemind_session");
    localStorage.removeItem("xuanji_session");
    return;
  }
  localStorage.setItem("fatemind_session", JSON.stringify(session));
}

function getSavedBirthInfo() {
  try {
    const saved = JSON.parse(localStorage.getItem("fatemind_birth_info") || localStorage.getItem("xuanji_birth_info") || "null");
    if (!saved?.savedAt) return createDefaultCityTestForm();
    return normalizeBirthInfoForTaiwanMarket({ ...createDefaultCityTestForm(), ...saved });
  } catch {
    return createDefaultCityTestForm();
  }
}

function normalizeBirthInfoForTaiwanMarket(form) {
  const birthPlaceText = String(form.birthPlace || "");
  const countryText = String(form.birthCountry || "");
  const inferFromText = (text) =>
    (text.includes("香港") ? "HK" : "") ||
    (text.includes("澳门") || text.includes("澳門") ? "MO" : "") ||
    (text.startsWith("中国") || text.startsWith("中國") || text.startsWith("中國大陸") ? "CN" : "") ||
    (text.includes("台湾") || text.includes("台灣") ? "TW" : "");
  const inferredCode = inferFromText(birthPlaceText) || form.birthCountryCode || inferFromText(countryText);

  if (form.birthPlaceType === "domestic" && inferredCode && inferredCode !== "TW") {
    const country = inferredCode === "CN" ? "中國大陸" : inferredCode === "HK" ? "香港" : inferredCode === "MO" ? "澳門" : form.birthCountry || "海外 / 其他地區";
    return {
      ...form,
      birthPlaceType: "overseas",
      birthCountryCode: inferredCode,
      birthCountry: country,
      birthPlace: String(form.birthPlace || "")
        .replace(/^中国\s+/, "中國大陸 ")
        .replace(/^中國\s+/, "中國大陸 ")
    };
  }
  if (!inferredCode) return form;
  const normalized = normalizeMarketLocation({
    id: form.birthPlaceId,
    label: form.birthPlace,
    countryCode: inferredCode,
    country: form.birthCountry,
    city: String(form.birthPlace || "").split(" ").slice(1).join(" "),
    type: form.birthPlaceType
  });
  return {
    ...form,
    birthPlace: normalized.label || form.birthPlace,
    birthCountryCode: normalized.countryCode || form.birthCountryCode,
    birthCountry: normalized.country || form.birthCountry,
    birthPlaceType: normalized.type || form.birthPlaceType
  };
}

function saveBirthInfo(form) {
  if (form.saveBirthInfo) {
    localStorage.setItem("fatemind_birth_info", JSON.stringify({ ...form, savedAt: new Date().toISOString() }));
    return;
  }
  localStorage.removeItem("fatemind_birth_info");
  localStorage.removeItem("xuanji_birth_info");
}

function getGuestId() {
  const existing = localStorage.getItem("fatemind_guest_id") || localStorage.getItem("xuanji_guest_id");
  if (existing) return existing;
  const guestId = `guest_${Math.random().toString(16).slice(2)}_${Date.now()}`;
  localStorage.setItem("fatemind_guest_id", guestId);
  return guestId;
}

async function apiRequest(path, options = {}) {
  const session = getStoredSession();
  const headers = {
    "Content-Type": "application/json",
    ...(options.headers || {})
  };
  if (session?.token) headers.Authorization = `Bearer ${session.token}`;
  let response;
  try {
    response = await fetch(path, {
      ...options,
      headers,
      body: options.body ? JSON.stringify(options.body) : undefined
    });
  } catch (error) {
    const friendly = new Error("本地服務暫時沒有連上，請確認服務已啟動或重啟後再試。");
    friendly.cause = error;
    throw friendly;
  }
  const data = await response.json().catch(() => ({}));
  if (!response.ok) {
    const apiMissing = response.status === 404 && /API\s*不[存存]?在|API 不存在/i.test(String(data.error || data.message || ""));
    const error = new Error(apiMissing ? "目前本地服務仍是舊版，請重啟本地服務後再試。" : data.message || data.error || "請求失敗");
    error.status = response.status;
    error.data = data;
    error.apiMissing = apiMissing;
    throw error;
  }
  return data;
}

function timezoneOffsetMinutes(timeZone, date = new Date()) {
  const formatter = new Intl.DateTimeFormat("en-US", {
    timeZone,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
    hourCycle: "h23"
  });
  const parts = Object.fromEntries(formatter.formatToParts(date).map((part) => [part.type, part.value]));
  const asUtc = Date.UTC(Number(parts.year), Number(parts.month) - 1, Number(parts.day), Number(parts.hour), Number(parts.minute), Number(parts.second));
  return Math.round((asUtc - date.getTime()) / 60000);
}

function formatTimezoneLabel({ birthPlace, timezone }) {
  if (!timezone) return "未選擇時區";
  const now = new Date();
  const offset = timezoneOffsetMinutes(timezone, now);
  const beijingOffset = timezoneOffsetMinutes("Asia/Shanghai", now);
  const sign = offset >= 0 ? "+" : "-";
  const abs = Math.abs(offset);
  const gmt = `GMT${sign}${Math.floor(abs / 60)}${abs % 60 ? `:${String(abs % 60).padStart(2, "0")}` : ""}`;
  const diffHours = (offset - beijingOffset) / 60;
  const compare = diffHours === 0 ? "與北京時間一致" : diffHours > 0 ? `比北京時間快${Math.abs(diffHours)}小時` : `比北京時間慢${Math.abs(diffHours)}小時`;
  return `${birthPlace || timezone} ${gmt}，${compare}`;
}

function formatGmtOffset(timezone) {
  if (!timezone) return "GMT";
  const offset = timezoneOffsetMinutes(timezone, new Date());
  const sign = offset >= 0 ? "+" : "-";
  const abs = Math.abs(offset);
  const hours = Math.floor(abs / 60);
  const minutes = abs % 60;
  return `GMT${sign}${hours}${minutes ? `:${String(minutes).padStart(2, "0")}` : ""}`;
}

function cx(...classes) {
  return classes.filter(Boolean).join(" ");
}

function SoftButton({ children, variant = "primary", className = "", ...props }) {
  const variants = {
    primary: "bg-white text-slate-950 hover:bg-cyan-100",
    cyan: "bg-cyan-300 text-slate-950 hover:bg-cyan-200",
    outline: "border border-white/20 bg-white/5 text-white hover:bg-white/10"
  };

  return (
    <button
      className={cx(
        "inline-flex h-[52px] items-center justify-center gap-2 rounded-full px-7 text-base font-semibold transition",
        variants[variant],
        className
      )}
      {...props}
    >
      {children}
    </button>
  );
}

function Header({ currentUser, onLogin, onLogout }) {
  const [menuOpen, setMenuOpen] = useState(false);
  const isStandaloneRoute = ["/admin", "/member", "/membership", "/master"].includes(window.location.pathname);
  const navHref = (id) => navLinkFor(id, isStandaloneRoute);

  return (
    <header className="relative z-30 mx-auto flex max-w-7xl items-center justify-between px-6 py-5 lg:px-8">
      <a href={isStandaloneRoute ? "/preview.html#top" : "#top"} className="flex items-center gap-3">
        <div className="flex h-12 w-12 overflow-hidden rounded-2xl border border-cyan-300/30 bg-[#020817] shadow-lg shadow-cyan-500/10 backdrop-blur">
          <img src={LOGO_SRC} alt={`${BRAND_NAME} ${BRAND_ENGLISH}`} className="h-full w-full object-cover" />
        </div>
        <div>
          <div className="text-lg font-semibold tracking-wide text-white">{BRAND_NAME}</div>
          <div className="text-xs text-cyan-100/60">{BRAND_ENGLISH}</div>
        </div>
      </a>

      <nav className="hidden items-center gap-8 text-sm text-white/70 md:flex">
        {navItems.map(([label, id]) => (
          <a key={id} href={navHref(id)} className="transition hover:text-white">
            {label}
          </a>
        ))}
      </nav>

      <div className="hidden items-center gap-3 md:flex">
        <a href={navHref("tests")} className="rounded-full px-4 py-2 text-sm font-medium text-white/80 transition hover:bg-white/10 hover:text-white">
          免費體驗
        </a>
        {currentUser ? (
          <>
            <a href="/member" className="inline-flex h-11 items-center gap-2 rounded-full bg-white/10 px-5 text-sm font-semibold text-white transition hover:bg-white/15">
              <UserRound className="h-4 w-4" />
              {currentUser.name}
            </a>
            {currentUser.role === "admin" && (
              <a href="/admin" className="inline-flex h-11 items-center rounded-full border border-cyan-200/20 bg-cyan-300/10 px-5 text-sm font-semibold text-cyan-100 transition hover:bg-cyan-300/15">
                船長後台
              </a>
            )}
            <button onClick={onLogout} className="inline-flex h-11 items-center rounded-full border border-white/15 bg-white/5 px-5 text-sm font-semibold text-white/80 transition hover:bg-white/10 hover:text-white">
              退出
            </button>
          </>
        ) : (
          <button
            onClick={onLogin}
            className="inline-flex h-11 items-center gap-2 rounded-full bg-cyan-300 px-5 text-sm font-semibold text-slate-950 transition hover:bg-cyan-200"
          >
            <LogIn className="h-4 w-4" />
            登入费曼号
          </button>
        )}
      </div>

      <button
        onClick={() => setMenuOpen((value) => !value)}
        className="inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/10 text-white md:hidden"
        aria-label="打開導航"
      >
        {menuOpen ? <X className="h-5 w-5" /> : <Menu className="h-6 w-6" />}
      </button>

      <AnimatePresence>
        {menuOpen && (
          <motion.div
            initial={{ opacity: 0, y: -8 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: -8 }}
            className="absolute left-6 right-6 top-[76px] rounded-3xl border border-white/10 bg-slate-950/80 p-4 shadow-2xl backdrop-blur-xl md:hidden"
          >
            <div className="flex flex-col gap-2">
              {navItems.map(([label, id]) => (
                <a key={id} href={navHref(id)} onClick={() => setMenuOpen(false)} className="rounded-2xl px-4 py-3 text-sm text-white/80 hover:bg-white/10">
                  {label}
                </a>
              ))}
              {currentUser ? (
                <>
                  <a href="/member" className="rounded-2xl px-4 py-3 text-sm text-white/80 hover:bg-white/10">會員中心</a>
                  {currentUser.role === "admin" && <a href="/admin" className="rounded-2xl px-4 py-3 text-sm text-white/80 hover:bg-white/10">船長後台</a>}
                  <button onClick={onLogout} className="mt-2 rounded-full border border-white/15 bg-white/5 px-5 py-3 text-sm font-semibold text-white">
                    登出
                  </button>
                </>
              ) : (
                <button onClick={onLogin} className="mt-2 rounded-full bg-cyan-300 px-5 py-3 text-sm font-semibold text-slate-950">
                  登入费曼号
                </button>
              )}
            </div>
          </motion.div>
        )}
      </AnimatePresence>
    </header>
  );
}

function CruiseCard() {
  const routeNodes = [
    { left: "18%", top: "24%", label: "事業" },
    { left: "72%", top: "22%", label: "財富" },
    { left: "80%", top: "58%", label: "情感" },
    { left: "24%", top: "68%", label: "城市" }
  ];
  return (
    <motion.div
      id="cruise"
      initial={{ opacity: 0, scale: 0.96 }}
      animate={{ opacity: 1, scale: 1 }}
      transition={{ duration: 0.8, delay: 0.1 }}
      className="relative"
    >
      <div className="absolute inset-0 rounded-[3rem] bg-cyan-300/10 blur-3xl" />
      <div className="relative overflow-hidden rounded-[2.5rem] border border-white/10 bg-white/[0.08] shadow-2xl shadow-cyan-950/40 backdrop-blur-xl">
        <div className="relative h-[500px] overflow-hidden rounded-[2.5rem] bg-[radial-gradient(circle_at_50%_18%,rgba(57,245,255,.13),transparent_24rem),linear-gradient(180deg,#0b1635_0%,#07142c_52%,#062036_100%)] p-8 sm:h-[540px]">
          <div className="star-field absolute inset-0 opacity-30" />
          <div className="absolute inset-x-8 top-8 h-[52%] rounded-[2rem] border border-cyan-200/10 bg-slate-950/12">
            <div className="absolute inset-0 opacity-55 [background-image:linear-gradient(rgba(99,240,212,.08)_1px,transparent_1px),linear-gradient(90deg,rgba(99,240,212,.08)_1px,transparent_1px)] [background-size:42px_42px]" />
            <svg className="absolute inset-0 h-full w-full" viewBox="0 0 420 250" preserveAspectRatio="none" aria-hidden="true">
              <defs>
                <linearGradient id="lifeRoute" x1="0" x2="1" y1="0" y2="1">
                  <stop offset="0%" stopColor="#39f5ff" stopOpacity="0.25" />
                  <stop offset="48%" stopColor="#63f0d4" stopOpacity="0.8" />
                  <stop offset="100%" stopColor="#7257ff" stopOpacity="0.55" />
                </linearGradient>
              </defs>
              <motion.path
                d="M42 172 C105 62 178 78 222 134 C268 193 321 190 374 72"
                fill="none"
                stroke="url(#lifeRoute)"
                strokeWidth="3"
                strokeLinecap="round"
                strokeDasharray="10 12"
                animate={{ strokeDashoffset: [0, -44] }}
                transition={{ duration: 5.5, repeat: Infinity, ease: "linear" }}
              />
            </svg>
            {routeNodes.map((node) => (
              <div key={node.label} className="absolute flex -translate-x-1/2 -translate-y-1/2 flex-col items-center gap-1" style={{ left: node.left, top: node.top }}>
                <span className="h-3 w-3 rounded-full bg-cyan-200 shadow-[0_0_24px_rgba(57,245,255,.8)]" />
                <span className="rounded-full border border-white/10 bg-slate-950/45 px-2.5 py-1 text-xs text-cyan-50/80 backdrop-blur">{node.label}</span>
              </div>
            ))}
            <Compass className="absolute right-5 top-5 h-8 w-8 text-cyan-100/70" />
          </div>

          <motion.div
            animate={{ y: [0, -8, 0], rotate: [-0.6, 0.6, -0.6] }}
            transition={{ duration: 7, repeat: Infinity, ease: "easeInOut" }}
            className="absolute left-1/2 top-[54%] w-[76%] -translate-x-1/2 -translate-y-1/2"
          >
            <div className="mx-auto h-20 w-[64%] rounded-t-[80px] border border-cyan-100/30 bg-white/10 backdrop-blur-md sm:h-24" />
            <div className="relative mx-auto h-36 w-full rounded-b-[90px] rounded-t-[28px] border border-cyan-100/30 bg-gradient-to-b from-white/25 to-white/10 shadow-2xl backdrop-blur-xl sm:h-40">
              <div className="absolute left-1/2 top-7 flex -translate-x-1/2 gap-3">
                {[1, 2, 3, 4, 5].map((i) => (
                  <div key={i} className="h-8 w-8 rounded-full border border-cyan-100/40 bg-cyan-100/20 sm:h-9 sm:w-9" />
                ))}
              </div>
              <div className="absolute bottom-7 left-1/2 h-10 w-32 -translate-x-1/2 rounded-full bg-cyan-200/80 shadow-lg shadow-cyan-300/30" />
            </div>
            <div className="mx-auto h-10 w-[48%] rounded-b-[70px] bg-cyan-200/20" />
          </motion.div>

          <div className="absolute bottom-0 left-0 right-0 h-44">
            <motion.div
              animate={{ x: [0, -80, 0] }}
              transition={{ duration: 8, repeat: Infinity, ease: "easeInOut" }}
              className="absolute bottom-0 h-28 w-[130%] rounded-t-[50%] bg-cyan-400/20"
            />
            <motion.div
              animate={{ x: [-60, 40, -60] }}
              transition={{ duration: 7, repeat: Infinity, ease: "easeInOut" }}
              className="absolute bottom-0 h-20 w-[120%] rounded-t-[50%] bg-blue-300/20"
            />
            <Waves className="absolute bottom-12 left-8 h-10 w-10 text-cyan-100/70" />
          </div>
        </div>
      </div>
    </motion.div>
  );
}

function Hero({ onLogin }) {
  return (
    <section id="top" className="hero-shell relative overflow-hidden">
      <div className="hero-bg-soft pointer-events-none absolute inset-0" />
      <div className="hero-mobile-blend pointer-events-none absolute inset-x-0 bottom-0 h-[56%]" />
      <div className="relative z-10 mx-auto grid max-w-7xl items-center gap-12 px-6 pb-14 pt-8 lg:grid-cols-[1.02fr_0.98fr] lg:px-8 lg:pb-20 lg:pt-16">
      <motion.div initial={{ opacity: 0, y: 24 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.7 }}>
        <div className="mb-6 inline-flex items-center gap-2 rounded-full border border-cyan-300/20 bg-cyan-300/10 px-4 py-2 text-sm text-cyan-100 shadow-lg shadow-cyan-500/10 backdrop-blur">
          <Sparkles className="h-4 w-4" />
          台灣首發 · 遊客也能登船，先免費完成一次人生航線測試
        </div>

        <h1 className="max-w-3xl text-5xl font-semibold leading-tight tracking-normal text-white sm:text-6xl">
          登上「费曼号」
          <span className="mt-1 block bg-gradient-to-r from-cyan-200 via-blue-200 to-violet-200 bg-clip-text text-transparent lg:whitespace-nowrap">
            看見你的下一站人生
          </span>
        </h1>

        <p className="mt-6 max-w-2xl text-lg leading-8 text-slate-300">
          费曼將八字、紫微、風水與現代生活決策結合，先以繁體中文與新台幣計價在台灣驗證商業模型，為你生成城市發展、事業機會、情感關係、財富節奏與遷移方向的個人化測試報告。
        </p>

        <div className="mt-8 flex flex-col gap-3 sm:flex-row">
          <a href="#tests">
            <SoftButton>
              立即開始測試
              <ChevronRight className="h-5 w-5" />
            </SoftButton>
          </a>
          <SoftButton variant="outline" onClick={onLogin}>
            <Anchor className="h-5 w-5" />
            登入费曼号
          </SoftButton>
        </div>

        <div className="mt-10 grid max-w-xl grid-cols-3 gap-4">
          {[
            ["台灣", "首發市場"],
            ["36", "人生主題"],
            ["24h", "AI 解讀"]
          ].map(([num, label]) => (
            <div key={label} className="rounded-2xl border border-white/10 bg-white/[0.06] p-4 backdrop-blur">
              <div className="text-2xl font-semibold text-cyan-100">{num}</div>
              <div className="mt-1 text-sm text-white/50">{label}</div>
            </div>
          ))}
        </div>
      </motion.div>

      <CruiseCard />
      </div>
    </section>
  );
}

function TestsSection({ currentUser, onLogin, onSubscribe, onUserUpdate }) {
  const [activeCategory, setActiveCategory] = useState("全部");
  const [query, setQuery] = useState("");
  const [selectedTest, setSelectedTest] = useState(null);
  const [testsCatalog, setTestsCatalog] = useState(testEntries);

  const applyTestsCatalog = useCallback((serverTests = []) => {
    const nextTests = mergeServerTests(serverTests);
    setTestsCatalog(nextTests);
    setSelectedTest((current) => {
      if (!current) return current;
      return nextTests.find((item) => (item.id || item.title) === (current.id || current.title)) || current;
    });
  }, []);

  const refreshTestsCatalog = useCallback(async () => {
    try {
      const data = await apiRequest(`/api/tests?ts=${Date.now()}`, { cache: "no-store" });
      applyTestsCatalog(data.tests || []);
    } catch {
      const cachedTests = readTestsCatalogSnapshot();
      if (cachedTests?.length) applyTestsCatalog(cachedTests);
      else setTestsCatalog(testEntries);
    }
  }, [applyTestsCatalog]);

  useEffect(() => {
    const cachedTests = readTestsCatalogSnapshot();
    if (cachedTests?.length) applyTestsCatalog(cachedTests);
    refreshTestsCatalog();
    const handleCatalogUpdated = (event) => {
      if (Array.isArray(event.detail?.tests)) applyTestsCatalog(event.detail.tests);
      refreshTestsCatalog();
    };
    const handleStorage = (event) => {
      if (event.key !== TESTS_CATALOG_UPDATED_STORAGE_KEY && event.key !== TESTS_CATALOG_SNAPSHOT_STORAGE_KEY) return;
      const snapshot = readTestsCatalogSnapshot();
      if (snapshot?.length) applyTestsCatalog(snapshot);
      refreshTestsCatalog();
    };
    const handleVisibility = () => {
      if (!document.hidden) refreshTestsCatalog();
    };
    const realtimeTimer = window.setInterval(refreshTestsCatalog, 5000);
    window.addEventListener(TESTS_CATALOG_UPDATED_EVENT, handleCatalogUpdated);
    window.addEventListener("storage", handleStorage);
    window.addEventListener("focus", handleCatalogUpdated);
    window.addEventListener("pageshow", handleCatalogUpdated);
    document.addEventListener("visibilitychange", handleVisibility);
    return () => {
      window.removeEventListener(TESTS_CATALOG_UPDATED_EVENT, handleCatalogUpdated);
      window.removeEventListener("storage", handleStorage);
      window.removeEventListener("focus", handleCatalogUpdated);
      window.removeEventListener("pageshow", handleCatalogUpdated);
      document.removeEventListener("visibilitychange", handleVisibility);
      window.clearInterval(realtimeTimer);
    };
  }, [applyTestsCatalog, refreshTestsCatalog]);

  const visibleCategories = useMemo(() => {
    const dynamicCategories = testsCatalog
      .filter((item) => item.status === undefined || item.status === "published")
      .map((item) => String(item.category || "未分類").trim())
      .filter(Boolean);
    return ["全部", ...Array.from(new Set(dynamicCategories))];
  }, [testsCatalog]);

  useEffect(() => {
    if (activeCategory !== "全部" && !visibleCategories.includes(activeCategory)) {
      setActiveCategory("全部");
    }
  }, [activeCategory, visibleCategories]);

  const filteredTests = useMemo(() => {
    const keyword = query.trim();
    return testsCatalog.filter((item) => {
      const isPublished = item.status === undefined || item.status === "published";
      const itemCategory = String(item.category || "未分類").trim();
      const matchCategory = activeCategory === "全部" || itemCategory === activeCategory;
      const matchQuery = !keyword || item.title.includes(keyword) || item.desc.includes(keyword) || item.tag.includes(keyword);
      return isPublished && matchCategory && matchQuery;
    });
  }, [activeCategory, query, testsCatalog]);

  return (
    <section id="tests" className="mx-auto max-w-7xl px-6 py-12 lg:px-8">
      <div className="mb-8 flex flex-col justify-between gap-5 md:flex-row md:items-end">
        <div>
          <div className="mb-3 inline-flex items-center gap-2 text-sm font-medium text-cyan-200">
            <ShipWheel className="h-4 w-4" />
            命理測驗港口
          </div>
          <h2 className="text-3xl font-semibold tracking-normal text-white md:text-5xl">選擇你的下一段航線</h2>
          <p className="mt-4 max-w-2xl text-slate-400">從一個問題開始，讓 AI 生成你的專屬人生地圖。遊客可免費體驗基礎報告。</p>
        </div>

        <div className="relative w-full md:w-[360px]">
          <Search className="absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-white/40" />
          <input
            value={query}
            onChange={(event) => setQuery(event.target.value)}
            placeholder="搜尋測試：城市、婚姻、事業..."
            className="h-[52px] w-full rounded-full border border-white/10 bg-white/[0.07] pl-12 pr-5 text-sm text-white outline-none placeholder:text-white/40 focus:border-cyan-200/60"
          />
        </div>
      </div>

      <div className="scrollbar-soft mb-8 flex gap-3 overflow-x-auto pb-2">
        {visibleCategories.map((category) => (
          <button
            key={category}
            onClick={() => setActiveCategory(category)}
            className={cx(
              "shrink-0 rounded-full px-5 py-2.5 text-sm transition",
              activeCategory === category
                ? "bg-cyan-300 text-slate-950"
                : "border border-white/10 bg-white/[0.05] text-white/70 hover:bg-white/10 hover:text-white"
            )}
          >
            {category}
          </button>
        ))}
      </div>

      <motion.div layout className="grid gap-x-5 gap-y-7 md:grid-cols-2 lg:grid-cols-4">
        <AnimatePresence mode="popLayout">
          {filteredTests.map((item, index) => {
            const Icon = item.icon;
            return (
              <motion.article
                layout
                key={item.id || item.title}
                initial={{ opacity: 0, y: 18 }}
                animate={{ opacity: 1, y: 0 }}
                exit={{ opacity: 0, scale: 0.96 }}
                transition={{ duration: 0.35, delay: index * 0.025 }}
                className="group h-full overflow-hidden rounded-3xl border border-white/10 bg-white/[0.06] p-6 text-white shadow-xl shadow-slate-950/20 backdrop-blur transition hover:-translate-y-1 hover:bg-white/[0.1]"
              >
                <div className="mb-5 flex items-center justify-between">
                  <div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-cyan-300/15 text-cyan-100 ring-1 ring-cyan-200/20">
                    <Icon className="h-6 w-6" />
                  </div>
                  <span className="rounded-full bg-white/10 px-3 py-1 text-xs text-cyan-100">{item.tag}</span>
                </div>
                <h3 className="text-xl font-semibold leading-snug">{item.title}</h3>
                <p className="mt-3 min-h-[72px] text-sm leading-6 text-slate-400">{item.desc}</p>
                <button
                  onClick={() => setSelectedTest(item)}
                  className="mt-5 flex items-center text-sm font-medium text-cyan-200 group-hover:text-cyan-100"
                >
                  開始測試
                  <ChevronRight className="ml-1 h-4 w-4 transition group-hover:translate-x-1" />
                </button>
              </motion.article>
            );
          })}
        </AnimatePresence>
      </motion.div>

      {filteredTests.length === 0 && (
        <div className="rounded-3xl border border-white/10 bg-white/[0.06] p-10 text-center backdrop-blur">
          <Compass className="mx-auto h-8 w-8 text-cyan-200" />
          <h3 className="mt-4 text-xl font-semibold text-white">這條航線暫時沒有匹配測試</h3>
          <p className="mt-3 text-sm text-slate-400">換一個關鍵詞，或清除篩選後重新探索完整測試港口。</p>
          <button
            onClick={() => {
              setActiveCategory("全部");
              setQuery("");
            }}
            className="mt-5 rounded-full bg-cyan-300 px-5 py-3 text-sm font-semibold text-slate-950"
          >
            清除篩選
          </button>
        </div>
      )}

      <TestPreviewModal
        test={selectedTest}
        recommendations={getRecommendedTests(testsCatalog, selectedTest)}
        currentUser={currentUser}
        onClose={() => setSelectedTest(null)}
        onSelectTest={(test) => setSelectedTest(test)}
        onLogin={onLogin}
        onSubscribe={onSubscribe}
        onUserUpdate={onUserUpdate}
      />
    </section>
  );
}

function getGenericTestQuestions(test) {
  if (Array.isArray(test.questions) && test.questions.length) return test.questions;
  return [
    {
      id: "current_state",
      title: "你現在最接近哪種狀態？",
      options: ["想改變但還沒開始", "已經有機會在眼前", "正在猶豫成本", "想確認風險"]
    },
    {
      id: "priority",
      title: "你最在意這件事帶來的什麼？",
      options: ["長期穩定", "收入或資源", "關係品質", "自由度和成長"]
    },
    {
      id: "risk",
      title: "你最擔心的風險是什麼？",
      options: ["判斷失誤", "時間不對", "投入太大", "別人不支持"]
    },
    {
      id: "pace",
      title: "如果要行動，你希望節奏是？",
      options: ["馬上推進", "一個月內試探", "三個月內觀察", "先收集更多資訊"]
    },
    {
      id: "support",
      title: "你最需要哪類支持？",
      options: ["清晰計畫", "貴人建議", "資金資源", "情緒穩定"]
    },
    {
      id: "outcome",
      title: "你希望報告重點回答什麼？",
      options: [`${test.category}趨勢`, "具體行動", "避坑提醒", "關鍵時間點"]
    }
  ];
}

function TestPreviewModal({ test, recommendations = [], currentUser, onClose, onSelectTest, onLogin, onSubscribe, onUserUpdate }) {
  const [stage, setStage] = useState("intake");
  const [cityForm, setCityForm] = useState(() => getSavedBirthInfo());
  const [birthProfiles, setBirthProfiles] = useState([]);
  const [selectedBirthProfileId, setSelectedBirthProfileId] = useState("");
  const [cityResult, setCityResult] = useState(null);
  const [genericAnswers, setGenericAnswers] = useState({});
  const [genericResult, setGenericResult] = useState(null);
  const [runError, setRunError] = useState("");
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [generationProgress, setGenerationProgress] = useState(null);

  useEffect(() => {
    setStage("intake");
    setCityForm(getSavedBirthInfo());
    setSelectedBirthProfileId("");
    setCityResult(null);
    setGenericAnswers({});
    setGenericResult(null);
    setRunError("");
    setIsSubmitting(false);
    setGenerationProgress(null);
  }, [test?.title]);

  useEffect(() => {
    if (!currentUser) {
      setBirthProfiles([]);
      return;
    }
    apiRequest("/api/member/birth-profiles")
      .then((data) => {
        const profiles = data.profiles || [];
        setBirthProfiles(profiles);
        const primary = profiles.find((profile) => profile.type === "primary") || profiles[0];
        if (primary?.birthInfo) {
          setSelectedBirthProfileId(primary.id);
          setCityForm((current) => ({ ...current, ...primary.birthInfo, birthProfileId: primary.id, saveBirthInfo: true }));
        }
      })
      .catch(() => setBirthProfiles([]));
  }, [currentUser?.id]);

  if (!test) return null;

  const Icon = test.icon;
  const isCityTest = test.title === "你適合在哪個城市發展？";
  const testId = test.id || test.title.replace(/[^\w\u4e00-\u9fa5]+/g, "-").slice(0, 40);
  const savedBirthInfoAvailable = Boolean(cityForm?.savedAt);
  const genericQuestions = getGenericTestQuestions(test);
  const selectedBirthProfile = birthProfiles.find((profile) => profile.id === selectedBirthProfileId);
  const selectedBirthProfileLocked = Boolean(selectedBirthProfile?.lockedAt);

  useEffect(() => {
    if (!isSubmitting || !generationProgress) return;
    const startedAt = generationProgress.startedAt || Date.now();
    const timer = window.setInterval(() => {
      setGenerationProgress((current) => {
        if (!current || current.progress >= 86) return current;
        const elapsedSeconds = Math.max(0, (Date.now() - startedAt) / 1000);
        const waitingProgress = Math.min(86, 48 + Math.floor(elapsedSeconds * 3.2));
        if (waitingProgress <= current.progress) return current;
        return {
          ...current,
          progress: waitingProgress,
          stage: current.stage || "正在等待 FateMind 生成報告"
        };
      });
    }, 900);
    return () => window.clearInterval(timer);
  }, [isSubmitting, generationProgress]);

  function startGenerationProgress(stage = "正在準備測算資料") {
    setGenerationProgress({
      progress: 12,
      stage,
      detail: "已開始整理命盤檔案與測驗資料。",
      startedAt: Date.now()
    });
  }

  function updateGenerationProgress(patch) {
    setGenerationProgress((current) => ({
      ...(current || { startedAt: Date.now() }),
      ...patch,
      progress: Math.max(current?.progress || 0, Number(patch.progress || 0))
    }));
  }

  async function runGenericTest({ useSavedBirthInfo = false, birthInfoOverride = null } = {}) {
    setRunError("");
    setIsSubmitting(true);
    startGenerationProgress("正在準備測算資料");
    try {
      let nextBirthInfo = birthInfoOverride || (useSavedBirthInfo ? cityForm : null);
      if (nextBirthInfo) {
        updateGenerationProgress({ progress: 24, stage: "正在整理出生資料", detail: "已讀取你選擇的命盤檔案與出生地資料。" });
        nextBirthInfo = nextBirthInfo.saveBirthInfo ? { ...nextBirthInfo, savedAt: nextBirthInfo.savedAt || new Date().toISOString() } : nextBirthInfo;
        saveBirthInfo(nextBirthInfo);
        setCityForm(nextBirthInfo);
      }
      updateGenerationProgress({ progress: 42, stage: "已提交後端測算", detail: "後端正在確認每日測驗規則、排盤基準與報告提示詞。" });
      const data = await apiRequest(`/api/tests/${encodeURIComponent(testId)}/run`, {
        method: "POST",
        body: {
          title: test.title,
          category: test.category,
          answers: useSavedBirthInfo ? { saved_birth_info: "已使用保存的出生資訊直接測算" } : genericAnswers,
          birthInfo: nextBirthInfo,
          birthProfileId: selectedBirthProfileId || nextBirthInfo?.birthProfileId || null,
          birthProfileLabel: birthProfiles.find((profile) => profile.id === (selectedBirthProfileId || nextBirthInfo?.birthProfileId))?.label || null,
          useSavedBirthInfo: Boolean(useSavedBirthInfo || nextBirthInfo?.savedAt),
          guestId: getGuestId()
        }
      });
      updateGenerationProgress({ progress: 92, stage: data.reused ? "已找到本日報告" : "報告已生成", detail: data.reused ? "今天已做過此項測驗，正在載入本日報告。" : "後端已返回完整報告，正在整理展示。" });
      setGenericResult(data.message ? { ...data.result, notice: data.message } : data.result);
      if (data.user) onUserUpdate(data.user);
      if (data.birthProfile) {
        setSelectedBirthProfileId(data.birthProfile.id);
        setBirthProfiles((profiles) => [data.birthProfile, ...profiles.filter((profile) => profile.id !== data.birthProfile.id)]);
      }
      updateGenerationProgress({ progress: 100, stage: "完成", detail: "報告已準備好。" });
      setStage("generic-result");
    } catch (error) {
      if (error.status === 402) onSubscribe(error.data?.message || "免費體驗已使用。開通會員後可繼續測試。");
      else setRunError(error.message || "生成失敗，請稍後再試");
    } finally {
      setIsSubmitting(false);
    }
  }

  function applyBirthProfile(profile) {
    setSelectedBirthProfileId(profile.id);
    if (profile.birthInfo) setCityForm({ ...cityForm, ...profile.birthInfo, birthProfileId: profile.id, saveBirthInfo: true });
  }

  async function createFriendBirthProfile() {
    try {
      setRunError("");
      const data = await apiRequest("/api/member/birth-profiles", {
        method: "POST",
        body: {
          type: "friend",
          label: `親友命盤 ${birthProfiles.filter((profile) => profile.type === "friend").length + 1}`,
          birthInfo: cityForm
        }
      });
      setBirthProfiles(data.profiles || []);
      setSelectedBirthProfileId(data.profile.id);
      setCityForm({ ...cityForm, birthProfileId: data.profile.id, saveBirthInfo: true });
    } catch (error) {
      if (error.status === 402) onSubscribe(error.data?.message || "開通會員後可建立親友命盤。");
      else setRunError(error.message || "親友命盤建立失敗");
    }
  }

  return (
    <AnimatePresence>
      <motion.div
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        className="fixed inset-0 z-50 flex items-end justify-center bg-slate-950/75 px-4 pb-4 backdrop-blur-xl sm:items-center sm:p-6"
        onMouseDown={onClose}
      >
        <motion.div
          initial={{ opacity: 0, y: 32, scale: 0.98 }}
          animate={{ opacity: 1, y: 0, scale: 1 }}
          exit={{ opacity: 0, y: 20, scale: 0.98 }}
          onMouseDown={(event) => event.stopPropagation()}
          className="max-h-[92vh] w-full max-w-2xl overflow-y-auto rounded-[2rem] border border-white/10 bg-[#101a2c]/95 text-white shadow-2xl shadow-cyan-950/30 backdrop-blur-xl scrollbar-soft"
        >
          <div className="relative p-6 sm:p-8">
            <div className="absolute right-0 top-0 h-48 w-48 rounded-full bg-cyan-300/10 blur-3xl" />
            <div className="relative flex items-start justify-between gap-4">
              <div className="flex items-center gap-4">
                <div className="flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-cyan-300/15 text-cyan-100 ring-1 ring-cyan-200/20">
                  <Icon className="h-7 w-7" />
                </div>
                <div>
                  <div className="mb-2 inline-flex rounded-full bg-white/10 px-3 py-1 text-xs text-cyan-100">{test.tag}</div>
                  <h3 className="text-2xl font-semibold leading-snug">{test.title}</h3>
                </div>
              </div>
              <button onClick={onClose} className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-white/10 text-white/70 transition hover:bg-white/15 hover:text-white">
                <X className="h-5 w-5" />
              </button>
            </div>

            {stage === "intake" ? (
              <CityBaziIntake
                form={cityForm}
                currentUser={currentUser}
                birthProfiles={birthProfiles}
                selectedBirthProfileId={selectedBirthProfileId}
                selectedBirthProfileLocked={selectedBirthProfileLocked}
                onSelectBirthProfile={applyBirthProfile}
                onCreateFriendBirthProfile={createFriendBirthProfile}
                onLockedBirthEdit={() => {
                  setRunError(
                    selectedBirthProfile?.type === "primary"
                      ? "本人命盤已鎖定。你可以直接繼續測算；若要替親友測算，請先新增親友命盤檔案。"
                      : "這個親友命盤已鎖定。你可以直接繼續測算；若資料填錯，請新增另一個親友檔案。"
                  );
                }}
                isSubmitting={isSubmitting}
                generationProgress={generationProgress}
                error={runError}
                submitLabel={isCityTest ? "生成城市發展報告" : "生成測試結果"}
                onChange={setCityForm}
                onBack={onClose}
                onSubmit={async () => {
                  if (!isCityTest) {
                    await runGenericTest({ useSavedBirthInfo: true, birthInfoOverride: cityForm });
                    return;
                  }
                  setRunError("");
                  setIsSubmitting(true);
                  startGenerationProgress("正在準備城市發展測算");
                  try {
                    updateGenerationProgress({ progress: 22, stage: "正在整理出生與地點資料", detail: "已讀取出生時間、出生地、目前身份與命盤檔案。" });
                    saveBirthInfo(cityForm);
                    updateGenerationProgress({ progress: 42, stage: "已提交後端排盤", detail: "後端正在確認命盤鎖定狀態與本日測驗記錄。" });
	                    const data = await apiRequest("/api/tests/city-development/run", {
	                      method: "POST",
	                      body: {
                          input: cityForm,
                          birthProfileId: selectedBirthProfileId || cityForm.birthProfileId || null,
                          birthProfileLabel: birthProfiles.find((profile) => profile.id === (selectedBirthProfileId || cityForm.birthProfileId))?.label || null,
                          useSavedBirthInfo: Boolean(selectedBirthProfileLocked || cityForm.savedAt),
                          guestId: getGuestId()
                        }
	                    });
	                    updateGenerationProgress({ progress: 92, stage: data.reused ? "已找到本日城市報告" : "城市報告已生成", detail: data.reused ? "今天已做過此項測驗，正在載入本日報告。" : "城市適配結果已返回，正在整理展示。" });
	                    setCityResult(data.message ? { ...data.result, notice: data.message } : data.result);
	                    if (data.user) onUserUpdate(data.user);
                      if (data.birthProfile) {
                        setSelectedBirthProfileId(data.birthProfile.id);
                        setBirthProfiles((profiles) => [data.birthProfile, ...profiles.filter((profile) => profile.id !== data.birthProfile.id)]);
                      }
                    updateGenerationProgress({ progress: 100, stage: "完成", detail: "報告已準備好。" });
                    setStage("result");
                  } catch (error) {
                    if (error.status === 402) {
                      onSubscribe(error.data?.message || "免費體驗已使用。開通會員後可繼續測試。");
                    } else {
                      setRunError(error.message || "生成失敗，請稍後再試");
                    }
                  } finally {
                    setIsSubmitting(false);
                  }
                }}
              />
            ) : stage === "questionnaire" && !isCityTest ? (
              <GenericTestQuestionnaire
                test={test}
                questions={genericQuestions}
                answers={genericAnswers}
                isSubmitting={isSubmitting}
                generationProgress={generationProgress}
                error={runError}
                onAnswer={(questionId, value) => setGenericAnswers({ ...genericAnswers, [questionId]: value })}
                onBack={onClose}
                onSubmit={() => runGenericTest({ useSavedBirthInfo: false })}
              />
            ) : stage === "result" && isCityTest && cityResult ? (
              <CityDevelopmentResult
                form={cityForm}
                result={cityResult}
                onRestart={() => setStage("intake")}
                onLogin={onLogin}
                onSubscribe={onSubscribe}
                recommendations={recommendations}
                onSelectTest={onSelectTest}
                onClose={onClose}
              />
            ) : stage === "generic-result" && genericResult ? (
              <GenericTestResult result={genericResult} recommendations={recommendations} onSelectTest={onSelectTest} onRestart={() => (savedBirthInfoAvailable ? runGenericTest({ useSavedBirthInfo: true }) : setStage("intake"))} onClose={onClose} onSubscribe={onSubscribe} />
            ) : null}
          </div>
        </motion.div>
      </motion.div>
    </AnimatePresence>
  );
}

function FieldLabel({ children }) {
  return <label className="mb-2 block text-xs font-medium text-white/55">{children}</label>;
}

function GenerationProgressCard({ progress }) {
  if (!progress) return null;
  const percent = Math.max(0, Math.min(100, Math.round(progress.progress || 0)));
  return (
    <div className="mt-4 rounded-3xl border border-cyan-200/20 bg-cyan-300/10 p-4 shadow-[0_0_28px_rgba(103,232,249,0.08)]">
      <div className="mb-3 flex items-start justify-between gap-4">
        <div>
          <div className="text-sm font-semibold text-cyan-50">{progress.stage || "正在生成報告"}</div>
          <div className="mt-1 text-xs leading-5 text-cyan-50/55">{progress.detail || "系統正在處理已完成的真實步驟。"}</div>
        </div>
        <div className="shrink-0 rounded-full bg-slate-950/35 px-3 py-1 text-xs font-semibold text-cyan-100">{percent}%</div>
      </div>
      <div className="h-2.5 overflow-hidden rounded-full bg-slate-950/45 ring-1 ring-white/10">
        <motion.div
          className="h-full rounded-full bg-gradient-to-r from-cyan-300 via-sky-300 to-violet-300"
          initial={false}
          animate={{ width: `${percent}%` }}
          transition={{ duration: 0.45, ease: "easeOut" }}
        />
      </div>
      <div className="mt-3 grid grid-cols-4 gap-2 text-[11px] text-white/35">
        {["提交", "排盤", "生成", "展示"].map((label, index) => {
          const done = percent >= [18, 42, 88, 100][index];
          return <span key={label} className={done ? "text-cyan-100/80" : ""}>{label}</span>;
        })}
      </div>
    </div>
  );
}

function GenericTestQuestionnaire({ test, questions, answers, isSubmitting, generationProgress, error, onAnswer, onBack, onSubmit }) {
  const completed = questions.filter((question) => answers[question.id]).length;
  const ready = completed === questions.length;

  return (
    <div className="relative mt-6">
      <div className="mb-5 rounded-3xl border border-cyan-200/15 bg-cyan-300/10 p-5">
        <div className="text-sm font-semibold text-cyan-100">{test.title}</div>
        <div className="mt-2 text-sm leading-6 text-slate-300">完成 {questions.length} 道輕問答後，系統會生成一份基礎趨勢報告。目前進度：{completed}/{questions.length}</div>
      </div>

      <div className="space-y-5">
        {questions.map((question, index) => (
          <div key={question.id} className="rounded-3xl border border-white/10 bg-white/[0.05] p-5">
            <div className="mb-4 text-sm font-semibold text-white">{index + 1}. {question.title}</div>
            <div className="grid gap-2 sm:grid-cols-2">
              {question.options.map((option) => (
                <button
                  key={option}
                  onClick={() => onAnswer(question.id, option)}
                  className={cx(
                    "min-h-11 rounded-2xl border px-4 py-3 text-left text-sm transition",
                    answers[question.id] === option
                      ? "border-cyan-200/50 bg-cyan-300/20 text-cyan-50 shadow-[0_0_24px_rgba(103,232,249,0.12)]"
                      : "border-white/10 bg-slate-950/20 text-slate-300 hover:bg-white/[0.07]"
                  )}
                >
                  {option}
                </button>
              ))}
            </div>
          </div>
        ))}
      </div>

      {error && <div className="mt-4 rounded-2xl border border-rose-300/20 bg-rose-400/10 p-4 text-sm text-rose-100">{error}</div>}
      <GenerationProgressCard progress={isSubmitting ? generationProgress : null} />

      <div className="mt-6 grid gap-3 sm:grid-cols-2">
        <button onClick={onBack} className="inline-flex h-[52px] items-center justify-center rounded-full border border-white/15 bg-white/5 px-6 text-sm font-semibold text-white transition hover:bg-white/10">
          取消
        </button>
        <button disabled={!ready || isSubmitting} onClick={onSubmit} className="inline-flex h-[52px] items-center justify-center gap-2 rounded-full bg-white px-6 text-sm font-semibold text-slate-950 transition hover:bg-cyan-100 disabled:cursor-not-allowed disabled:opacity-60">
          {isSubmitting ? "正在生成..." : "生成基礎報告"}
          <ChevronRight className="h-4 w-4" />
        </button>
      </div>
    </div>
  );
}

function RecommendedTests({ tests = [], onSelectTest }) {
  if (!tests.length) return null;
  return (
    <div className="mt-5 rounded-3xl border border-white/10 bg-white/[0.045] p-5">
      <div className="mb-4 flex items-center gap-2 text-sm font-semibold text-cyan-100">
        <Sparkles className="h-4 w-4" />
        您可能感興趣的其他測試
      </div>
      <div className="grid gap-3 md:grid-cols-3">
        {tests.map((item) => {
          const Icon = item.icon || Sparkles;
          return (
            <button
              key={item.id || item.title}
              onClick={() => onSelectTest?.(item)}
              className="rounded-2xl border border-white/10 bg-slate-950/25 p-4 text-left transition hover:border-cyan-200/30 hover:bg-white/[0.07]"
            >
              <div className="mb-3 flex items-center justify-between gap-3">
                <div className="flex h-10 w-10 items-center justify-center rounded-xl bg-cyan-300/15 text-cyan-100">
                  <Icon className="h-5 w-5" />
                </div>
                <span className="rounded-full bg-white/10 px-2.5 py-1 text-xs text-cyan-100">{item.tag || item.category}</span>
              </div>
              <div className="text-sm font-semibold leading-5 text-white">{item.title}</div>
              <div className="mt-2 line-clamp-2 text-xs leading-5 text-slate-400">{item.desc}</div>
            </button>
          );
        })}
      </div>
    </div>
  );
}

function GenericTestResult({ result, recommendations = [], onSelectTest, onRestart, onClose, onSubscribe }) {
  return (
    <div className="relative mt-6">
      {result.notice && (
        <div className="mb-4 rounded-3xl border border-cyan-200/25 bg-cyan-300/10 p-4 text-sm leading-6 text-cyan-50">
          {result.notice}
        </div>
      )}
      {result.basisInfo && !result.hideTechnicalBasis && (
        <div className="mt-4 rounded-3xl border border-cyan-200/15 bg-slate-950/25 p-5">
          <div className="mb-4 text-sm font-semibold text-cyan-100">測算基準</div>
          <div className="grid gap-3 text-sm text-slate-300 sm:grid-cols-2">
            <div>出生日期：{result.basisInfo.date}</div>
            <div>出生時間：{result.basisInfo.time}</div>
            <div>出生地：{result.basisInfo.place}</div>
            <div>目前身份：{result.basisInfo.nationality}</div>
          </div>
	          {result.basisInfo.pillars?.fullPillars && (
	            <div className="mt-4 rounded-2xl border border-white/10 bg-white/[0.05] p-4 text-sm text-slate-300">
	              八字：<span className="font-semibold text-white">{result.basisInfo.pillars.fullPillars}</span>
	            </div>
	          )}
	          {result.basisInfo.luckCycles?.currentLuck && (
	            <div className="mt-4 grid gap-3 text-sm text-slate-300 sm:grid-cols-3">
	              <div className="rounded-2xl border border-white/10 bg-white/[0.05] p-4">
	                <div className="text-xs text-white/45">目前大運</div>
	                <div className="mt-1 font-semibold text-white">{result.basisInfo.luckCycles.currentLuck.pillar}</div>
	                <div className="mt-1 text-xs text-white/45">
	                  {result.basisInfo.luckCycles.currentLuck.startAgeText || `${result.basisInfo.luckCycles.currentLuck.startAge}歲`}-
	                  {result.basisInfo.luckCycles.currentLuck.endAgeText || `${result.basisInfo.luckCycles.currentLuck.endAge}歲`}
	                </div>
	              </div>
	              <div className="rounded-2xl border border-white/10 bg-white/[0.05] p-4">
	                <div className="text-xs text-white/45">今年流年</div>
	                <div className="mt-1 font-semibold text-white">{result.basisInfo.luckCycles.annualLuck?.[0]?.year} · {result.basisInfo.luckCycles.annualLuck?.[0]?.pillar}</div>
	              </div>
	              <div className="rounded-2xl border border-white/10 bg-white/[0.05] p-4">
	                <div className="text-xs text-white/45">近期流月</div>
	                <div className="mt-1 font-semibold text-white">{result.basisInfo.luckCycles.monthlyLuck?.[0]?.label} · {result.basisInfo.luckCycles.monthlyLuck?.[0]?.pillar}</div>
	              </div>
	            </div>
	          )}
	        </div>
	      )}

      <div className="mt-4 rounded-3xl border border-white/10 bg-white/[0.045] p-5">
        <div className="mb-4 text-sm font-semibold text-cyan-100">報告解釋</div>
        <div className="space-y-3">
          {(result.interpretation || []).map((item) => (
            <div key={item} className="flex gap-3 text-sm leading-6 text-slate-300">
              <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-cyan-200" />
              {item}
            </div>
          ))}
        </div>
      </div>

      {Array.isArray(result.sections) && result.sections.length > 0 && (
        <ReportReader title="完整報告內容" sections={result.sections.map((section) => ({ ...section, content: reportSectionContent(section) }))} />
      )}

      <div className="mt-4 rounded-3xl border border-white/10 bg-slate-950/25 p-5">
        <div className="mb-4 text-sm font-semibold text-white">下一步行動</div>
        <div className="space-y-3">
          {(result.suggestions || []).map((item, index) => (
            <div key={item} className="flex gap-3 text-sm leading-6 text-slate-300">
              <span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-cyan-300/15 text-xs font-semibold text-cyan-100">{index + 1}</span>
              {item}
            </div>
          ))}
        </div>
        <p className="mt-4 text-xs leading-6 text-white/40">{result.disclaimer}</p>
      </div>

      <RecommendedTests tests={recommendations} onSelectTest={onSelectTest} />

      <div className="mt-6 grid gap-3 sm:grid-cols-3">
        <button onClick={onRestart} className="inline-flex h-[52px] items-center justify-center rounded-full border border-white/15 bg-white/5 px-5 text-sm font-semibold text-white transition hover:bg-white/10">重新測試</button>
        <button onClick={() => onSubscribe("開通會員後，可把這份報告納入長期人生報告，並獲得 AI 持續追問。")} className="inline-flex h-[52px] items-center justify-center rounded-full border border-white/15 bg-white/5 px-5 text-sm font-semibold text-white transition hover:bg-white/10">保存到長期報告</button>
        <button onClick={onClose} className="inline-flex h-[52px] items-center justify-center rounded-full bg-white px-5 text-sm font-semibold text-slate-950 transition hover:bg-cyan-100">完成</button>
      </div>
    </div>
  );
}

function SegmentedControl({ value, options, onChange }) {
  return (
    <div className="grid grid-cols-2 gap-2 rounded-2xl border border-white/10 bg-white/[0.045] p-1">
      {options.map((option) => (
        <button
          key={option.value}
          onClick={() => onChange(option.value)}
          className={cx(
            "rounded-xl px-3 py-2.5 text-sm transition",
            value === option.value ? "bg-cyan-300 text-slate-950" : "text-white/65 hover:bg-white/8 hover:text-white"
          )}
        >
          {option.label}
        </button>
      ))}
    </div>
  );
}

function formatBirthTimeSummary(form) {
  const calendar = form.calendarType === "lunar" ? "農曆" : "國曆";
  const month = form.calendarType === "lunar" ? formatLunarMonth(form.month) : `${String(form.month || "--").padStart(2, "0")}月`;
  const day = form.calendarType === "lunar" ? formatLunarDay(form.day) : `${String(form.day || "--").padStart(2, "0")}日`;
  const date = `${form.year || "--"}年${month}${day}`;
  const time = form.birthTimeMode === "unknown" ? "時間未知" : `${String(form.hour || "00").padStart(2, "0")}:${String(form.minute || "00").padStart(2, "0")}`;
  return `${calendar} ${date} ${time}`;
}

function formatLunarMonth(value) {
  const names = ["正月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "冬月", "臘月"];
  const index = Number(value) - 1;
  return names[index] || "--月";
}

function formatLunarDay(value) {
  const day = Number(value);
  const names = [
    "初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十",
    "十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九",
    "二十", "廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十"
  ];
  return names[day - 1] || "--日";
}

function birthTimeParts(form) {
  return {
    calendar: form.calendarType === "lunar" ? "農曆" : "國曆",
    year: String(form.year || "----"),
    month: form.calendarType === "lunar" ? formatLunarMonth(form.month) : String(form.month || "--").padStart(2, "0"),
    day: form.calendarType === "lunar" ? formatLunarDay(form.day) : String(form.day || "--").padStart(2, "0"),
    hour: form.birthTimeMode === "unknown" ? "未知" : String(form.hour || "00").padStart(2, "0"),
    minute: form.birthTimeMode === "unknown" ? "未知" : String(form.minute || "00").padStart(2, "0")
  };
}

function BirthTimeFixedRow({ form, compact = false }) {
  const parts = birthTimeParts(form);
  const cells = [
    ["年", parts.year],
    ["月", parts.month],
    ["日", parts.day],
    ["時", parts.hour],
    ["分", parts.minute]
  ];

  return (
    <div className={cx("w-full", compact ? "" : "space-y-2")}>
      <div className={cx("inline-flex rounded-full border border-cyan-200/15 bg-cyan-300/10 px-3 py-1 text-xs font-semibold text-cyan-100", compact && "mb-2")}>
        {parts.calendar}
      </div>
      <div className="grid grid-cols-5 gap-2">
        {cells.map(([label, value]) => (
          <div key={label} className="min-w-0 rounded-2xl border border-white/10 bg-slate-950/25 px-2 py-2 text-center">
            <div className={cx("text-base font-semibold text-white sm:text-lg", form.calendarType === "lunar" && (label === "月" || label === "日") ? "" : "font-mono tabular-nums")}>{value}</div>
            <div className="mt-0.5 text-[11px] text-white/40">{label}</div>
          </div>
        ))}
      </div>
    </div>
  );
}

function BirthTimePickerModal({ form, open, onClose, onConfirm }) {
  const currentYear = new Date().getFullYear();
  const [draft, setDraft] = useState(form);
  const [rawInput, setRawInput] = useState("");
  const [pickerPulse, setPickerPulse] = useState("");
  const pickerColumnRefs = useRef({});
  const pickerScrollTimers = useRef({});
  const years = useMemo(() => Array.from({ length: 121 }, (_, index) => String(currentYear - 100 + index)), [currentYear]);
  const months = useMemo(() => Array.from({ length: 12 }, (_, index) => String(index + 1).padStart(2, "0")), []);
  const days = useMemo(() => Array.from({ length: 31 }, (_, index) => String(index + 1).padStart(2, "0")), []);
  const hours = useMemo(() => ["未知", ...Array.from({ length: 24 }, (_, index) => String(index).padStart(2, "0"))], []);
  const minutes = useMemo(() => ["未知", ...Array.from({ length: 60 }, (_, index) => String(index).padStart(2, "0"))], []);

  useEffect(() => {
    if (open) setDraft(form);
  }, [open, form]);

  const pickerColumns = [
    ["年", years, String(draft.year || currentYear)],
    ["月", months, String(draft.month || "1").padStart(2, "0")],
    ["日", days, String(draft.day || "1").padStart(2, "0")],
    ["時", hours, draft.birthTimeMode === "unknown" ? "未知" : String(draft.hour || "0").padStart(2, "0")],
    ["分", minutes, draft.birthTimeMode === "unknown" ? "未知" : String(draft.minute || "0").padStart(2, "0")]
  ];

  const pickerSelectedKey = pickerColumns.map(([, , selected]) => selected).join("|");

  function pickerDisplayValue(label, value) {
    if (draft.calendarType !== "lunar") return value;
    if (label === "月") return formatLunarMonth(value);
    if (label === "日") return formatLunarDay(value);
    return value;
  }

  useEffect(() => {
    if (!open) return;
    const frame = window.requestAnimationFrame(() => {
      pickerColumns.forEach(([label, , selected]) => {
        const column = pickerColumnRefs.current[label];
        const selectedOption = Array.from(column?.querySelectorAll("[data-picker-value]") || []).find((node) => node.dataset.pickerValue === selected);
        selectedOption?.scrollIntoView({ block: "center", behavior: "auto" });
      });
    });
    return () => window.cancelAnimationFrame(frame);
  }, [open, pickerSelectedKey]);

  useEffect(() => {
    return () => Object.values(pickerScrollTimers.current).forEach((timer) => window.clearTimeout(timer));
  }, []);

  function updateDraft(patch) {
    setDraft((current) => ({ ...current, ...patch }));
  }

  function applyRawInput() {
    const digits = rawInput.replace(/\D/g, "");
    if (digits.length < 8) return;
    const next = {
      ...draft,
      year: digits.slice(0, 4),
      month: String(Number(digits.slice(4, 6))),
      day: String(Number(digits.slice(6, 8)))
    };
    if (digits.length >= 12) {
      next.birthTimeMode = "known";
      next.hour = String(Number(digits.slice(8, 10)));
      next.minute = String(Number(digits.slice(10, 12)));
    }
    setDraft(next);
  }

  function chooseTimeValue(type, value) {
    if (value === "未知") {
      updateDraft({ birthTimeMode: "unknown" });
      setPickerPulse(type);
      return;
    }
    updateDraft({ birthTimeMode: "known", [type]: String(Number(value)) });
    setPickerPulse(type);
  }

  function choosePickerValue(label, value) {
    if (label === "年") updateDraft({ year: value });
    if (label === "月") updateDraft({ month: String(Number(value)) });
    if (label === "日") updateDraft({ day: String(Number(value)) });
    if (label === "時") chooseTimeValue("hour", value);
    if (label === "分") chooseTimeValue("minute", value);
    setPickerPulse(label);
  }

  function schedulePickerScroll(label, column) {
    window.clearTimeout(pickerScrollTimers.current[label]);
    pickerScrollTimers.current[label] = window.setTimeout(() => {
      const options = Array.from(column.querySelectorAll("[data-picker-value]"));
      const center = column.getBoundingClientRect().top + column.clientHeight / 2;
      const closest = options
        .map((node) => ({
          node,
          distance: Math.abs(node.getBoundingClientRect().top + node.offsetHeight / 2 - center)
        }))
        .sort((a, b) => a.distance - b.distance)[0]?.node;
      const value = closest?.dataset.pickerValue;
      const selected = pickerColumns.find(([itemLabel]) => itemLabel === label)?.[2];
      if (value && value !== selected) choosePickerValue(label, value);
    }, 120);
  }

  if (!open) return null;

  return (
    <AnimatePresence>
      <motion.div
        className="fixed inset-0 z-[70] flex items-end justify-center bg-slate-950/70 px-4 pb-4 backdrop-blur-sm sm:items-center sm:p-6"
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        onMouseDown={onClose}
      >
        <motion.div
          initial={{ opacity: 0, y: 28, scale: 0.98 }}
          animate={{ opacity: 1, y: 0, scale: 1 }}
          exit={{ opacity: 0, y: 20, scale: 0.98 }}
          onMouseDown={(event) => event.stopPropagation()}
          className="w-full max-w-2xl rounded-[2rem] border border-cyan-200/15 bg-[#101a2c]/95 p-5 text-white shadow-2xl shadow-cyan-950/40 backdrop-blur-xl"
        >
          <div className="flex items-center gap-3">
            <button
              onClick={() => {
                const now = new Date();
                setDraft({
                  ...draft,
                  calendarType: "solar",
                  year: String(now.getFullYear()),
                  month: String(now.getMonth() + 1),
                  day: String(now.getDate()),
                  birthTimeMode: "known",
                  hour: String(now.getHours()),
                  minute: String(now.getMinutes())
                });
              }}
              className="flex h-12 w-12 shrink-0 items-center justify-center rounded-full border border-white/10 bg-white/[0.08] text-lg font-semibold text-cyan-100 transition hover:bg-cyan-300/15"
            >
              今
            </button>
            <div className="grid flex-1 grid-cols-2 overflow-hidden rounded-full border border-white/10 bg-white/[0.06] p-1 text-center text-sm font-semibold">
              {[
                ["solar", "國曆"],
                ["lunar", "農曆"]
              ].map(([value, label], index) => (
                <button
                  key={value}
                  onClick={() => updateDraft({ calendarType: value })}
                  className={cx(
                    "h-11 rounded-full transition",
                    draft.calendarType === value ? "bg-cyan-300 text-slate-950" : "text-white/70 hover:bg-white/10 hover:text-white"
                  )}
                >
                  {label}
                </button>
              ))}
            </div>
            <button onClick={onClose} className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full text-white/55 transition hover:bg-white/10 hover:text-white">
              <X className="h-7 w-7" />
            </button>
          </div>

          <div className="mt-7 flex gap-3 rounded-full border border-white/10 bg-white/[0.05] p-1">
            <input
              value={rawInput}
              onChange={(event) => setRawInput(event.target.value)}
              placeholder="輸入出生年月日時分(格式199303270255)"
              inputMode="numeric"
              className="h-12 min-w-0 flex-1 rounded-full bg-transparent px-5 text-sm text-white outline-none placeholder:text-white/35"
            />
            <button onClick={applyRawInput} className="h-12 rounded-full bg-cyan-300 px-7 text-sm font-semibold text-slate-950 transition hover:bg-cyan-200">
              確定
            </button>
          </div>

          <div className="mt-5 rounded-3xl border border-white/10 bg-white/[0.045] p-4">
            <div className="mb-3 text-xs font-medium text-cyan-100/70">目前已選</div>
            <BirthTimeFixedRow form={draft} compact />
          </div>

          <div className="mt-7 border-t border-white/10 pt-5">
            <div className="grid grid-cols-5 text-center text-lg font-semibold text-cyan-50">
              {pickerColumns.map(([label]) => <div key={label}>{label}</div>)}
            </div>
            <div className="relative mt-4 grid grid-cols-5 gap-2">
              <div className="pointer-events-none absolute left-0 right-0 top-1/2 z-0 h-16 -translate-y-1/2 rounded-xl border border-cyan-200/15 bg-cyan-300/10 shadow-lg shadow-cyan-950/20" />
              {pickerColumns.map(([label, values, selected]) => (
                <div
                  key={label}
                  ref={(node) => {
                    pickerColumnRefs.current[label] = node;
                  }}
                  className="xuanji-picker-column scrollbar-soft relative z-10 h-44 overflow-y-auto rounded-2xl py-14"
                  onScroll={(event) => schedulePickerScroll(label, event.currentTarget)}
                >
                  {values.map((value) => {
                    const active = value === selected;
                    return (
                      <button
                        key={value}
                        type="button"
                        data-picker-value={value}
                        onClick={(event) => {
                          choosePickerValue(label, value);
                          event.currentTarget.scrollIntoView({ block: "center", behavior: "smooth" });
                        }}
                        className={cx(
                          "xuanji-picker-option flex h-10 w-full items-center justify-center rounded-xl text-center text-base transition",
                          draft.calendarType !== "lunar" && "font-mono tabular-nums",
                          active
                            ? "bg-cyan-300/20 text-xl font-semibold text-cyan-50 shadow-[0_0_22px_rgba(103,232,249,0.18)]"
                            : "text-white/28 hover:bg-white/[0.04] hover:text-white/70",
                          pickerPulse === label && active && "animate-picker-confirm"
                        )}
                      >
                        {pickerDisplayValue(label, value)}
                      </button>
                    );
                  })}
                </div>
              ))}
            </div>
            <p className="mt-3 text-center text-xs text-cyan-100/55">點擊數字即可選擇，列表滚動時會自動吸附到刻度感。</p>
          </div>

          <button
            onClick={() => onConfirm(draft)}
            className="mt-7 h-16 w-full rounded-full bg-cyan-300 text-xl font-semibold text-slate-950 transition hover:bg-cyan-200"
          >
            確定
          </button>
        </motion.div>
      </motion.div>
    </AnimatePresence>
  );
}

const popularOverseasCountryCodes = ["CN", "HK", "MO", "US", "SG", "CA", "GB", "AU", "JP", "KR", "TH", "MY", "PH", "AE", "FR", "DE"];
const taiwanDomesticRegionCodes = new Set(["TW"]);
const taiwanPriorityLocations = [
  ["tw-taipei", "台北", "台北", 121.56, 25.04],
  ["tw-new-taipei", "新北", "新北", 121.46, 25.01],
  ["tw-taoyuan", "桃園", "桃園", 121.3, 24.99],
  ["tw-taichung", "台中", "台中", 120.67, 24.15],
  ["tw-tainan", "台南", "台南", 120.21, 22.99],
  ["tw-kaohsiung", "高雄", "高雄", 120.31, 22.62],
  ["tw-hsinchu", "新竹", "新竹", 120.96, 24.8],
  ["tw-keelung", "基隆", "基隆", 121.74, 25.13],
  ["tw-yilan", "宜蘭", "宜蘭", 121.75, 24.75],
  ["tw-hualien", "花蓮", "花蓮", 121.6, 23.99],
  ["tw-taitung", "台東", "台東", 121.15, 22.76],
  ["tw-chiayi", "嘉義", "嘉義", 120.45, 23.48],
  ["tw-changhua", "彰化", "彰化", 120.54, 24.08],
  ["tw-nantou", "南投", "南投", 120.69, 23.9],
  ["tw-yunlin", "雲林", "雲林", 120.54, 23.71],
  ["tw-miaoli", "苗栗", "苗栗", 120.82, 24.56],
  ["tw-pingtung", "屏東", "屏東", 120.49, 22.67],
  ["tw-penghu", "澎湖", "澎湖", 119.58, 23.57],
  ["tw-kinmen", "金門", "金門", 118.32, 24.43],
  ["tw-lienchiang", "連江", "連江", 119.95, 26.16]
].map(([id, city, region, longitude, latitude], index) => ({
  id,
  birthPlaceId: id,
  countryCode: "TW",
  country: "台灣",
  countryName: "台灣",
  type: "domestic",
  label: `台灣 ${city}`,
  city,
  cityName: city,
  region,
  timezone: "Asia/Taipei",
  longitude,
  latitude,
  priority: 100 - index
}));

const domesticRegionNames = {
  Beijing: "北京市",
  Shanghai: "上海市",
  Tianjin: "天津市",
  Chongqing: "重慶市",
  Hebei: "河北省",
  Shanxi: "山西省",
  Liaoning: "遼寧省",
  Jilin: "吉林省",
  Heilongjiang: "黑龍江省",
  Jiangsu: "江蘇省",
  Zhejiang: "浙江省",
  Anhui: "安徽省",
  Fujian: "福建省",
  Jiangxi: "江西省",
  Shandong: "山東省",
  Henan: "河南省",
  Hubei: "湖北省",
  Hunan: "湖南省",
  Guangdong: "廣東省",
  Hainan: "海南省",
  Sichuan: "四川省",
  Guizhou: "貴州省",
  Yunnan: "云南省",
  Shaanxi: "陝西省",
  Gansu: "甘肅省",
  Qinghai: "青海省",
  Taiwan: "台灣",
  Taipei: "台北",
  "New Taipei": "新北",
  Taoyuan: "桃園",
  Taichung: "台中",
  Tainan: "台南",
  Kaohsiung: "高雄",
  Hsinchu: "新竹",
  Chiayi: "嘉義",
  Changhua: "彰化",
  Pingtung: "屏東",
  Yilan: "宜蘭",
  Hualien: "花蓮",
  Taitung: "台東",
  Nantou: "南投",
  Yunlin: "雲林",
  Miaoli: "苗栗",
  Penghu: "澎湖",
  Kinmen: "金門",
  Lienchiang: "連江",
  "Inner Mongolia": "內蒙古",
  Guangxi: "廣西",
  Tibet: "西藏",
  Ningxia: "寧夏",
  Xinjiang: "新疆",
  Hongkong: "香港",
  "Hong Kong": "香港",
  Macao: "澳門"
};

function countryMatches(group, query) {
  const normalized = String(query || "").trim().toLowerCase();
  const normalizedTw = normalized
    .replaceAll("中国大陆", "中國大陸")
    .replaceAll("大陆", "大陸")
    .replaceAll("台湾", "台灣")
    .replaceAll("澳门", "澳門");
  if (!normalized) return true;
  return [group.country, group.countryName, group.countryCode, group.iso2, group.region, group.subregion]
    .filter(Boolean)
    .some((value) => String(value).toLowerCase().includes(normalized) || String(value).toLowerCase().includes(normalizedTw));
}

function marketCountryLabel(code, fallback = "") {
  if (code === "TW") return "台灣";
  if (code === "CN") return "中國大陸";
  if (code === "HK") return "香港";
  if (code === "MO") return "澳門";
  return String(fallback || "")
    .replaceAll("中国台湾", "台灣")
    .replaceAll("中國台灣", "台灣")
    .replaceAll("中国香港", "香港")
    .replaceAll("中國香港", "香港")
    .replaceAll("中国澳门", "澳門")
    .replaceAll("中國澳門", "澳門")
    .replace(/^中国$/, "中國大陸")
    .replace(/^中國$/, "中國大陸");
}

function marketLocationType(code, fallbackType) {
  if (code === "TW") return "domestic";
  if (code) return "overseas";
  return fallbackType === "domestic" ? "domestic" : "overseas";
}

function normalizeMarketLocation(location) {
  if (!location) return location;
  const countryCode = location.countryCode || location.iso2 ||
    (String(location.country || location.label || "").includes("台湾") || String(location.country || location.label || "").includes("台灣") ? "TW" : "") ||
    (String(location.country || location.label || "").includes("香港") ? "HK" : "") ||
    (String(location.country || location.label || "").includes("澳门") || String(location.country || location.label || "").includes("澳門") ? "MO" : "") ||
    (String(location.country || location.label || "").startsWith("中国") || String(location.country || location.label || "").startsWith("中國") ? "CN" : "");
  const country = marketCountryLabel(countryCode, location.country || location.countryName);
  const city = String(location.city || location.cityName || "").replaceAll("台湾", "台灣");
  return {
    ...location,
    countryCode: countryCode || location.countryCode,
    country,
    countryName: marketCountryLabel(countryCode, location.countryName || country),
    type: marketLocationType(countryCode, location.type),
    city,
    cityName: location.cityName || city,
    label: country && city ? `${country} ${city}` : marketCountryLabel(countryCode, location.label || country || city)
  };
}

function normalizeMarketLocationGroup(group) {
  if (!group) return group;
  const countryCode = group.countryCode || group.iso2;
  return {
    ...group,
    country: marketCountryLabel(countryCode, group.country || group.countryName),
    countryName: marketCountryLabel(countryCode, group.countryName || group.country),
    type: marketLocationType(countryCode, group.type)
  };
}

function frontendLocationMatches(location, query) {
  const keyword = String(query || "").trim().toLowerCase();
  if (!keyword) return true;
  const normalizedKeyword = keyword
    .replaceAll("台湾", "台灣")
    .replaceAll("台北市", "台北")
    .replaceAll("高雄市", "高雄");
  return [location.label, location.city, location.cityName, location.region, location.country]
    .filter(Boolean)
    .some((value) => String(value).toLowerCase().includes(keyword) || String(value).toLowerCase().includes(normalizedKeyword));
}

function mergeLocationOptions(...groups) {
  const seen = new Set();
  return groups.flat().filter(Boolean).filter((location) => {
    const key = location.id || `${location.countryCode}:${location.city}:${location.region}`;
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
    });
}

function centsToAdminAmount(value) {
  const amount = Number(value || 0) / 100;
  return Number.isFinite(amount) ? Math.round(amount) : 0;
}

function adminAmountToCents(value) {
  const amount = Number(String(value || "").replace(/[^\d.]/g, ""));
  return Number.isFinite(amount) ? Math.round(amount * 100) : 0;
}

function syncSubscriptionProductPrice(settings, productId, priceCents) {
  const products = settings.commerceProducts || [];
  return products.map((product) => (product.id === productId ? { ...product, priceCents: Number(priceCents || 0) } : product));
}

function domesticLocationText(location) {
  const region = domesticRegionNames[location.region] || location.region || location.country || "台灣";
  const city = location.city || location.cityName || location.label;
  return {
    accent: region,
    main: city,
    meta: formatGmtOffset(location.timezone)
  };
}

function overseasLocationText(location) {
  return {
    accent: `${location.country || "海外 / 其他地區"} ${formatGmtOffset(location.timezone)}`,
    main: location.city || location.cityName || location.label,
    meta: location.region || location.timezone
  };
}

function LocationPickerModal({
  open,
  mode,
  onModeChange,
  countryOptions,
  overseasCountryOptions,
  selectedCountry,
  onCountryChange,
  domesticSearch,
  onDomesticSearchChange,
  countrySearch,
  onCountrySearchChange,
  overseasCitySearch,
  onOverseasCitySearchChange,
  cityOptions,
  selectedLocationId,
  activeCountry,
  locationMeta,
  onChooseLocation,
  onClose
}) {
  if (!open) return null;

  const cityRows = cityOptions.slice(0, 80);
  const searchValue = mode === "domestic" ? domesticSearch : countrySearch;
  const searchPlaceholder = mode === "domestic" ? "搜尋台灣城市，例如 台北 / 新北 / 高雄" : "先輸入國家 / 地區，例如 中國大陸 / 香港 / 日本 / 美國";

  return (
    <AnimatePresence>
      <motion.div
        className="fixed inset-0 z-[72] flex items-end justify-center bg-slate-950/70 px-4 pb-4 backdrop-blur-sm sm:items-center sm:p-6"
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        onMouseDown={onClose}
      >
        <motion.div
          initial={{ opacity: 0, y: 28, scale: 0.98 }}
          animate={{ opacity: 1, y: 0, scale: 1 }}
          exit={{ opacity: 0, y: 20, scale: 0.98 }}
          onMouseDown={(event) => event.stopPropagation()}
          className="w-full max-w-3xl rounded-[2rem] border border-cyan-200/15 bg-[#101a2c]/95 p-5 text-white shadow-2xl shadow-cyan-950/40 backdrop-blur-xl"
        >
          <div className="flex items-center gap-3">
            <div className="grid flex-1 grid-cols-2 overflow-hidden rounded-full border border-white/10 bg-white/[0.06] p-1 text-center text-sm font-semibold">
              {[
                ["domestic", "台灣境內"],
                ["overseas", "海外 / 其他地區"]
              ].map(([value, label]) => (
                <button
                  key={value}
                  type="button"
                  onClick={() => onModeChange(value)}
                  className={cx(
                    "h-11 rounded-full transition",
                    mode === value ? "bg-cyan-300 text-slate-950" : "text-white/70 hover:bg-white/10 hover:text-white"
                  )}
                >
                  {label}
                </button>
              ))}
            </div>
            <button onClick={onClose} className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full text-white/55 transition hover:bg-white/10 hover:text-white">
              <X className="h-7 w-7" />
            </button>
          </div>

          <div className="mt-6 flex gap-3">
            <div className="relative min-w-0 flex-1">
              <Search className="absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-white/35" />
              <input
                value={searchValue}
                onChange={(event) => {
                  if (mode === "domestic") onDomesticSearchChange(event.target.value);
                  else onCountrySearchChange(event.target.value);
                }}
                placeholder={searchPlaceholder}
                className="h-13 w-full rounded-full border border-white/10 bg-white/[0.06] pl-12 pr-5 text-sm text-white outline-none placeholder:text-white/35 focus:border-cyan-200/60"
              />
            </div>
            <button onClick={onClose} className="h-13 rounded-full px-4 text-sm font-semibold text-cyan-100 transition hover:bg-white/10">
              取消
            </button>
          </div>

          {mode === "overseas" && (
            <div className="mt-4">
              <div className="mb-2 text-xs font-medium text-cyan-100/60">國家 / 地區（含中國大陸、港澳）</div>
              <div className="flex gap-2 overflow-x-auto pb-2 scrollbar-soft">
                {overseasCountryOptions.slice(0, 18).map((country) => (
                  <button
                    key={country.countryCode}
                    type="button"
                    onClick={() => onCountryChange(country.countryCode)}
                    className={cx(
                      "shrink-0 rounded-full border px-4 py-2 text-sm transition",
                      selectedCountry === country.countryCode
                        ? "border-cyan-200/50 bg-cyan-300 text-slate-950"
                        : "border-white/10 bg-white/[0.045] text-white/70 hover:bg-white/[0.08] hover:text-white"
                    )}
                  >
                    {country.country}
                  </button>
                ))}
              </div>
              <div className="relative mt-2">
                <Search className="absolute left-4 top-1/2 h-4 w-4 -translate-y-1/2 text-white/30" />
                <input
                  value={overseasCitySearch}
                  onChange={(event) => onOverseasCitySearchChange(event.target.value)}
                  placeholder={activeCountry ? `在${activeCountry.country}內搜尋城市` : "選擇國家後搜尋城市"}
                  className="h-11 w-full rounded-full border border-white/10 bg-white/[0.045] pl-10 pr-4 text-sm text-white outline-none placeholder:text-white/35 focus:border-cyan-200/60"
                />
              </div>
            </div>
          )}

          <div className="mt-5 border-t border-white/10 pt-4">
            <div className="mb-3 flex items-center justify-between gap-3 text-xs text-white/45">
              <span>
                {mode === "domestic"
                  ? "台灣城市"
                  : `${activeCountry?.country || "海外 / 其他地區"}城市`}
              </span>
              <span>{locationMeta.total ? `顯示 ${Math.min(locationMeta.limit || 0, locationMeta.total)}/${locationMeta.total}` : "無匹配結果"}</span>
            </div>
            <div className="max-h-[360px] space-y-2 overflow-y-auto pr-1 scrollbar-soft">
              {cityRows.map((location) => {
                const text = mode === "domestic" ? domesticLocationText(location) : overseasLocationText(location);
                const active = selectedLocationId === location.id;
                return (
                  <button
                    key={location.id}
                    type="button"
                    onClick={() => onChooseLocation(location)}
                    className={cx(
                      "flex min-h-14 w-full items-center justify-between gap-4 rounded-2xl border px-4 py-3 text-left transition",
                      active
                        ? "border-cyan-200/50 bg-cyan-300/15 shadow-[0_0_24px_rgba(103,232,249,0.12)]"
                        : "border-white/10 bg-white/[0.035] hover:border-cyan-200/30 hover:bg-white/[0.07]"
                    )}
                  >
                    <span className="min-w-0">
                      <span className="text-sm font-semibold text-amber-200/90">{text.accent}</span>
                      <span className="mx-2 text-white/35">-</span>
                      <span className="text-sm font-semibold text-white">{text.main}</span>
                    </span>
                    <span className="shrink-0 text-xs text-white/35">{text.meta}</span>
                  </button>
                );
              })}
              {!cityRows.length && (
                <div className="rounded-2xl border border-white/10 bg-white/[0.035] p-5 text-center text-sm text-white/45">
                  沒有找到匹配地點，換一個關鍵詞試試。
                </div>
              )}
            </div>
          </div>

          <button
            onClick={onClose}
            className="mt-6 h-14 w-full rounded-full bg-cyan-300 text-lg font-semibold text-slate-950 transition hover:bg-cyan-200"
          >
            確定
          </button>
        </motion.div>
      </motion.div>
    </AnimatePresence>
  );
}

function CityBaziIntake({
  form,
  currentUser,
  birthProfiles = [],
  selectedBirthProfileId,
  selectedBirthProfileLocked = false,
  onSelectBirthProfile,
  onCreateFriendBirthProfile,
  onLockedBirthEdit,
  isSubmitting,
  generationProgress,
  error,
  onChange,
  onBack,
  onSubmit,
  submitLabel = "生成城市發展報告"
}) {
  const [locations, setLocations] = useState([]);
  const [locationGroups, setLocationGroups] = useState([]);
  const [selectedCountry, setSelectedCountry] = useState(form.birthPlaceType === "overseas" ? form.birthCountryCode || "CN" : "TW");
  const [domesticSearch, setDomesticSearch] = useState("");
  const [countrySearch, setCountrySearch] = useState("");
  const [overseasCitySearch, setOverseasCitySearch] = useState("");
  const [locationMeta, setLocationMeta] = useState({ total: 0, limit: 160, country: null });
  const [locationStatus, setLocationStatus] = useState("正在按目前時區匹配國家 / 地區...");
  const [birthTimePickerOpen, setBirthTimePickerOpen] = useState(false);
  const [locationPickerOpen, setLocationPickerOpen] = useState(false);
  const [locationMode, setLocationMode] = useState(form.birthPlaceType === "overseas" ? "overseas" : "domestic");
  const update = (key, value) => onChange({ ...form, [key]: value });
  const birthTimeIsLocked = Boolean(selectedBirthProfileLocked && selectedBirthProfileId);
  const countryOptions = locationGroups;
  const cityOptions = locations;
  const currentSelectedLocation = form.birthPlaceId && !cityOptions.some((location) => location.id === form.birthPlaceId)
    ? [{ id: form.birthPlaceId, city: form.birthPlace?.split(" ").slice(1).join(" ") || form.birthPlace, label: form.birthPlace, region: "目前已選", timezone: form.timezone, capital: false }]
    : [];
  const totalCityCount = countryOptions.reduce((total, country) => total + Number(country.cityCount || 0), 0);
  const activeCountryCode = locationMode === "domestic" ? "TW" : selectedCountry;
  const activeCitySearch = locationMode === "domestic" ? domesticSearch : overseasCitySearch;
  const visibleCurrentLocation = form.birthCountryCode === activeCountryCode ? currentSelectedLocation : [];
  const overseasCountryOptions = useMemo(() => {
    const overseas = countryOptions.filter((group) => !taiwanDomesticRegionCodes.has(group.countryCode) && countryMatches(group, countrySearch));
    return overseas.sort((a, b) => {
      const aPopular = popularOverseasCountryCodes.includes(a.countryCode);
      const bPopular = popularOverseasCountryCodes.includes(b.countryCode);
      return Number(bPopular) - Number(aPopular) || a.country.localeCompare(b.country, "zh-Hans-CN");
    });
  }, [countryOptions, countrySearch]);
  const activeCountry = countryOptions.find((group) => group.countryCode === selectedCountry);

  useEffect(() => {
    const params = new URLSearchParams({ country: activeCountryCode, q: activeCitySearch, limit: "160" });
    const timer = window.setTimeout(() => {
      apiRequest(`/api/locations?${params.toString()}`)
        .then((data) => {
          const fallbackTaiwanLocations = activeCountryCode === "TW"
            ? taiwanPriorityLocations.filter((location) => frontendLocationMatches(location, activeCitySearch))
            : [];
          const normalizedLocations = mergeLocationOptions(
            fallbackTaiwanLocations,
            (data.locations || []).map(normalizeMarketLocation)
          );
          const normalizedGroups = (data.groups || []).map(normalizeMarketLocationGroup);
          const normalizedCountry = normalizeMarketLocationGroup(data.country);
          const displayedTotal = activeCountryCode === "TW"
            ? Math.max(Number(data.total || 0), normalizedLocations.length)
            : Number(data.total || 0);
          setLocations(normalizedLocations);
          setLocationGroups(normalizedGroups);
          setLocationMeta({ total: displayedTotal, limit: data.limit || 160, country: normalizedCountry || null });
          setLocationStatus(
            displayedTotal > data.limit
              ? `目前顯示 ${data.limit}/${displayedTotal} 個城市，可輸入城市名稱繼續搜尋。`
              : `已載入 ${normalizedCountry?.country || "目前國家/地區"} ${displayedTotal} 個城市。`
          );
        })
        .catch(() => {
          setLocations([]);
          setLocationStatus("地點數據暫時不可用，請稍後再試。");
        });
    }, activeCitySearch ? 220 : 0);
    return () => window.clearTimeout(timer);
  }, [activeCountryCode, activeCitySearch]);

  useEffect(() => {
    if (locationMode !== "overseas" || !overseasCountryOptions.length) return;
    const selectedStillVisible = overseasCountryOptions.some((country) => country.countryCode === selectedCountry);
    if (!selectedStillVisible) {
      setSelectedCountry(overseasCountryOptions[0].countryCode);
      setOverseasCitySearch("");
    }
  }, [locationMode, overseasCountryOptions, selectedCountry]);

  useEffect(() => {
    if (form.savedAt && form.birthCountryCode) {
      setLocationStatus("已使用上次保存的出生地資訊，可手動切換國家 / 地區和城市。");
      return;
    }
    const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || "Asia/Shanghai";
    apiRequest("/api/locations/resolve", {
      method: "POST",
      body: { timezone }
    })
      .then((data) => {
        const location = normalizeMarketLocation(data.location);
        if (!location) return;
        const isTaiwanLocation = location.countryCode === "TW";
        setLocationMode(isTaiwanLocation ? "domestic" : "overseas");
        setSelectedCountry(isTaiwanLocation ? "TW" : location.countryCode || "CN");
        setDomesticSearch("");
        setCountrySearch(isTaiwanLocation ? "" : location.country || "");
        setOverseasCitySearch("");
        chooseLocation(location);
        setLocationStatus(`已按目前時區默認選擇：${location.country}`);
      })
      .catch(() => setLocationStatus("可手動選擇國家 / 地區和城市。"));
  }, []);

  function changeLocationMode(nextMode) {
    setLocationMode(nextMode);
    if (nextMode === "domestic") {
      setSelectedCountry("TW");
      setDomesticSearch("");
      return;
    }
    const fallbackCountry =
      (form.birthPlaceType === "overseas" && form.birthCountryCode) ||
      overseasCountryOptions[0]?.countryCode ||
      "US";
    setSelectedCountry(fallbackCountry);
    setCountrySearch(form.birthPlaceType === "overseas" ? form.birthCountry || "" : "");
    setOverseasCitySearch("");
  }

  function chooseLocation(locationOrId) {
    const location = typeof locationOrId === "object"
      ? normalizeMarketLocation(locationOrId)
      : locations.find((item) => item.id === locationOrId || item.label === locationOrId);
    if (!location) {
      update("birthPlace", locationOrId);
      return;
    }
    setSelectedCountry(location.countryCode || selectedCountry);
    onChange({
      ...form,
      birthPlace: location.label,
      birthPlaceId: location.id,
      birthCountryCode: location.countryCode,
      birthCountry: location.country,
      birthPlaceType: location.type,
      timezone: location.timezone
    });
  }

  return (
    <div className="relative mt-6">
      {currentUser && (
        <div className="mb-4 rounded-3xl border border-cyan-200/15 bg-cyan-300/10 p-5">
          <div className="mb-3 flex items-start justify-between gap-3">
            <div>
              <div className="text-sm font-semibold text-cyan-100">命盤檔案</div>
              <p className="mt-1 text-xs leading-5 text-cyan-50/60">本人命盤首次測算後會鎖定；替親友測算請建立親友檔案。</p>
            </div>
            <button
              type="button"
              onClick={onCreateFriendBirthProfile}
              className="shrink-0 rounded-full border border-white/15 bg-white/5 px-4 py-2 text-xs font-semibold text-white transition hover:bg-white/10"
            >
              新增親友
            </button>
          </div>
          <div className="grid gap-2 sm:grid-cols-2">
            {birthProfiles.length ? birthProfiles.map((profile) => (
              <button
                type="button"
                key={profile.id}
                onClick={() => onSelectBirthProfile?.(profile)}
                className={cx(
                  "rounded-2xl border p-3 text-left transition",
                  selectedBirthProfileId === profile.id
                    ? "border-cyan-200/50 bg-cyan-300/20 text-cyan-50"
                    : "border-white/10 bg-slate-950/25 text-slate-300 hover:bg-white/[0.07]"
                )}
              >
                <div className="flex items-center justify-between gap-2">
                  <span className="text-sm font-semibold">{profile.label}</span>
                  <span className="rounded-full bg-white/10 px-2 py-0.5 text-[11px] text-cyan-100">{profile.type === "primary" ? "本人" : "親友"}</span>
                </div>
                <div className="mt-2 line-clamp-2 text-xs leading-5 text-white/45">
                  {profile.lockedAt ? "已鎖定" : "尚未鎖定"} · {profile.birthInfo?.birthPlace || "尚未保存出生地"}
                </div>
              </button>
            )) : (
              <div className="rounded-2xl border border-white/10 bg-slate-950/25 p-3 text-xs leading-5 text-slate-400 sm:col-span-2">
                第一次測算後，系統會自動建立並鎖定你的本人命盤。
              </div>
            )}
          </div>
        </div>
      )}

      <div>
        <FieldLabel>出生時間</FieldLabel>
        <button
          type="button"
          onClick={() => {
            if (birthTimeIsLocked) {
              onLockedBirthEdit?.();
              return;
            }
            setBirthTimePickerOpen(true);
          }}
          className={cx(
            "flex min-h-[56px] w-full items-center justify-between gap-4 rounded-2xl border px-4 py-4 text-left outline-none transition",
            birthTimeIsLocked
              ? "border-cyan-200/20 bg-cyan-300/[0.08] hover:border-cyan-200/35"
              : "border-white/10 bg-white/[0.06] hover:border-cyan-200/40 hover:bg-white/[0.08]"
          )}
        >
          <span className="min-w-0 flex-1">
            <BirthTimeFixedRow form={form} compact />
            <span className="mt-2 block text-xs text-white/40">
              {birthTimeIsLocked ? "命盤已鎖定，可直接繼續測算；需要更改時請建立親友命盤。" : "點擊選擇國曆 / 農曆出生年月日時分，時間不清楚可選未知"}
            </span>
          </span>
          <CalendarClock className={cx("h-5 w-5 shrink-0", birthTimeIsLocked ? "text-cyan-100/45" : "text-cyan-200")} />
        </button>
      </div>

      <BirthTimePickerModal
        form={form}
        open={birthTimePickerOpen}
        onClose={() => setBirthTimePickerOpen(false)}
        onConfirm={(nextForm) => {
          onChange(nextForm);
          setBirthTimePickerOpen(false);
        }}
      />

      <div className="mt-4">
        <div>
          <FieldLabel>性別</FieldLabel>
          <SegmentedControl
            value={form.gender}
            onChange={(value) => update("gender", value)}
            options={[
              { value: "female", label: "女" },
              { value: "male", label: "男" }
            ]}
          />
        </div>
      </div>

      <div className="mt-4">
        <FieldLabel>出生地</FieldLabel>
        <button
          type="button"
          onClick={() => setLocationPickerOpen(true)}
          className="flex min-h-[64px] w-full items-center justify-between gap-4 rounded-2xl border border-white/10 bg-white/[0.06] px-4 py-3 text-left outline-none transition hover:border-cyan-200/40 hover:bg-white/[0.08]"
        >
          <span className="min-w-0">
            <span className="block text-sm font-semibold text-white">{form.birthPlace || "請選擇出生地"}</span>
            <span className="mt-1 block truncate text-xs text-cyan-100/55">{formatTimezoneLabel(form)}</span>
          </span>
          <Search className="h-5 w-5 shrink-0 text-cyan-200" />
        </button>
        <p className="mt-3 text-xs leading-5 text-cyan-50/55">
          {locationStatus}
          {totalCityCount ? ` 全球地點庫共 ${countryOptions.length} 個國家/地區、${totalCityCount.toLocaleString()} 個城市。` : ""}
        </p>
      </div>

      <LocationPickerModal
        open={locationPickerOpen}
        mode={locationMode}
        onModeChange={changeLocationMode}
        countryOptions={countryOptions}
        overseasCountryOptions={overseasCountryOptions}
        selectedCountry={selectedCountry}
        onCountryChange={(countryCode) => {
          setSelectedCountry(countryCode);
          setCountrySearch(countryOptions.find((country) => country.countryCode === countryCode)?.country || "");
          setOverseasCitySearch("");
        }}
        domesticSearch={domesticSearch}
        onDomesticSearchChange={setDomesticSearch}
        countrySearch={countrySearch}
        onCountrySearchChange={(value) => {
          setCountrySearch(value);
          setOverseasCitySearch("");
        }}
        overseasCitySearch={overseasCitySearch}
        onOverseasCitySearchChange={setOverseasCitySearch}
        cityOptions={[...visibleCurrentLocation, ...cityOptions]}
        selectedLocationId={form.birthPlaceId}
        activeCountry={activeCountry}
        locationMeta={locationMeta}
        onChooseLocation={chooseLocation}
        onClose={() => setLocationPickerOpen(false)}
      />

      <label className="mt-4 flex items-center gap-3 rounded-2xl border border-white/10 bg-white/[0.045] p-4 text-sm text-slate-300">
        <input
          type="checkbox"
          checked={Boolean(form.saveBirthInfo)}
          onChange={(event) => update("saveBirthInfo", event.target.checked)}
          className="h-4 w-4 accent-cyan-300"
        />
        保存資訊，下次測試無需輸入
      </label>

      <div className="mt-4">
          <FieldLabel>目前國籍 / 長期身份</FieldLabel>
        <input
          value={form.currentNationality}
          onChange={(event) => update("currentNationality", event.target.value)}
          placeholder="例如：台灣 / 加拿大 / 新加坡"
          className="h-12 w-full rounded-2xl border border-white/10 bg-white/[0.06] px-4 text-sm outline-none placeholder:text-white/30 focus:border-cyan-200/60"
        />
      </div>

      {error && (
        <div className="mt-4 rounded-2xl border border-rose-300/20 bg-rose-400/10 p-4 text-sm text-rose-100">
          {error}
        </div>
      )}

      <GenerationProgressCard progress={isSubmitting ? generationProgress : null} />

      <div className="mt-6 grid gap-3 sm:grid-cols-2">
        <button onClick={onBack} className="inline-flex h-[52px] items-center justify-center rounded-full border border-white/15 bg-white/5 px-6 text-sm font-semibold text-white transition hover:bg-white/10">
          取消
        </button>
        <button disabled={isSubmitting} onClick={onSubmit} className="inline-flex h-[52px] items-center justify-center gap-2 rounded-full bg-white px-6 text-sm font-semibold text-slate-950 transition hover:bg-cyan-100 disabled:cursor-not-allowed disabled:opacity-60">
          {isSubmitting ? "正在排盤..." : submitLabel}
          <ChevronRight className="h-4 w-4" />
        </button>
      </div>
    </div>
  );
}

const taiwanDomesticCityTerms = [
  "台灣", "臺灣", "台北", "臺北", "新北", "桃園", "桃園", "台中", "臺中",
  "台南", "臺南", "高雄", "基隆", "新竹", "嘉义", "嘉義", "彰化",
  "南投", "云林", "雲林", "屏東", "屏東", "宜兰", "宜蘭", "花莲",
  "花蓮", "台東", "臺東", "澎湖", "金門", "金門", "連江", "連江"
];

function uniqueCityList(...lists) {
  return [...new Set(lists.flat().filter(Boolean))];
}

function isTaiwanDomesticCity(city) {
  const text = String(city || "");
  return taiwanDomesticCityTerms.some((term) => text.includes(term));
}

function stripTaiwanCitiesFromOverseasText(text) {
  let next = String(text || "");
  taiwanDomesticCityTerms.forEach((term) => {
    next = next
      .replaceAll(`${term}、`, "")
      .replaceAll(`、${term}`, "")
      .replaceAll(` ${term}`, "")
      .replaceAll(term, "");
  });
  return next
    .replace(/比較、/g, "比較")
    .replace(/，、/g, "，")
    .replace(/、、+/g, "、")
    .replace(/。\s*。/g, "。")
    .trim();
}

function normalizePurposeCityData(data) {
  if (!data) return data;
  const overseas = data.overseas || [];
  const movedDomestic = overseas.filter(isTaiwanDomesticCity);
  return {
    ...data,
    domestic: uniqueCityList(data.domestic || [], movedDomestic),
    overseas: overseas.filter((city) => !isTaiwanDomesticCity(city))
  };
}

function normalizeCityDevelopmentResult(result = {}) {
  const overseas = result.overseas || [];
  const movedDomestic = overseas.filter(isTaiwanDomesticCity);

  return {
    ...result,
    domestic: uniqueCityList(result.domestic || [], movedDomestic),
    overseas: overseas.filter((city) => !isTaiwanDomesticCity(city)),
    actionSuggestions: (result.actionSuggestions || []).map((item) =>
      String(item).includes("海外") ? stripTaiwanCitiesFromOverseasText(item) : item
    ),
    purposeCities: result.purposeCities
      ? {
          careerWealth: normalizePurposeCityData(result.purposeCities.careerWealth),
          love: normalizePurposeCityData(result.purposeCities.love),
          balanced: normalizePurposeCityData(result.purposeCities.balanced)
        }
      : result.purposeCities
  };
}

function CityDevelopmentResult({ form, result: rawResult, recommendations = [], onSelectTest, onRestart, onLogin, onSubscribe, onClose }) {
  const result = normalizeCityDevelopmentResult(rawResult);
  const baziInfo = result.baziInfo || {
    yearPillar: result.pillarsPreview?.year,
    monthPillar: result.pillarsPreview?.month,
    dayPillar: result.pillarsPreview?.day,
    hourPillar: result.pillarsPreview?.hour,
    dayMaster: result.usefulElement,
    fullPillars: [result.pillarsPreview?.year, result.pillarsPreview?.month, result.pillarsPreview?.day, result.pillarsPreview?.hour].filter(Boolean).join(" "),
    timeStatus: form.birthTimeMode === "unknown" ? "出生時間未知" : "已納入出生時分"
  };

  return (
    <div className="relative mt-6">
      {result.notice && (
        <div className="mb-4 rounded-3xl border border-cyan-200/25 bg-cyan-300/10 p-4 text-sm leading-6 text-cyan-50">
          {result.notice}
        </div>
      )}
      <div className="rounded-3xl border border-cyan-200/15 bg-cyan-300/10 p-5">
        <div className="text-sm text-cyan-100/75">城市發展基礎報告</div>
        <h4 className="mt-2 text-3xl font-semibold text-white">你更適合去{result.direction}尋找發展主場</h4>
        <p className="mt-3 text-sm leading-7 text-slate-300">{result.preference}</p>
        {result.reportSummary && <p className="mt-3 text-sm leading-7 text-slate-300">{result.reportSummary}</p>}
      </div>

      <div className="mt-4 grid gap-3 sm:grid-cols-4">
        {Object.entries(result.pillarsPreview).map(([label, value]) => (
          <div key={label} className="rounded-2xl border border-white/10 bg-white/[0.06] p-4">
            <div className="text-xs uppercase text-white/35">{label}</div>
            <div className="mt-1 text-sm font-semibold text-white">{value}</div>
          </div>
        ))}
      </div>

      <div className="mt-4 rounded-3xl border border-white/10 bg-white/[0.045] p-5">
        <div className="mb-4 flex items-center gap-2 text-sm font-semibold text-cyan-100">
          <Compass className="h-4 w-4" />
          什麼樣的地方適合你
        </div>
        <p className="text-sm leading-7 text-slate-300">
          適合優先考慮：{result.placeType}。城市選擇上，先看是否能長期提供你的喜用元素，再看行業機會、身份路徑和生活承壓成本。
        </p>
        {result.plainExplanation && (
          <div className="mt-4 grid gap-3">
            {[
              ["你需要的環境", result.plainExplanation.need],
              ["為什麼這些城市更匹配", result.plainExplanation.cityLogic],
              ["需要少去的地方", result.plainExplanation.avoid]
            ].map(([title, desc]) => (
              <div key={title} className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
                <div className="text-sm font-semibold text-white">{title}</div>
                <div className="mt-2 text-sm leading-6 text-slate-400">{desc}</div>
              </div>
            ))}
          </div>
        )}
      </div>

      <div className="mt-4 grid gap-4 sm:grid-cols-2">
        <div className="rounded-3xl border border-white/10 bg-white/[0.045] p-5">
          <div className="mb-4 text-sm font-semibold text-cyan-100">城市選擇邏輯</div>
          <div className="space-y-3">
            {(result.citySelectionLogic || []).map((item, index) => (
              <div key={item} className="flex gap-3 text-sm leading-6 text-slate-300">
                <span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-cyan-300/15 text-xs font-semibold text-cyan-100">{index + 1}</span>
                {item}
              </div>
            ))}
          </div>
        </div>
        <div className="rounded-3xl border border-white/10 bg-white/[0.045] p-5">
          <div className="mb-4 text-sm font-semibold text-cyan-100">下一步怎麼驗證</div>
          <div className="space-y-3">
            {(result.actionSuggestions || []).map((item) => (
              <div key={item} className="flex gap-3 text-sm leading-6 text-slate-300">
                <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-cyan-200" />
                {item}
              </div>
            ))}
          </div>
        </div>
      </div>

      <div className="mt-4 grid gap-4 sm:grid-cols-2">
        <CityRecommendationList title="台灣境內優先城市" cities={result.domestic} />
        <CityRecommendationList title="海外 / 其他地區優先城市" cities={result.overseas} />
      </div>

      {result.purposeCities && (
        <div className="mt-4 rounded-3xl border border-white/10 bg-white/[0.045] p-5">
          <div className="mb-4 flex items-center gap-2 text-sm font-semibold text-cyan-100">
            <MapPinned className="h-4 w-4" />
            按人生目標細分的城市建議
          </div>
          <div className="grid gap-4 lg:grid-cols-3">
            <PurposeCityCard title="更利於事業和財運" data={result.purposeCities.careerWealth} />
            <PurposeCityCard title="更利於找到正緣對象" data={result.purposeCities.love} />
            <PurposeCityCard title="事業和感情都有利" data={result.purposeCities.balanced} />
          </div>
        </div>
      )}

      <div className="mt-4 rounded-3xl border border-white/10 bg-slate-950/25 p-5">
        <div className="mb-3 text-sm font-semibold text-white">測算基準</div>
        <div className="grid gap-2 text-sm leading-6 text-slate-400 sm:grid-cols-2">
          <div>出生日期：{form.calendarType === "solar" ? "國曆" : "農曆"} {form.year}-{form.month}-{form.day}</div>
          <div>出生時間：{form.birthTimeMode === "unknown" ? "未知" : `${form.hour}:${String(form.minute).padStart(2, "0")}`}</div>
          <div>出生地：{form.birthPlaceType === "domestic" ? "台灣境內" : "海外 / 其他地區"} · {form.birthPlace}</div>
          <div>目前國籍：{form.currentNationality}</div>
        </div>

        <div className="mt-5 rounded-2xl border border-cyan-200/15 bg-cyan-300/10 p-4">
          <div className="mb-3 text-sm font-semibold text-cyan-100">八字資訊</div>
          <div className="grid gap-3 sm:grid-cols-4">
            {[
              ["年柱", baziInfo.yearPillar],
              ["月柱", baziInfo.monthPillar],
              ["日柱", baziInfo.dayPillar],
              ["時柱", baziInfo.hourPillar]
            ].map(([label, value]) => (
              <div key={label} className="rounded-2xl border border-white/10 bg-slate-950/25 p-3">
                <div className="text-xs text-white/40">{label}</div>
                <div className="mt-1 text-sm font-semibold text-white">{value || "--"}</div>
              </div>
            ))}
          </div>
          <div className="mt-3 grid gap-2 text-xs leading-5 text-slate-400 sm:grid-cols-2">
            <div>日主參考：{baziInfo.dayMaster || "--"}</div>
            <div>{baziInfo.timeStatus}</div>
            {baziInfo.calendarNote && <div className="sm:col-span-2">{baziInfo.calendarNote}</div>}
          </div>
        </div>
      </div>

      <RecommendedTests tests={recommendations} onSelectTest={onSelectTest} />

      <div className="mt-6 grid gap-3 sm:grid-cols-3">
        <button onClick={onRestart} className="inline-flex h-[52px] items-center justify-center rounded-full border border-white/15 bg-white/5 px-5 text-sm font-semibold text-white transition hover:bg-white/10">
          重新填寫
        </button>
        <button
          onClick={() => {
            onClose();
            onSubscribe("開通會員後，可保存完整命理報告、每日玄學提醒，並獲得 24 小時線上玄學大師專屬解答。");
          }}
          className="inline-flex h-[52px] items-center justify-center rounded-full border border-white/15 bg-white/5 px-5 text-sm font-semibold text-white transition hover:bg-white/10"
        >
          保存到费曼号
        </button>
        <button onClick={onClose} className="inline-flex h-[52px] items-center justify-center rounded-full bg-white px-5 text-sm font-semibold text-slate-950 transition hover:bg-cyan-100">
          完成
        </button>
      </div>
    </div>
  );
}

function CityRecommendationList({ title, cities }) {
  return (
    <div className="rounded-3xl border border-white/10 bg-white/[0.06] p-5">
      <div className="mb-4 text-sm font-semibold text-cyan-100">{title}</div>
      <div className="flex flex-wrap gap-2">
        {cities.map((city) => (
          <span key={city} className="rounded-full bg-white/10 px-3 py-2 text-sm text-white">
            {city}
          </span>
        ))}
      </div>
    </div>
  );
}

function PurposeCityCard({ title, data }) {
  if (!data) return null;
  return (
    <div className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
      <div className="text-sm font-semibold text-white">{title}</div>
      <p className="mt-2 min-h-[72px] text-xs leading-6 text-slate-400">{data.reason}</p>
      <div className="mt-4 text-xs font-semibold text-cyan-100">台灣境內</div>
      <div className="mt-2 flex flex-wrap gap-2">
        {(data.domestic || []).map((city) => <span key={city} className="rounded-full bg-white/10 px-2.5 py-1.5 text-xs text-white">{city}</span>)}
      </div>
      <div className="mt-4 text-xs font-semibold text-cyan-100">海外 / 其他地區</div>
      <div className="mt-2 flex flex-wrap gap-2">
        {(data.overseas || []).map((city) => <span key={city} className="rounded-full bg-white/10 px-2.5 py-1.5 text-xs text-white">{city}</span>)}
      </div>
    </div>
  );
}

function MembershipPlanCard({ plan, highlighted = false, currentUser, onSubscribe }) {
  const isMember = currentUser?.membership?.status === "active";
  return (
    <div className={cx(
      "relative flex h-full flex-col rounded-[2rem] border p-6 backdrop-blur",
      highlighted
        ? "border-cyan-200/45 bg-cyan-300/[0.12] shadow-2xl shadow-cyan-950/30"
        : "border-white/10 bg-white/[0.055]"
    )}>
      {highlighted && (
        <div className="absolute right-5 top-5 rounded-full bg-cyan-300 px-3 py-1 text-xs font-semibold text-slate-950">
          最推薦
        </div>
      )}
      <div className="pr-24">
        <div className="text-sm font-semibold text-cyan-100">{plan.name}</div>
        <div className="mt-2 text-xs text-white/45">{plan.badge}</div>
      </div>
      <div className="mt-8 flex items-end gap-2">
        <div className="text-5xl font-semibold text-white">{formatMoneyCents(plan.priceCents, plan.currencySymbol, plan.locale)}</div>
        <div className="pb-2 text-sm text-slate-400">/{plan.period}</div>
      </div>
      <div className="mt-3 text-sm text-cyan-100/80">{plan.note}</div>
      <div className="mt-6 grid gap-3 text-sm text-slate-300">
        {["無限人生測試", "完整命理報告", "每日玄學提醒與開運貼士", "24 小時 AI 玄學大師指導"].map((item) => (
          <div key={item} className="flex items-center gap-2">
            <BadgeCheck className="h-4 w-4 shrink-0 text-cyan-200" />
            {item}
          </div>
        ))}
      </div>
      <button
        onClick={() => {
          if (isMember) {
            window.location.href = "/member";
            return;
          }
          onSubscribe(`${plan.name}：${plan.billing}。開通後可無限使用人生測試，獲得完整命理報告、每日玄學提醒與 24 小時 AI 玄學大師指導。`, plan.id);
        }}
        className={cx(
          "mt-7 h-[52px] rounded-full text-sm font-semibold transition",
          highlighted ? "bg-cyan-300 text-slate-950 hover:bg-cyan-200" : "border border-white/15 bg-white/5 text-white hover:bg-white/10"
        )}
      >
        {isMember ? "進入會員中心" : plan.cta}
      </button>
      <div className="mt-3 text-center text-xs text-white/40">{plan.billing}</div>
    </div>
  );
}

function MembershipPage({ currentUser, onLogin, onSubscribe, onUserUpdate, siteSettings = activeMarket }) {
  const plans = buildMembershipPlans(siteSettings);
  const [checkoutMessage, setCheckoutMessage] = useState("");
  const [checkoutLoading, setCheckoutLoading] = useState("");
  const addOnProducts = (siteSettings.commerceProducts || [])
    .filter((product) => product.status !== "archived")
    .filter((product) => !["monthly", "annual"].includes(product.id));

  async function buyProduct(product) {
    if (!currentUser) {
      onLogin();
      return;
    }
    setCheckoutLoading(product.id);
    setCheckoutMessage("");
    try {
      const data = await apiRequest("/api/billing/checkout", { method: "POST", body: { productId: product.id } });
      if (data.user && onUserUpdate) onUserUpdate(data.user);
      if (data.checkout?.mode === "live" && data.checkout?.redirectUrl) {
        window.location.href = data.checkout.redirectUrl;
        return;
      }
      setCheckoutMessage(`${data.product?.name || product.name} 已完成本機測試付款，訂單 ${data.order?.id} 已建立。`);
    } catch (error) {
      setCheckoutMessage(error.message || "建立訂單失敗");
    } finally {
      setCheckoutLoading("");
    }
  }

  return (
    <section id="membership" className="mx-auto max-w-7xl px-6 py-10 lg:px-8 lg:py-16">
      <div className="grid gap-10 lg:grid-cols-[0.92fr_1.08fr] lg:items-center">
        <motion.div initial={{ opacity: 0, y: 18 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.55 }}>
          <div className="mb-5 inline-flex items-center gap-2 rounded-full border border-cyan-200/20 bg-cyan-300/10 px-4 py-2 text-sm text-cyan-100">
            <Crown className="h-4 w-4" />
            {siteSettings.launchTag || activeMarket.launchTag} · 费曼号會員
          </div>
          <h1 className="max-w-3xl text-4xl font-semibold leading-tight tracking-normal text-white md:text-6xl">
            從一次體驗，
            <span className="block bg-gradient-to-r from-cyan-200 via-blue-200 to-violet-200 bg-clip-text text-transparent">
              進入完整人生航海圖
            </span>
          </h1>
          <p className="mt-6 max-w-2xl text-base leading-8 text-slate-300 md:text-lg">
            台灣首發版採用新台幣計價。遊客可以免費完成一次人生測試，但無法保存報告，也無法獲得每日玄學提醒、完整命理報告與 24 小時 AI 玄學大師指導。開通會員後，付費期內可無限次測試，並持續沉澱你的個人命盤檔案。
          </p>
          <div className="mt-5 flex flex-wrap gap-2">
            {(siteSettings.launchNotes || activeMarket.launchNotes).map((note) => (
              <span key={note} className="rounded-full border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-cyan-100/80">
                {note}
              </span>
            ))}
          </div>
          <div className="mt-8 flex flex-col gap-3 sm:flex-row">
            <SoftButton variant="cyan" onClick={() => {
              if (currentUser?.membership?.status === "active") {
                window.location.href = "/member";
                return;
              }
              onSubscribe("開通费曼号會員後，可解鎖無限人生測試、完整命理報告、每日玄學提醒和 24 小時 AI 玄學大師指導。", "annual");
            }}>
              {currentUser?.membership?.status === "active" ? "進入會員中心" : "查看推薦方案"}
              <ChevronRight className="h-5 w-5" />
            </SoftButton>
	            {currentUser?.membership?.status !== "active" && (
	              <SoftButton variant="outline" onClick={currentUser ? () => (window.location.href = "/member") : onLogin}>
	                <UserRound className="h-5 w-5" />
	                {currentUser ? "進入會員中心" : "登入费曼号"}
	              </SoftButton>
	            )}
          </div>
        </motion.div>

        <motion.div initial={{ opacity: 0, y: 18 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.55, delay: 0.08 }} className="rounded-[2rem] border border-white/10 bg-white/[0.055] p-5 shadow-2xl shadow-cyan-950/20 backdrop-blur">
          <div className="grid gap-3 sm:grid-cols-2">
            <MembershipPlanCard plan={plans.monthly} currentUser={currentUser} onSubscribe={onSubscribe} />
            <MembershipPlanCard plan={plans.annual} highlighted currentUser={currentUser} onSubscribe={onSubscribe} />
          </div>
        </motion.div>
      </div>

      {addOnProducts.length > 0 && (
        <div className="mt-8">
          {checkoutMessage && <div className="mb-4 rounded-2xl border border-cyan-200/15 bg-cyan-300/10 p-4 text-sm text-cyan-100">{checkoutMessage}</div>}
          <div className="grid gap-4 md:grid-cols-2">
            {addOnProducts.map((product) => (
              <div key={product.id} className="rounded-3xl border border-white/10 bg-white/[0.055] p-5 backdrop-blur">
              <div className="flex items-start justify-between gap-4">
                <div>
                  <div className="text-sm font-semibold text-cyan-100">{product.type === "credit_pack" ? "點數商品" : "單次商品"}</div>
                  <h3 className="mt-2 text-xl font-semibold text-white">{product.name}</h3>
                </div>
                <div className="rounded-2xl bg-cyan-300/15 px-4 py-2 text-sm font-semibold text-cyan-100">
                  {formatMoneyCents(product.priceCents, siteSettings.currencySymbol || activeMarket.currencySymbol, siteSettings.locale || activeMarket.locale)}
                </div>
              </div>
              <p className="mt-3 text-sm leading-6 text-slate-400">{product.description}</p>
              <button
                disabled={checkoutLoading === product.id}
                onClick={() => buyProduct(product)}
                className="mt-5 h-11 w-full rounded-full border border-white/15 bg-white/5 text-sm font-semibold text-white transition hover:bg-white/10 disabled:opacity-60"
              >
                {checkoutLoading === product.id ? "建立訂單中..." : currentUser ? "立即購買" : "登入後購買"}
              </button>
              </div>
            ))}
          </div>
        </div>
      )}

      <div className="mt-12 grid gap-5 md:grid-cols-4">
        {memberBenefits.map((benefit, index) => {
          const Icon = benefit.icon;
          return (
            <motion.div
              key={benefit.title}
              initial={{ opacity: 0, y: 16 }}
              whileInView={{ opacity: 1, y: 0 }}
              viewport={{ once: true, margin: "-80px" }}
              transition={{ duration: 0.45, delay: index * 0.04 }}
              className="rounded-3xl border border-white/10 bg-white/[0.055] p-5 backdrop-blur"
            >
              <div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-cyan-300/15 text-cyan-100 ring-1 ring-cyan-200/20">
                <Icon className="h-5 w-5" />
              </div>
              <h3 className="mt-5 text-lg font-semibold text-white">{benefit.title}</h3>
              <p className="mt-2 text-sm leading-6 text-slate-400">{benefit.desc}</p>
            </motion.div>
          );
        })}
      </div>

      <div className="mt-12 overflow-hidden rounded-[2rem] border border-white/10 bg-white/[0.055] backdrop-blur">
        <div className="grid gap-6 p-6 lg:grid-cols-[0.8fr_1.2fr] lg:p-8">
          <div>
            <div className="inline-flex items-center gap-2 rounded-full bg-white/10 px-4 py-2 text-sm text-cyan-100">
              <BadgeCheck className="h-4 w-4" />
              權益對比
            </div>
            <h2 className="mt-5 text-3xl font-semibold text-white">遊客與會員的差別</h2>
            <p className="mt-4 text-sm leading-7 text-slate-400">
              免費體驗用來判斷產品是否適合你；會員用於連續記錄、反覆測試、長期追問和獲得完整報告。
            </p>
          </div>
          <div className="overflow-hidden rounded-3xl border border-white/10">
            <div className="grid grid-cols-[1.15fr_0.9fr_1fr] bg-slate-950/35 text-sm font-semibold text-white">
              <div className="p-4">能力</div>
              <div className="p-4 text-slate-300">普通遊客</div>
              <div className="p-4 text-cyan-100">费曼号會員</div>
            </div>
            {accessRows.map(([label, guest, member]) => (
              <div key={label} className="grid grid-cols-[1.15fr_0.9fr_1fr] border-t border-white/10 text-sm">
                <div className="p-4 text-white">{label}</div>
                <div className="p-4 text-slate-400">{guest}</div>
                <div className="p-4 text-cyan-100">{member}</div>
              </div>
            ))}
          </div>
        </div>
      </div>

      <div className="mt-12 grid gap-5 lg:grid-cols-3">
        {[
          ["為什麼推薦年付？", `年付約按 10 個月價格計算，目前方案可省 ${formatMoneyCents(Math.max(plans.monthly.priceCents * 12 - plans.annual.priceCents, 0), plans.annual.currencySymbol, plans.annual.locale)}。適合想長期記錄運勢、城市發展、情感關係和財富節奏的用戶。`],
          ["完整命理報告包含什麼？", "系統會聚合你的測試、出生資訊與命盤結果，形成可複盤的人生航線，而不是單次結論。"],
          ["會員到期後會怎樣？", "到期後不會刪除帳號，但無限測試、完整報告、每日提醒與 AI 大師指導會暫停，續費後恢復。"]
        ].map(([title, desc]) => (
          <div key={title} className="rounded-3xl border border-white/10 bg-slate-950/25 p-6">
            <h3 className="text-lg font-semibold text-white">{title}</h3>
            <p className="mt-3 text-sm leading-7 text-slate-400">{desc}</p>
          </div>
        ))}
      </div>
    </section>
  );
}

function VipSection({ onLogin, onSubscribe, currentUser }) {
  return (
    <section id="vip" className="mx-auto max-w-7xl px-6 py-16 lg:px-8">
      <div className="overflow-hidden rounded-[2rem] border border-white/10 bg-gradient-to-r from-cyan-300/15 via-blue-400/10 to-violet-400/15 p-8 shadow-2xl shadow-cyan-950/20 backdrop-blur md:p-10">
        <div className="grid gap-8 md:grid-cols-[1fr_auto] md:items-center">
          <div>
            <div className="mb-3 inline-flex items-center gap-2 rounded-full bg-white/10 px-4 py-2 text-sm text-cyan-100">
              <Crown className="h-4 w-4" />
              费曼号會員
            </div>
            <h2 className="text-3xl font-semibold tracking-normal text-white md:text-4xl">保存所有測試記錄，生成你的長期人生航海圖</h2>
            <p className="mt-4 max-w-3xl text-slate-300">
              遊客僅可免費完成一次人生測試；會員可無限測試，獲得完整命理報告、每日玄學提醒和 24 小時 AI 玄學大師指導。
            </p>
          </div>
          <SoftButton onClick={() => {
            if (currentUser?.membership?.status === "active") {
              window.location.href = "/member";
              return;
            }
            onSubscribe("開通费曼号會員後，可保存報告並解鎖完整命理報告、每日玄學提醒和 24 小時 AI 玄學大師指導。", "annual");
          }} className="w-full md:w-auto">
            {currentUser?.membership?.status === "active" ? "查看會員權益" : "查看會員方案"}
          </SoftButton>
        </div>
      </div>
    </section>
  );
}

function Footer() {
  const legalLinks = [
    ["免費聲明", "/preview.html#legal-disclaimer"],
    ["服務條款", "/preview.html#legal-terms"],
    ["隱私政策", "/preview.html#legal-privacy"],
    ["退款政策", "/preview.html#legal-refund"]
  ];
  return (
    <footer className="mx-auto flex max-w-7xl flex-col justify-between gap-6 px-6 py-10 text-sm text-white/45 lg:flex-row lg:items-center lg:px-8">
      <div>
        <div className="flex items-center gap-2 text-white">
          <span className="flex h-8 w-8 overflow-hidden rounded-xl border border-cyan-300/25 bg-[#020817]">
            <img src={LOGO_SRC} alt={`${BRAND_NAME} ${BRAND_ENGLISH}`} className="h-full w-full object-cover" />
          </span>
          {BRAND_NAME}
        </div>
        <p className="mt-2">{BRAND_TAGLINE}</p>
      </div>
      <div className="flex flex-wrap gap-4">
        {navItems.map(([label, id]) => (
          <a key={id} href={navLinkFor(id)} className="transition hover:text-cyan-100">
            {label}
          </a>
        ))}
        {legalLinks.map(([label, href]) => (
          <a key={href} href={href} className="transition hover:text-cyan-100">
            {label}
          </a>
        ))}
      </div>
    </footer>
  );
}

function legalDocumentByPath(settings, path, hash = "") {
  const hashKey = String(hash || "").replace("#legal-", "");
  const key = String(hash || "").startsWith("#legal-") ? hashKey : path.split("/").filter(Boolean)[1] || "disclaimer";
  const fallback = activeMarket.legalDocuments?.[key] || {};
  return {
    key,
    title: settings.legalDocuments?.[key]?.title || fallback.title || "法務內容",
    updatedAt: settings.legalDocuments?.[key]?.updatedAt || fallback.updatedAt || "2026-06-07",
    content: settings.legalDocuments?.[key]?.content || fallback.content || "內容準備中。"
  };
}

function LegalPage({ siteSettings }) {
  const document = legalDocumentByPath(siteSettings, window.location.pathname, window.location.hash);
  return (
    <section className="mx-auto max-w-4xl px-6 py-14 lg:px-8">
      <div className="rounded-[2rem] border border-white/10 bg-white/[0.06] p-6 backdrop-blur md:p-8">
        <div className="mb-3 inline-flex items-center gap-2 text-sm font-medium text-cyan-200">
          <BadgeCheck className="h-4 w-4" />
          法務與使用聲明
        </div>
        <h1 className="text-3xl font-semibold text-white md:text-5xl">{document.title}</h1>
        <p className="mt-3 text-sm text-slate-500">最後更新：{document.updatedAt}</p>
        <div className="mt-8 whitespace-pre-line text-sm leading-8 text-slate-300">
          {document.content}
        </div>
        <div className="mt-8 rounded-2xl border border-cyan-200/15 bg-cyan-300/10 p-4 text-sm leading-6 text-cyan-100">
          以上內容為上線前版本，後續可由船長後台隨時調整。正式商用前，建議再由熟悉目標市場的法律或合規顧問確認。
        </div>
      </div>
    </section>
  );
}

function LoginModal({ open, onClose, onAuthenticated }) {
  const [mode, setMode] = useState("register");
  const [form, setForm] = useState({ email: "", password: "", name: "", resetToken: "" });
  const [error, setError] = useState("");
  const [notice, setNotice] = useState("");
  const [devResetLink, setDevResetLink] = useState("");
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    if (open) {
      setError("");
      setNotice("");
      setDevResetLink("");
      setLoading(false);
      const params = new URLSearchParams(window.location.search);
      const resetToken = params.get("resetToken") || "";
      if (resetToken || window.location.hash === "#reset-password") {
        setMode("reset");
        setForm((current) => ({ ...current, resetToken }));
      }
    }
  }, [open]);

  async function submitAuth() {
    setError("");
    setNotice("");
    setLoading(true);
    try {
      if (mode === "forgot") {
        const data = await apiRequest("/api/auth/password-reset/request", {
          method: "POST",
          body: { email: form.email }
        });
        setNotice(data.message || "如果信箱存在，我們會寄出密碼重設連結。");
        setDevResetLink(data.devResetLink || "");
        return;
      }
      if (mode === "reset") {
        const data = await apiRequest("/api/auth/password-reset/confirm", {
          method: "POST",
          body: { token: form.resetToken, password: form.password }
        });
        setNotice(data.message || "密碼已更新，請重新登入。");
        setMode("login");
        setForm({ email: form.email, password: "", name: "", resetToken: "" });
        return;
      }
      const data = await apiRequest(mode === "register" ? "/api/auth/register" : "/api/auth/login", {
        method: "POST",
        body: form
      });
      setStoredSession({ token: data.token, user: data.user });
      onAuthenticated(data.user);
      onClose();
    } catch (authError) {
      setError(authError.message || "認證失敗");
    } finally {
      setLoading(false);
    }
  }

  return (
    <AnimatePresence>
      {open && (
        <motion.div
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
          className="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/75 px-6 backdrop-blur-xl"
          onMouseDown={onClose}
        >
          <motion.div
            initial={{ opacity: 0, y: 20, scale: 0.96 }}
            animate={{ opacity: 1, y: 0, scale: 1 }}
            exit={{ opacity: 0, y: 16, scale: 0.96 }}
            onMouseDown={(event) => event.stopPropagation()}
            className="w-full max-w-md rounded-[2rem] border border-white/10 bg-white/[0.08] p-6 text-white shadow-2xl backdrop-blur-xl"
          >
            <div className="flex items-center justify-between">
              <div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-cyan-300 text-slate-950">
                <LogIn className="h-5 w-5" />
              </div>
              <button onClick={onClose} className="flex h-10 w-10 items-center justify-center rounded-full bg-white/10 text-white/70">
                <X className="h-5 w-5" />
              </button>
            </div>
            <h2 className="mt-7 text-2xl font-semibold">
              {mode === "register" ? "註冊费曼号" : mode === "forgot" ? "忘記密碼" : mode === "reset" ? "重設密碼" : "登入费曼号"}
            </h2>
            <p className="mt-3 text-sm leading-6 text-slate-400">
              {mode === "forgot"
                ? "輸入註冊信箱後，我們會寄出密碼重設連結。本地測試模式會直接顯示重設連結。"
                : mode === "reset"
                  ? "請輸入重設連結中的 token 與新密碼。"
                  : "註冊後可保存測試結果、開通會員、進入後台管理測試和 AI 提示詞。首個註冊帳號自動成為管理員。"}
            </p>
            {mode !== "forgot" && mode !== "reset" && <div className="mt-5 grid grid-cols-2 gap-2 rounded-2xl border border-white/10 bg-white/[0.045] p-1">
              {[
                ["register", "註冊"],
                ["login", "登入"]
              ].map(([value, label]) => (
                <button
                  key={value}
                  onClick={() => setMode(value)}
                  className={cx("rounded-xl px-3 py-2.5 text-sm transition", mode === value ? "bg-cyan-300 text-slate-950" : "text-white/65 hover:bg-white/8")}
                >
                  {label}
                </button>
              ))}
            </div>}
            <div className="mt-6 space-y-3">
              {mode === "register" && (
                <input
                  value={form.name}
                  onChange={(event) => setForm({ ...form, name: event.target.value })}
                  className="h-[52px] w-full rounded-2xl border border-white/10 bg-white/[0.07] px-4 text-sm outline-none placeholder:text-white/35 focus:border-cyan-200/60"
                  placeholder="暱稱"
                />
              )}
              {mode !== "reset" && (
                <input
                  value={form.email}
                  onChange={(event) => setForm({ ...form, email: event.target.value })}
                  className="h-[52px] w-full rounded-2xl border border-white/10 bg-white/[0.07] px-4 text-sm outline-none placeholder:text-white/35 focus:border-cyan-200/60"
                  placeholder="信箱"
                />
              )}
              {mode === "reset" && (
                <input
                  value={form.resetToken}
                  onChange={(event) => setForm({ ...form, resetToken: event.target.value })}
                  className="h-[52px] w-full rounded-2xl border border-white/10 bg-white/[0.07] px-4 text-sm outline-none placeholder:text-white/35 focus:border-cyan-200/60"
                  placeholder="重設 token"
                />
              )}
              <input
                value={form.password}
                onChange={(event) => setForm({ ...form, password: event.target.value })}
                type="password"
                className="h-[52px] w-full rounded-2xl border border-white/10 bg-white/[0.07] px-4 text-sm outline-none placeholder:text-white/35 focus:border-cyan-200/60"
                placeholder={mode === "forgot" ? "不用填密碼" : mode === "reset" ? "新密碼，至少 8 個字元" : "密碼"}
                disabled={mode === "forgot"}
              />
            </div>
            {notice && <div className="mt-4 rounded-2xl border border-cyan-200/20 bg-cyan-300/10 p-3 text-sm leading-6 text-cyan-100">{notice}</div>}
            {devResetLink && (
              <button
                onClick={() => {
                  const token = new URL(devResetLink, window.location.origin).searchParams.get("resetToken") || "";
                  setMode("reset");
                  setForm({ ...form, resetToken: token, password: "" });
                }}
                className="mt-3 w-full rounded-2xl border border-cyan-200/20 bg-white/[0.05] p-3 text-left text-xs leading-5 text-cyan-100 transition hover:bg-white/[0.08]"
              >
                本地測試重設連結：{devResetLink}
              </button>
            )}
            {error && <div className="mt-4 rounded-2xl border border-rose-300/20 bg-rose-400/10 p-3 text-sm text-rose-100">{error}</div>}
            <button disabled={loading} onClick={submitAuth} className="mt-5 inline-flex h-[52px] w-full items-center justify-center gap-2 rounded-full bg-cyan-300 text-sm font-semibold text-slate-950 hover:bg-cyan-200 disabled:opacity-60">
              <ArrowRight className="h-4 w-4" />
              {loading ? "處理中..." : mode === "forgot" ? "寄送重設連結" : mode === "reset" ? "更新密碼" : "進入人生遊輪"}
            </button>
            <div className="mt-4 flex flex-wrap justify-center gap-x-4 gap-y-2 text-xs text-cyan-100/70">
              {mode === "login" && <button onClick={() => setMode("forgot")} className="hover:text-cyan-50">忘記密碼？</button>}
              {(mode === "forgot" || mode === "reset") && <button onClick={() => setMode("login")} className="hover:text-cyan-50">回到登入</button>}
              {mode !== "register" && <button onClick={() => setMode("register")} className="hover:text-cyan-50">註冊新帳號</button>}
            </div>
          </motion.div>
        </motion.div>
      )}
    </AnimatePresence>
  );
}

function PaywallModal({ open, message, initialPlan = "monthly", currentUser, onClose, onLogin, onSubscribed, siteSettings = activeMarket }) {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");
  const [selectedPlan, setSelectedPlan] = useState(initialPlan || "monthly");
  const plans = useMemo(() => buildMembershipPlans(siteSettings), [siteSettings]);

  useEffect(() => {
    if (open) {
      setSelectedPlan(initialPlan || "monthly");
      setError("");
      setLoading(false);
    }
  }, [open, initialPlan]);

  async function subscribe() {
    if (!currentUser) {
      onClose();
      onLogin();
      return;
    }
    setLoading(true);
    setError("");
    try {
      const data = await apiRequest("/api/billing/subscribe", { method: "POST", body: { plan: selectedPlan } });
      setStoredSession({ ...getStoredSession(), user: data.user });
      onSubscribed(data.user);
      if (data.checkout?.mode === "live" && data.checkout?.redirectUrl) {
        window.location.href = data.checkout.redirectUrl;
        return;
      }
      onClose();
    } catch (subscribeError) {
      setError(subscribeError.message || "開通失敗");
    } finally {
      setLoading(false);
    }
  }

  return (
    <AnimatePresence>
      {open && (
        <motion.div className="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/75 px-6 backdrop-blur-xl" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} onMouseDown={onClose}>
          <motion.div
            initial={{ opacity: 0, y: 20, scale: 0.96 }}
            animate={{ opacity: 1, y: 0, scale: 1 }}
            exit={{ opacity: 0, y: 16, scale: 0.96 }}
            onMouseDown={(event) => event.stopPropagation()}
            className="w-full max-w-lg rounded-[2rem] border border-white/10 bg-white/[0.08] p-6 text-white shadow-2xl backdrop-blur-xl"
          >
            <div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-cyan-300 text-slate-950">
              <Crown className="h-5 w-5" />
            </div>
            <h2 className="mt-7 text-2xl font-semibold">開通费曼号會員</h2>
            <p className="mt-3 text-sm leading-7 text-slate-300">
              {message || "遊客僅可免費完成一次人生測試，無法保存報告。開通會員後可解鎖完整命理報告、每日玄學提醒和 24 小時線上玄學大師專屬解答。"}
            </p>
            <div className="mt-6 grid gap-3 sm:grid-cols-2">
              {Object.values(plans).map((plan) => (
                <button
                  key={plan.id}
                  onClick={() => setSelectedPlan(plan.id)}
                  className={cx(
                    "rounded-3xl border p-5 text-left transition",
                    selectedPlan === plan.id
                      ? "border-cyan-200/45 bg-cyan-300/15"
                      : "border-white/10 bg-white/[0.05] hover:bg-white/[0.08]"
                  )}
                >
                  <div className="text-sm font-semibold text-cyan-100">{plan.name}</div>
                  <div className="mt-3 text-3xl font-semibold text-white">{formatMoneyCents(plan.priceCents, plan.currencySymbol, plan.locale)}<span className="text-sm font-medium text-cyan-100/60"> / {plan.period}</span></div>
                  <div className="mt-2 text-xs leading-5 text-slate-400">{plan.note}</div>
                </button>
              ))}
            </div>
            <div className="mt-4 rounded-3xl border border-cyan-200/15 bg-cyan-300/10 p-5">
              <div className="grid gap-2 text-sm text-slate-300">
                {["無限人生測試", "完整命理報告", "每日玄學提醒與開運貼士", "24 小時 AI 玄學大師專屬解答"].map((item) => (
                  <div key={item} className="flex items-center gap-2">
                    <BadgeCheck className="h-4 w-4 text-cyan-200" />
                    {item}
                  </div>
                ))}
              </div>
            </div>
            {error && <div className="mt-4 rounded-2xl border border-rose-300/20 bg-rose-400/10 p-3 text-sm text-rose-100">{error}</div>}
            <div className="mt-6 grid gap-3 sm:grid-cols-2">
              <button onClick={onClose} className="h-[52px] rounded-full border border-white/15 bg-white/5 text-sm font-semibold text-white transition hover:bg-white/10">稍後再說</button>
              <button disabled={loading} onClick={subscribe} className="h-[52px] rounded-full bg-cyan-300 text-sm font-semibold text-slate-950 transition hover:bg-cyan-200 disabled:opacity-60">
                {currentUser ? (loading ? "開通中..." : plans[selectedPlan]?.cta || "立即開通") : "先註冊 / 登入"}
              </button>
            </div>
          </motion.div>
        </motion.div>
      )}
    </AnimatePresence>
  );
}

function MasterOnlinePage({ currentUser, onLogin, onSubscribe, siteSettings = activeMarket }) {
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState("");
  const [selectedTopic, setSelectedTopic] = useState("事業");
  const [answerDepth, setAnswerDepth] = useState("quick");
  const [lastQuestion, setLastQuestion] = useState("");
  const [loading, setLoading] = useState(false);
  const [notice, setNotice] = useState("");
  const isMember = currentUser?.membership?.status === "active";
  const topics = [
    ["感情", "正緣、復合、關係走向"],
    ["事業", "換工作、創業、職場選擇"],
    ["財運", "收入、投資、金錢壓力"],
    ["城市", "發展地、移居、海外方向"],
    ["流年", "近期節奏、年份關卡"],
    ["風水", "居住、方位、空間能量"]
  ];
  const starterQuestions = {
    感情: ["這段關係該不該繼續？", "我什麼時候比較容易遇到正緣？"],
    事業: ["我今年適合換工作嗎？", "我適合創業還是穩定上班？"],
    財運: ["我近期財運最該注意什麼？", "我適合靠什麼方式增加收入？"],
    城市: ["我適合留在台灣還是去海外發展？", "我目前所在城市適合我嗎？"],
    流年: ["我接下來三個月運勢重點是什麼？", "今年最需要避開的風險是什麼？"],
    風水: ["我現在的住處會影響運勢嗎？", "我適合什麼樣的房子和方位？"]
  };
  const followUpActions = ["看時間點", "看風險", "看行動建議"];

  useEffect(() => {
    if (!isMember) return;
    apiRequest("/api/member/master-chat")
      .then((data) => setMessages(data.messages || []))
      .catch((error) => setNotice(error.message || "暫時無法載入對話"));
  }, [isMember, currentUser?.id]);

  async function sendMessage(overrideText = "", intent = "") {
    const safeOverride = typeof overrideText === "string" ? overrideText : "";
    const text = String(safeOverride || input).trim();
    if (!text) return;
    setInput("");
    setLastQuestion(intent ? lastQuestion || text : text);
    const tempUser = { id: `tmp-u-${Date.now()}`, role: "user", content: text, createdAt: new Date().toISOString() };
    setMessages((items) => [...items, tempUser]);
    setLoading(true);
    setNotice("");
    try {
      const data = await apiRequest("/api/member/master-chat", {
        method: "POST",
        body: {
          message: text,
          category: selectedTopic,
          depth: answerDepth,
          intent
        }
      });
      setMessages((items) => [...items.filter((item) => item.id !== tempUser.id), ...(data.messages || [])]);
    } catch (error) {
      setMessages((items) => items.filter((item) => item.id !== tempUser.id));
      if (error.status === 402) onSubscribe(error.data?.message || "開通會員後可使用大師在線。", "annual");
      else setNotice(error.message || "發送失敗");
    } finally {
      setLoading(false);
    }
  }

  if (!isMember) {
    return (
      <section id="master" className="mx-auto max-w-7xl px-6 py-14 lg:px-8">
        <div className="overflow-hidden rounded-[2rem] border border-white/10 bg-white/[0.06] p-8 backdrop-blur">
          <div className="mb-3 inline-flex items-center gap-2 text-sm font-medium text-cyan-200">
            <MessageCircle className="h-4 w-4" />
            大師在線
          </div>
          <h1 className="max-w-3xl text-4xl font-semibold tracking-normal text-white md:text-6xl">24 小時 AI 玄學大師，一對一陪你拆解問題</h1>
          <p className="mt-5 max-w-3xl text-base leading-8 text-slate-300">
            遊客可查看會員特權，但無法進入對話。開通费曼号會員後，可隨時提問，獲得命理諮詢回覆、完整命理諮詢報告、每日玄學提醒與開運貼士。
          </p>
          <div className="mt-8 grid gap-4 md:grid-cols-3">
            {[
              ["一對一 24 小時諮詢", "可持續追問，不限時間整理人生決策。"],
              ["完整命理諮詢報告", "結合測試記錄、八字基準與你的問題生成深度分析。"],
              ["每日玄學提醒", "每天獲得當日節奏、風險提醒與可執行開運貼士。"]
            ].map(([title, desc]) => (
              <div key={title} className="rounded-3xl border border-white/10 bg-slate-950/25 p-5">
                <div className="text-sm font-semibold text-cyan-100">{title}</div>
                <div className="mt-3 text-sm leading-6 text-slate-400">{desc}</div>
              </div>
            ))}
          </div>
          <div className="mt-8 flex flex-col gap-3 sm:flex-row">
            <button onClick={() => currentUser ? onSubscribe("開通會員後可進入大師在線，獲得 24 小時一對一 AI 命理諮詢。", "annual") : onLogin()} className="h-[52px] rounded-full bg-cyan-300 px-7 text-sm font-semibold text-slate-950 transition hover:bg-cyan-200">
              {currentUser ? "開通會員進入大師在線" : "登入 / 註冊後開通"}
            </button>
            <a href="/membership" className="inline-flex h-[52px] items-center justify-center rounded-full border border-white/15 bg-white/5 px-7 text-sm font-semibold text-white transition hover:bg-white/10">
              查看會員方案
            </a>
          </div>
        </div>
      </section>
    );
  }

  return (
    <section id="master" className="mx-auto max-w-7xl px-6 py-10 lg:px-8">
      <div className="grid min-h-[72vh] overflow-hidden rounded-[2rem] border border-white/10 bg-white/[0.06] backdrop-blur lg:grid-cols-[310px_minmax(0,1fr)]">
        <aside className="border-b border-white/10 p-5 lg:border-b-0 lg:border-r">
          <div className="flex items-center gap-3">
            <div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-cyan-300 text-slate-950">
              <MessageCircle className="h-5 w-5" />
            </div>
            <div>
              <div className="font-semibold text-white">大師在線</div>
              <div className="text-xs text-cyan-100/60">會員 24 小時諮詢</div>
            </div>
          </div>
          <div className="mt-5 rounded-2xl border border-cyan-200/15 bg-cyan-300/10 p-4 text-sm leading-6 text-cyan-50/80">
            先選問題類型，再用短答逐步追問。系統會只帶必要背景，讓每次回覆更精準也更節省成本。
          </div>
          <div className="mt-5">
            <div className="mb-3 text-xs font-semibold text-white/45">問題類型</div>
            <div className="grid gap-2">
              {topics.map(([topic, desc]) => (
                <button
                  key={topic}
                  onClick={() => setSelectedTopic(topic)}
                  className={cx(
                    "rounded-2xl border px-4 py-3 text-left transition",
                    selectedTopic === topic ? "border-cyan-200/45 bg-cyan-300/15" : "border-white/10 bg-white/[0.04] hover:bg-white/[0.08]"
                  )}
                >
                  <div className="text-sm font-semibold text-white">{topic}</div>
                  <div className="mt-1 text-xs text-slate-400">{desc}</div>
                </button>
              ))}
            </div>
          </div>
          <div className="mt-5">
            <div className="mb-3 text-xs font-semibold text-white/45">回答長度</div>
            <div className="grid grid-cols-2 gap-2 rounded-2xl border border-white/10 bg-slate-950/30 p-1">
              {[
                ["quick", "快速解答"],
                ["deep", "深度解讀"]
              ].map(([value, label]) => (
                <button
                  key={value}
                  onClick={() => setAnswerDepth(value)}
                  className={cx("h-10 rounded-xl text-xs font-semibold transition", answerDepth === value ? "bg-cyan-300 text-slate-950" : "text-white/60 hover:bg-white/10 hover:text-white")}
                >
                  {label}
                </button>
              ))}
            </div>
          </div>
          <button onClick={() => setMessages([])} className="mt-4 h-10 w-full rounded-full border border-white/15 bg-white/5 text-sm font-semibold text-white transition hover:bg-white/10">
            清空當前畫面
          </button>
        </aside>
        <div className="flex min-h-[72vh] flex-col">
          <div className="border-b border-white/10 px-5 py-4">
            <div className="font-semibold text-white">费曼命理師</div>
            <div className="mt-1 text-xs text-slate-400">回答會參考你的測試記錄與後台提示詞；重大決策仍建議結合現實資訊判斷。</div>
          </div>
          <div className="scrollbar-soft flex-1 space-y-4 overflow-y-auto p-5">
            {!messages.length && (
              <div className="rounded-3xl border border-white/10 bg-slate-950/25 p-5">
                <div className="text-sm font-semibold text-white">先從一個清楚問題開始</div>
                <div className="mt-2 text-sm leading-7 text-slate-300">
                  目前選擇：{selectedTopic} · {answerDepth === "quick" ? "快速解答" : "深度解讀"}。建議先問一個具體場景，回答會先給結論，再給依據和下一步。
                </div>
                <div className="mt-4 flex flex-wrap gap-2">
                  {(starterQuestions[selectedTopic] || []).map((question) => (
                    <button
                      key={question}
                      onClick={() => sendMessage(question)}
                      className="rounded-full border border-cyan-200/20 bg-cyan-300/10 px-4 py-2 text-xs font-semibold text-cyan-50 transition hover:bg-cyan-300/20"
                    >
                      {question}
                    </button>
                  ))}
                </div>
              </div>
            )}
            {messages.map((item) => (
              <div key={item.id} className={cx("flex", item.role === "user" ? "justify-end" : "justify-start")}>
                <div className={cx("max-w-[86%] rounded-3xl px-5 py-4 text-sm", item.role === "user" ? "bg-cyan-300 text-slate-950 leading-7" : "border border-white/10 bg-slate-950/35 text-slate-200 shadow-lg shadow-slate-950/15")}>
                  <MasterMessageContent role={item.role} content={item.content} />
                </div>
              </div>
            ))}
            {loading && <div className="text-sm text-cyan-100/70">大師正在推演中...</div>}
            {notice && <div className="rounded-2xl border border-rose-300/20 bg-rose-400/10 p-3 text-sm text-rose-100">{notice}</div>}
          </div>
          <div className="border-t border-white/10 p-4">
            {!!messages.length && (
              <div className="mb-3 flex flex-wrap gap-2">
                {followUpActions.map((action) => (
                  <button
                    key={action}
                    disabled={loading}
                    onClick={() => sendMessage(`${lastQuestion || "剛才的問題"}，請${action.replace("看", "補充分析")}`, action)}
                    className="rounded-full border border-white/10 bg-white/[0.05] px-4 py-2 text-xs font-semibold text-white/75 transition hover:bg-white/[0.1] disabled:opacity-50"
                  >
                    {action}
                  </button>
                ))}
              </div>
            )}
            <div className="flex gap-3">
              <input
                value={input}
                onChange={(event) => setInput(event.target.value)}
                onKeyDown={(event) => {
                  if (event.key === "Enter" && !event.shiftKey) sendMessage();
                }}
                placeholder="輸入你想問大師的問題..."
                className="h-12 min-w-0 flex-1 rounded-full border border-white/10 bg-slate-950/35 px-5 text-sm text-white outline-none placeholder:text-white/35 focus:border-cyan-200/50"
              />
              <button disabled={loading || !input.trim()} onClick={() => sendMessage()} className="h-12 rounded-full bg-cyan-300 px-6 text-sm font-semibold text-slate-950 transition hover:bg-cyan-200 disabled:opacity-60">
                發送
              </button>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

function MasterMessageContent({ role, content }) {
  const text = String(content || "").trim();
  if (role === "user") return <>{text}</>;
  const blocks = text
    .replace(/。(?=\s*(?:\d+\.|[0-9]+、|可追問：|結論|建議|風險|時間))/g, "。\n\n")
    .replace(/(\s)([0-9]+\.)/g, "\n$2")
    .split(/\n{2,}/)
    .map((block) => block.trim())
    .filter(Boolean);

  return (
    <div className="master-answer">
      {blocks.length ? blocks.map((block, index) => {
        const isFollowUp = block.includes("可追問：");
        return (
          <p key={`${block.slice(0, 18)}-${index}`} className={cx(isFollowUp && "master-answer-followup")}>
            {block}
          </p>
        );
      }) : <p>{text}</p>}
    </div>
  );
}

function LuckCyclePreview({ profile }) {
  const luck = profile?.luckCycles;
  if (!luck) return null;
  const currentYear = new Date().getFullYear();
  const annual = (luck.annualLuck || []).find((item) => Number(item.year) === currentYear) || (luck.annualLuck || [])[0];
  const months = (luck.monthlyLuck || []).slice(0, 4);
  return (
    <div className="mt-4 rounded-2xl border border-cyan-200/15 bg-cyan-300/10 p-4">
      <div className="mb-3 text-xs font-semibold text-cyan-100">本地排盤 · 大運流年</div>
      <div className="grid gap-3 text-xs leading-5 text-slate-300 sm:grid-cols-3">
        <div className="rounded-xl bg-slate-950/30 p-3">
          <div className="text-white/45">行運方向</div>
          <div className="mt-1 font-semibold text-white">{luck.directionLabel} · {luck.startAgeText || `${luck.startAge}歲`}起運</div>
          <div className="mt-1 text-white/45">{luck.startRule || "按節氣折算起運"}</div>
        </div>
        <div className="rounded-xl bg-slate-950/30 p-3">
          <div className="text-white/45">目前大運</div>
          <div className="mt-1 font-semibold text-white">{luck.currentLuck?.pillar || "--"}</div>
          <div className="mt-1 text-white/45">
            {luck.currentLuck?.startAgeText || `${luck.currentLuck?.startAge ?? "--"}歲`}-
            {luck.currentLuck?.endAgeText || `${luck.currentLuck?.endAge ?? "--"}歲`}
          </div>
        </div>
        <div className="rounded-xl bg-slate-950/30 p-3">
          <div className="text-white/45">今年流年</div>
          <div className="mt-1 font-semibold text-white">{annual?.year || currentYear} · {annual?.pillar || "--"}</div>
          <div className="mt-1 text-white/45">{annual?.age ? `${annual.age} 歲` : "年齡依出生年估算"}</div>
        </div>
      </div>
      {months.length > 0 && (
        <div className="mt-3 flex flex-wrap gap-2">
          {months.map((month) => (
            <span key={month.label} className="rounded-full bg-white/10 px-3 py-1 text-xs text-cyan-50/80">
              {month.label} {month.pillar}
            </span>
          ))}
        </div>
      )}
      <p className="mt-3 text-xs leading-5 text-cyan-50/45">{luck.note}</p>
    </div>
  );
}

function splitPillar(pillar = "") {
  if (!pillar || pillar.includes("未知")) return { stem: "--", branch: "--" };
  return {
    stem: String(pillar).slice(0, 1) || "--",
    branch: String(pillar).slice(1, 2) || "--"
  };
}

function pillarTone(stem = "") {
  if ("丙丁巳午".includes(stem)) return "text-red-300";
  if ("甲乙寅卯".includes(stem)) return "text-emerald-300";
  if ("壬癸子亥".includes(stem)) return "text-sky-300";
  if ("庚辛申酉".includes(stem)) return "text-slate-100";
  if ("戊己辰戌丑未".includes(stem)) return "text-amber-300";
  return "text-white";
}

function BaziPillarColumn({ title, pillar, subtitle }) {
  const parts = splitPillar(pillar);
  return (
    <div className="min-w-[88px] rounded-2xl border border-white/10 bg-slate-950/35 p-3 text-center">
      <div className="text-xs text-white/45">{title}</div>
      <div className={cx("mt-3 text-4xl font-semibold leading-none", pillarTone(parts.stem))}>{parts.stem}</div>
      <div className={cx("mt-3 text-4xl font-semibold leading-none", pillarTone(parts.branch))}>{parts.branch}</div>
      <div className="mt-3 min-h-[18px] text-[11px] text-white/35">{subtitle}</div>
    </div>
  );
}

function BaziProfileDetailModal({ profile, onClose }) {
  if (!profile) return null;
  const birth = profile.birthInfo || {};
  const bazi = profile.bazi || {};
  const luck = profile.luckCycles || {};
  const cycles = luck.luckCycles || [];
  const annual = luck.annualLuck || [];
  const monthly = luck.monthlyLuck || [];
  const displayDate = birth.year
    ? `${birth.calendarType === "lunar" ? "農曆" : "國曆"} ${birth.year}-${String(birth.month || "").padStart(2, "0")}-${String(birth.day || "").padStart(2, "0")}`
    : "出生日期未填";
  const solarDateText = bazi.solarLocalTime || (bazi.solarDate
    ? `${bazi.solarDate.year}-${String(bazi.solarDate.month || "").padStart(2, "0")}-${String(bazi.solarDate.day || "").padStart(2, "0")} ${displayTime}`
    : null);
  const displayTime = birth.birthTimeMode === "unknown"
    ? "時辰未知"
    : `${String(birth.hour ?? "").padStart(2, "0")}:${String(birth.minute ?? 0).padStart(2, "0")}`;
  const pillarColumns = [
    ["年柱", bazi.yearPillar, "祖上 / 外部環境"],
    ["月柱", bazi.monthPillar, "月令 / 事業根基"],
    ["日柱", bazi.dayPillar, "日主 / 自我核心"],
    ["時柱", bazi.hourPillar, "晚景 / 行動出口"]
  ];

  return (
    <AnimatePresence>
      <motion.div
        className="fixed inset-0 z-[80] flex items-end justify-center bg-slate-950/80 px-3 pb-3 backdrop-blur-xl sm:items-center sm:p-6"
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        onMouseDown={onClose}
      >
        <motion.div
          initial={{ opacity: 0, y: 24, scale: 0.98 }}
          animate={{ opacity: 1, y: 0, scale: 1 }}
          exit={{ opacity: 0, y: 16, scale: 0.98 }}
          transition={{ duration: 0.22 }}
          onMouseDown={(event) => event.stopPropagation()}
          className="max-h-[92vh] w-full max-w-7xl overflow-hidden rounded-[2rem] border border-cyan-200/15 bg-[#08111f] text-white shadow-2xl shadow-cyan-950/40"
        >
          <div className="relative overflow-hidden border-b border-white/10 bg-[radial-gradient(circle_at_20%_0%,rgba(94,234,212,.22),transparent_24rem),linear-gradient(135deg,rgba(15,23,42,.96),rgba(8,17,31,.98))] p-5 md:p-7">
            <div className="absolute inset-0 opacity-30 [background-image:radial-gradient(circle_at_1px_1px,rgba(255,255,255,.32)_1px,transparent_0)] [background-size:26px_26px]" />
            <div className="relative flex flex-col gap-5 md:flex-row md:items-center md:justify-between">
              <div className="flex items-center gap-4">
                <div className="flex h-16 w-16 items-center justify-center rounded-2xl border border-cyan-200/25 bg-cyan-300/10">
                  <img src={LOGO_SRC} alt="FateMind" className="h-10 w-10 rounded-xl object-cover" />
                </div>
                <div>
                  <div className="flex flex-wrap items-center gap-3">
                    <h3 className="text-2xl font-semibold tracking-wide">{profile.label || "本人命盤"}</h3>
                    <span className="rounded-full bg-amber-300/15 px-3 py-1 text-xs font-semibold text-amber-100">{profile.type === "primary" ? "本人命盤" : "親友命盤"}</span>
                  </div>
                  <div className="mt-2 grid gap-x-6 gap-y-1 text-sm text-slate-300 md:grid-cols-2">
                    <div>{displayDate}</div>
                    <div>陽曆參考：{solarDateText || (birth.year ? `${birth.year}-${String(birth.month || "").padStart(2, "0")}-${String(birth.day || "").padStart(2, "0")} ${displayTime}` : "--")}</div>
                    <div>出生地：{birth.birthPlace || "未填"}</div>
                    <div>時區：{birth.timezone || "未填"}</div>
                  </div>
                </div>
              </div>
              <button onClick={onClose} className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-white/10 text-white/70 transition hover:bg-white/15 hover:text-white">
                <X className="h-5 w-5" />
              </button>
            </div>
          </div>

          <div className="max-h-[calc(92vh-120px)] overflow-y-auto p-4 md:p-6">
            <div className="grid gap-5 xl:grid-cols-[1.05fr_0.95fr]">
              <div className="rounded-3xl border border-white/10 bg-white/[0.04] p-4 md:p-5">
                <div className="mb-4 flex items-center justify-between gap-3">
                  <div>
                    <div className="text-sm font-semibold text-cyan-100">四柱排盤</div>
                    <div className="mt-1 text-xs text-white/45">年柱、月柱、日柱、時柱由本地規則生成，供後續報告與大師在線引用。</div>
                  </div>
                  <span className="rounded-full bg-cyan-300/10 px-3 py-1 text-xs font-semibold text-cyan-100">{bazi.fullPillars || "--"}</span>
                </div>
                <div className="grid grid-cols-2 gap-3 md:grid-cols-4">
                  {pillarColumns.map(([title, pillar, subtitle]) => (
                    <BaziPillarColumn key={title} title={title} pillar={pillar} subtitle={subtitle} />
                  ))}
                </div>
                <div className="mt-4 grid gap-3 text-xs leading-5 text-slate-300 md:grid-cols-3">
                  <div className="rounded-2xl bg-slate-950/35 p-3">
                    <div className="text-white/45">日主參考</div>
                    <div className="mt-1 font-semibold text-white">{bazi.dayMaster || "--"}</div>
                  </div>
                  <div className="rounded-2xl bg-slate-950/35 p-3">
                    <div className="text-white/45">月柱規則</div>
                    <div className="mt-1 font-semibold text-white">{bazi.monthRule || "節氣定月 / 五虎遁"}</div>
                  </div>
                  <div className="rounded-2xl bg-slate-950/35 p-3">
                    <div className="text-white/45">時柱規則</div>
                    <div className="mt-1 font-semibold text-white">{bazi.hourRule || "五鼠遁"}</div>
                  </div>
                </div>
                <div className="mt-4 rounded-2xl border border-white/10 bg-slate-950/35 p-4 text-xs leading-6 text-slate-300">
                  <div className="font-semibold text-cyan-100">排盤備註</div>
                  <div className="mt-2 grid gap-2 md:grid-cols-2">
                    <div>日界：{bazi.dayBoundaryRule || "--"}</div>
                    <div>時間：{bazi.timeStatus || "--"}</div>
                    <div className="md:col-span-2">{bazi.calendarNote || "國曆按所在地時區換算後進入排盤。"}</div>
                  </div>
                </div>
              </div>

              <div className="rounded-3xl border border-cyan-200/15 bg-cyan-300/10 p-4 md:p-5">
                <div className="mb-4 flex items-center justify-between">
                  <div>
                    <div className="text-sm font-semibold text-cyan-100">大運流年流月</div>
                    <div className="mt-1 text-xs text-white/45">{luck.directionLabel || "--"} · {luck.startAgeText || "--"}起運</div>
                  </div>
                  <div className="rounded-full bg-white/10 px-3 py-1 text-xs text-white/60">{luck.startRule || "按節氣起運"}</div>
                </div>

                <div className="overflow-x-auto rounded-2xl border border-white/10 bg-slate-950/25">
                  <table className="w-full min-w-[720px] text-center text-xs">
                    <thead className="bg-white/[0.06] text-white/50">
                      <tr>
                        <th className="px-3 py-3 text-left">大運</th>
                        {cycles.slice(0, 10).map((cycle) => (
                          <th key={cycle.index} className={cx("px-3 py-3", cycle.active && "bg-cyan-300/15 text-cyan-100")}>
                            {cycle.startYear}
                            <div className="mt-1 text-white/35">{cycle.startAgeText}</div>
                          </th>
                        ))}
                      </tr>
                    </thead>
                    <tbody>
                      <tr className="border-t border-white/10">
                        <td className="px-3 py-4 text-left font-semibold text-cyan-100">干支</td>
                        {cycles.slice(0, 10).map((cycle) => (
                          <td key={cycle.index} className={cx("px-3 py-4 text-lg font-semibold", cycle.active ? "bg-cyan-300/15 text-white" : "text-white/80")}>{cycle.pillar}</td>
                        ))}
                      </tr>
                      <tr className="border-t border-white/10 text-white/45">
                        <td className="px-3 py-3 text-left">年齡</td>
                        {cycles.slice(0, 10).map((cycle) => (
                          <td key={cycle.index} className="px-3 py-3">{cycle.startAgeText}-{cycle.endAgeText}</td>
                        ))}
                      </tr>
                    </tbody>
                  </table>
                </div>

                <div className="mt-4 grid gap-3 md:grid-cols-2">
                  <div className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
                    <div className="mb-3 text-sm font-semibold text-cyan-100">流年</div>
                    <div className="grid grid-cols-2 gap-2">
                      {annual.slice(0, 8).map((item) => (
                        <div key={item.year} className={cx("rounded-xl p-3 text-xs", item.active ? "bg-cyan-300 text-slate-950" : "bg-white/[0.06] text-slate-300")}>
                          <div className="font-semibold">{item.year}</div>
                          <div className="mt-1 text-lg font-semibold">{item.pillar}</div>
                          <div className="mt-1 opacity-70">{item.age}歲</div>
                        </div>
                      ))}
                    </div>
                  </div>
                  <div className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
                    <div className="mb-3 text-sm font-semibold text-cyan-100">近期流月</div>
                    <div className="grid grid-cols-2 gap-2">
                      {monthly.slice(0, 12).map((item) => (
                        <div key={item.label} className="rounded-xl bg-white/[0.06] p-3 text-xs text-slate-300">
                          <div className="font-semibold text-white">{item.label}</div>
                          <div className="mt-1 text-lg font-semibold text-cyan-100">{item.pillar}</div>
                        </div>
                      ))}
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </motion.div>
      </motion.div>
    </AnimatePresence>
  );
}

function MemberCenter({ currentUser, onLogin, onSubscribe, onUserUpdate, onLogout, siteSettings = activeMarket }) {
  const [center, setCenter] = useState(null);
  const [fullReport, setFullReport] = useState(null);
  const [selectedRecord, setSelectedRecord] = useState(null);
  const [selectedBaziProfile, setSelectedBaziProfile] = useState(null);
  const [activeMemberTab, setActiveMemberTab] = useState("overview");
  const [masterQuestion, setMasterQuestion] = useState("");
  const [profileName, setProfileName] = useState(currentUser?.name || "");
  const [newPassword, setNewPassword] = useState("");
  const [message, setMessage] = useState("");
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    if (!currentUser) return;
    apiRequest("/api/member/center")
      .then((data) => setCenter(data))
      .catch(() => {});
  }, [currentUser?.id, currentUser?.membership?.status]);

  useEffect(() => {
    setProfileName(currentUser?.name || "");
  }, [currentUser?.name]);

  if (!currentUser) {
    return (
      <section id="member" className="mx-auto max-w-7xl px-6 py-16 lg:px-8">
        <div className="rounded-[2rem] border border-white/10 bg-white/[0.06] p-8 backdrop-blur">
          <div className="mb-3 inline-flex items-center gap-2 text-sm font-medium text-cyan-200">
            <UserRound className="h-4 w-4" />
            會員中心
          </div>
          <h2 className="text-3xl font-semibold text-white md:text-5xl">登入费曼号，管理你的人生航海圖</h2>
          <p className="mt-4 max-w-2xl text-slate-400">保存測試報告、每日提醒與大師諮詢記錄，後續會在這裡生成長期人生報告。</p>
          <button onClick={onLogin} className="mt-7 h-[52px] rounded-full bg-cyan-300 px-7 text-sm font-semibold text-slate-950">登入 / 註冊费曼号</button>
        </div>
      </section>
    );
  }

  const isMember = currentUser.membership?.status === "active";
  const addOnProducts = (siteSettings.commerceProducts || [])
    .filter((product) => product.status !== "archived")
    .filter((product) => product.id === "full-report");

  async function loadFullReport() {
    try {
      setLoading(true);
      setMessage("完整命理報告正在生成，通常需要 15-45 秒。請稍候，不要重複點擊。");
      const data = await apiRequest("/api/member/full-report");
      setFullReport(data.report);
      setMessage(data.report?.summary || "完整報告已生成");
    } catch (error) {
      if (error.status === 402) onSubscribe(error.data?.message || "開通會員後可查看完整命理報告。");
      else {
        const timedOut = /timeout|aborted|逾時|超時/i.test(error.message || "");
        setMessage(timedOut ? "完整報告生成時間較長，請稍後再試一次；測試記錄和會員資料都已保存。" : error.message || "報告生成失敗");
      }
    } finally {
      setLoading(false);
    }
  }

  async function loadDailyReminder() {
    try {
      setLoading(true);
      const data = await apiRequest("/api/member/daily-reminder");
      setMessage(data.reminder?.content || "今日提醒已生成");
      setCenter(center ? { ...center, reminders: [data.reminder, ...(center.reminders || []).filter((item) => item.id !== data.reminder.id)] } : center);
    } catch (error) {
      if (error.status === 402) onSubscribe(error.data?.message || "開通會員後可獲得每日玄學提醒。");
      else setMessage(error.message || "提醒生成失敗");
    } finally {
      setLoading(false);
    }
  }

  async function submitMasterQuestion() {
    try {
      setLoading(true);
      const data = await apiRequest("/api/member/master-questions", { method: "POST", body: { question: masterQuestion } });
      setMasterQuestion("");
      setMessage("問題已提交，等待大師解答");
      setCenter(center ? { ...center, masterQuestions: [data.question, ...(center.masterQuestions || [])] } : center);
    } catch (error) {
      if (error.status === 402) onSubscribe(error.data?.message || "開通會員後可使用專屬解答。");
      else setMessage(error.message || "提交失敗");
    } finally {
      setLoading(false);
    }
  }

  async function saveProfile() {
    try {
      setLoading(true);
      const data = await apiRequest("/api/member/profile", {
        method: "PUT",
        body: { name: profileName, password: newPassword || undefined }
      });
      setNewPassword("");
      setMessage("帳號資料已保存");
      onUserUpdate(data.user);
      setCenter(center ? { ...center, user: data.user } : center);
    } catch (error) {
      setMessage(error.message || "資料保存失敗");
    } finally {
      setLoading(false);
    }
  }

  async function buyMemberProduct(product) {
    try {
      setLoading(true);
      const data = await apiRequest("/api/billing/checkout", { method: "POST", body: { productId: product.id } });
      if (data.user) onUserUpdate(data.user);
      if (data.checkout?.mode === "live" && data.checkout?.redirectUrl) {
        window.location.href = data.checkout.redirectUrl;
        return;
      }
      setMessage(`${data.product?.name || product.name} 已購買成功，已寫入會員中心。`);
      const refreshed = await apiRequest("/api/member/center");
      setCenter(refreshed);
    } catch (error) {
      if (error.status === 401) onLogin();
      else setMessage(error.message || "購買失敗");
    } finally {
      setLoading(false);
    }
  }

  async function createMemberFriendProfile() {
    try {
      setLoading(true);
      const friendCount = (center?.birthProfiles || []).filter((profile) => profile.type === "friend").length;
      const data = await apiRequest("/api/member/birth-profiles", {
        method: "POST",
        body: { type: "friend", label: `親友命盤 ${friendCount + 1}` }
      });
      setCenter(center ? { ...center, birthProfiles: data.profiles || [] } : center);
      setMessage("親友命盤已新增。下次測試前可選擇這個檔案並填入出生資料。");
    } catch (error) {
      if (error.status === 402) onSubscribe(error.data?.message || "開通會員後可建立親友命盤。");
      else setMessage(error.message || "新增親友命盤失敗");
    } finally {
      setLoading(false);
    }
  }

  async function renameBirthProfile(profileId, label) {
    try {
      const data = await apiRequest(`/api/member/birth-profiles/${encodeURIComponent(profileId)}`, {
        method: "PUT",
        body: { label }
      });
      setCenter(center ? { ...center, birthProfiles: data.profiles || [] } : center);
      setMessage("命盤檔案名稱已保存");
    } catch (error) {
      setMessage(error.message || "命盤檔案保存失敗");
    }
  }

  async function openBaziProfileDetail(profile) {
    if (!profile?.id) return;
    try {
      setLoading(true);
      const data = await apiRequest(`/api/member/birth-profiles/${encodeURIComponent(profile.id)}`);
      const freshProfile = data.profile || profile;
      setSelectedBaziProfile(freshProfile);
      setCenter(center ? {
        ...center,
        birthProfiles: (center.birthProfiles || []).map((item) => (item.id === freshProfile.id ? freshProfile : item))
      } : center);
    } catch (error) {
      setMessage(error.message || "命盤詳情讀取失敗，請稍後再試。");
      setSelectedBaziProfile(profile);
    } finally {
      setLoading(false);
    }
  }

  const memberNav = [
    { id: "overview", label: "總覽", icon: UserRound },
    { id: "reports", label: "報告記錄", icon: BadgeCheck },
    { id: "profiles", label: "命盤檔案", icon: MapPinned },
    { id: "services", label: "會員服務", icon: Crown },
    { id: "billing", label: "訂單提醒", icon: Coins },
    { id: "account", label: "帳號設定", icon: UserRound }
  ];
  const primaryBirthProfile = (center?.birthProfiles || []).find((profile) => profile.type === "primary") || (center?.birthProfiles || [])[0];

  return (
    <>
      <WorkspaceFrame
        title="我的费曼号"
        subtitle="集中管理測試記錄、完整報告、每日提醒和 24 小時專屬解答。"
        badge={<><UserRound className="h-4 w-4" /> 會員中心</>}
        navItems={memberNav}
        activeTab={activeMemberTab}
        onTabChange={setActiveMemberTab}
        action={
          <button onClick={() => !isMember && onSubscribe("開通會員後，可解鎖完整命理報告、每日玄學提醒和 24 小時線上玄學大師專屬解答。", "annual")} className={cx("h-[52px] rounded-full px-7 text-sm font-semibold", isMember ? "border border-cyan-200/20 bg-cyan-300/10 text-cyan-100" : "bg-cyan-300 text-slate-950")}>
            {isMember ? "會員已開通" : "升級會員"}
          </button>
        }
      >
        {message && <div className="mb-5 rounded-2xl border border-cyan-200/15 bg-cyan-300/10 p-4 text-sm text-cyan-100">{message}</div>}

        {activeMemberTab === "overview" && (
          <div className="grid gap-5">
            <div className="grid gap-5 md:grid-cols-2">
              {[["測試記錄", center?.usage?.tests ?? 0], ["大師諮詢", center?.usage?.masterQuestions ?? 0]].map(([label, value]) => (
                <div key={label} className="rounded-3xl border border-white/10 bg-white/[0.06] p-5 backdrop-blur">
                  <div className="text-sm text-slate-400">{label}</div>
                  <div className="mt-2 text-3xl font-semibold text-white">{value}</div>
                </div>
              ))}
            </div>
            <Panel title="最近記錄" description="點擊任意記錄查看報告詳情。">
              <div className="grid gap-3 md:grid-cols-2">
                {(center?.runs || []).slice(0, 4).map((run) => (
                  <button key={run.id} onClick={() => setSelectedRecord({ type: "run", item: run })} className="rounded-2xl border border-white/10 bg-slate-950/25 p-4 text-left transition hover:border-cyan-200/30 hover:bg-white/[0.07]">
                    <div className="text-sm font-semibold text-cyan-100">{run.testTitle}</div>
                    <div className="mt-2 text-sm font-medium text-white">{run.headline}</div>
                    <div className="mt-2 line-clamp-2 text-xs leading-5 text-slate-400">{run.summary}</div>
                  </button>
                ))}
              </div>
            </Panel>
            <Panel
              title="本人大運流年"
              description="根據你鎖定的命盤檔案，使用本地節氣排盤規則生成，供完整報告與大師在線引用。"
              action={
                <div className="flex flex-wrap gap-2">
                  {primaryBirthProfile?.birthInfo?.year && (
                    <button disabled={loading} onClick={() => openBaziProfileDetail(primaryBirthProfile)} className="h-10 rounded-full bg-cyan-300 px-4 text-xs font-semibold text-slate-950 disabled:opacity-60">詳情</button>
                  )}
                  <button onClick={() => setActiveMemberTab("profiles")} className="h-10 rounded-full border border-cyan-200/20 bg-cyan-300/10 px-4 text-xs font-semibold text-cyan-100">查看命盤檔案</button>
                </div>
              }
            >
              {primaryBirthProfile?.luckCycles ? (
                <LuckCyclePreview profile={primaryBirthProfile} />
              ) : (
                <div className="rounded-2xl border border-white/10 bg-slate-950/25 p-4 text-sm leading-6 text-slate-400">
                  尚未建立可排盤的本人命盤。完成一次命理測驗並勾選保存資料後，這裡會顯示大運、流年與近期流月。
                </div>
              )}
            </Panel>
          </div>
        )}

	        {activeMemberTab === "reports" && (
          <div className="grid gap-5">
            <Panel
              title="完整命理報告"
              description="會員可聚合所有測試記錄，生成長期人生航線；AI 生成通常需要 15-45 秒。"
              action={<button disabled={loading} onClick={loadFullReport} className="h-11 rounded-full bg-white px-5 text-sm font-semibold text-slate-950 disabled:opacity-60">{loading ? "生成中，請稍候..." : "生成 / 更新報告"}</button>}
            >
              {fullReport ? (
                <div className="grid gap-5">
                  {(fullReport.promptSource || fullReport.aiFallbackReason) && (
                    <div className={cx("rounded-full border px-4 py-2 text-xs leading-5 md:w-fit", fullReport.aiGenerated ? "border-cyan-200/15 bg-cyan-300/10 text-cyan-100" : "border-amber-300/20 bg-amber-400/10 text-amber-100")}>
                      {fullReport.promptSource || fullReport.aiFallbackReason}
                    </div>
                  )}
                  <ReportReader title={fullReport.title || "完整命理報告"} summary={fullReport.summary} sections={fullReport.sections || []} />
                  {!(fullReport.sections || []).length && (
                    <div className="rounded-2xl border border-amber-300/20 bg-amber-400/10 p-4 text-sm leading-6 text-amber-100">
                      報告已生成，但沒有返回可展示章節。請檢查後台「命理諮詢報告」提示詞是否要求輸出 title、summary、sections。
                    </div>
                  )}
                </div>
              ) : (
                <div className="text-sm text-slate-400">點擊右上方按鈕生成完整報告。</div>
              )}
            </Panel>
            <Panel title="全部測試記錄">
              <div className="grid gap-3 md:grid-cols-2">
                {(center?.runs || []).map((run) => (
                  <button key={run.id} onClick={() => setSelectedRecord({ type: "run", item: run })} className="rounded-2xl border border-white/10 bg-slate-950/25 p-4 text-left transition hover:border-cyan-200/30 hover:bg-white/[0.07]">
                    <div className="text-sm font-semibold text-cyan-100">{run.testTitle}</div>
                    <div className="mt-2 text-sm font-medium text-white">{run.headline}</div>
                    <div className="mt-2 line-clamp-2 text-xs leading-5 text-slate-400">{run.summary}</div>
                    <div className="mt-2 text-xs text-slate-500">{new Date(run.createdAt).toLocaleString()}</div>
                  </button>
                ))}
              </div>
            </Panel>
          </div>
	        )}

        {activeMemberTab === "profiles" && (
          <div className="grid gap-5">
            <Panel
              title="命盤檔案鎖定"
              description="會員權益以本人命盤為核心；親友測算請建立親友檔案，避免多人共用同一份會員權益。"
              action={<button disabled={loading} onClick={createMemberFriendProfile} className="h-11 rounded-full bg-white px-5 text-sm font-semibold text-slate-950 disabled:opacity-60">新增親友檔案</button>}
            >
              <div className="grid gap-3 md:grid-cols-2">
                {(center?.birthProfiles || []).map((profile) => (
                  <div key={profile.id} className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
                    <div className="mb-3 flex items-center justify-between gap-3">
                      <div className="flex flex-wrap items-center gap-2">
                        <div className="rounded-full bg-cyan-300/15 px-3 py-1 text-xs font-semibold text-cyan-100">{profile.type === "primary" ? "本人命盤" : "親友命盤"}</div>
                        <button
                          onClick={() => openBaziProfileDetail(profile)}
                          disabled={!profile.birthInfo?.year}
                          className="rounded-full border border-cyan-200/20 bg-cyan-300/10 px-3 py-1 text-xs font-semibold text-cyan-100 transition hover:bg-cyan-300/20 disabled:cursor-not-allowed disabled:opacity-40"
                        >
                          詳情
                        </button>
                      </div>
                      <div className={cx("rounded-full px-3 py-1 text-xs font-semibold", profile.lockedAt ? "bg-emerald-300/15 text-emerald-100" : "bg-white/10 text-white/55")}>
                        {profile.lockedAt ? "已鎖定" : "未鎖定"}
                      </div>
                    </div>
                    <input
                      value={profile.label || ""}
                      onChange={(event) => setCenter({ ...center, birthProfiles: center.birthProfiles.map((item) => (item.id === profile.id ? { ...item, label: event.target.value } : item)) })}
                      onBlur={(event) => renameBirthProfile(profile.id, event.target.value)}
                      className="h-11 w-full rounded-2xl border border-white/10 bg-white/[0.06] px-4 text-sm font-semibold text-white outline-none"
                    />
	                    <div className="mt-3 text-xs leading-5 text-slate-400">
	                      {profile.birthInfo?.year
	                        ? `${profile.birthInfo.calendarType === "lunar" ? "農曆" : "國曆"} ${profile.birthInfo.year}-${String(profile.birthInfo.month || "").padStart(2, "0")}-${String(profile.birthInfo.day || "").padStart(2, "0")} · ${profile.birthInfo.birthPlace || "出生地未填"}`
	                        : "尚未填入出生資料。下次測試時選擇此檔案即可補全並鎖定。"}
	                    </div>
	                    {profile.bazi?.fullPillars && (
	                      <div className="mt-3 rounded-2xl border border-white/10 bg-white/[0.04] p-3 text-xs leading-5 text-slate-300">
	                        八字：<span className="font-semibold text-white">{profile.bazi.fullPillars}</span>
	                      </div>
	                    )}
	                    <LuckCyclePreview profile={profile} />
	                  </div>
	                ))}
                {!(center?.birthProfiles || []).length && (
                  <div className="rounded-2xl border border-white/10 bg-slate-950/25 p-4 text-sm leading-6 text-slate-400 md:col-span-2">
                    尚未建立命盤檔案。完成第一次命理測驗後，系統會自動建立「本人命盤」。
                  </div>
                )}
              </div>
              <div className="mt-4 rounded-2xl border border-amber-300/20 bg-amber-400/10 p-4 text-xs leading-6 text-amber-100">
                目前會員方案可建立 {center?.entitlements?.friendProfileLimit ?? 0} 個親友命盤。本人命盤鎖定後若要更換生日，請在 24 小時內修正；超過後需聯絡客服，避免帳號被多人共用。
              </div>
            </Panel>
          </div>
        )}

        {activeMemberTab === "services" && (
          <div className="grid gap-5">
            <Panel title="每日玄學提醒" action={<button disabled={loading} onClick={loadDailyReminder} className="h-11 rounded-full bg-white px-5 text-sm font-semibold text-slate-950 disabled:opacity-60">生成今日提醒</button>}>
              <div className="grid gap-3">
                {(center?.reminders || []).slice(0, 6).map((item) => (
                  <div key={item.id} className="rounded-2xl border border-white/10 bg-slate-950/25 p-4 text-sm leading-6 text-slate-300">{item.date} · {item.content}</div>
                ))}
                {!(center?.reminders || []).length && <div className="text-sm text-slate-400">暫無提醒。</div>}
              </div>
            </Panel>
            <Panel title="專屬大師解答" description="提交後會進入後台隊列，管理員可回覆。">
              <div className="grid gap-3">
                <textarea value={masterQuestion} onChange={(event) => setMasterQuestion(event.target.value)} placeholder="向 24 小時線上玄學大師提問..." className="min-h-28 rounded-2xl border border-white/10 bg-white/[0.06] p-4 text-sm leading-6 text-white outline-none placeholder:text-white/30" />
                <button disabled={loading || !masterQuestion.trim()} onClick={submitMasterQuestion} className="h-12 rounded-full bg-cyan-300 text-sm font-semibold text-slate-950 disabled:opacity-60">提交專屬解答</button>
                {(center?.masterQuestions || []).map((item) => (
                  <div key={item.id} className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
                    <div className="text-sm text-white">{item.question}</div>
                    <div className="mt-2 text-sm leading-6 text-slate-400">{item.answer}</div>
                    <div className="mt-2 text-xs text-cyan-100/60">{item.status}</div>
                  </div>
                ))}
              </div>
            </Panel>
          </div>
        )}

        {activeMemberTab === "billing" && (
          <div className="grid gap-5">
            <Panel title="目前權益">
              <div className="grid gap-3 md:grid-cols-3">
                {[
                  ["會員狀態", isMember ? "已開通" : "免費帳號"],
                  ["單次完整命理報告", center?.entitlements?.fullReportAccess ? "已解鎖" : "未購買"]
                ].map(([label, value]) => (
                  <div key={label} className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
                    <div className="text-sm text-slate-400">{label}</div>
                    <div className="mt-2 text-xl font-semibold text-white">{value}</div>
                  </div>
                ))}
              </div>
            </Panel>

            {addOnProducts.length > 0 && (
              <Panel title="加購商品" description="不想立刻訂閱時，也可以先購買單次完整命理報告。">
                <div className="grid gap-3 md:grid-cols-2">
                  {addOnProducts.map((product) => (
                    <div key={product.id} className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
                      <div className="flex items-start justify-between gap-3">
                        <div>
                          <div className="text-sm font-semibold text-white">{product.name}</div>
                          <div className="mt-2 text-xs leading-5 text-slate-400">{product.description}</div>
                        </div>
                        <div className="shrink-0 rounded-xl bg-cyan-300/15 px-3 py-1.5 text-xs font-semibold text-cyan-100">
                          {formatMoneyCents(product.priceCents, siteSettings.currencySymbol || activeMarket.currencySymbol, siteSettings.locale || activeMarket.locale)}
                        </div>
                      </div>
                      <button disabled={loading} onClick={() => buyMemberProduct(product)} className="mt-4 h-10 w-full rounded-full border border-white/15 bg-white/5 text-sm font-semibold text-white transition hover:bg-white/10 disabled:opacity-60">
                        購買
                      </button>
                    </div>
                  ))}
                </div>
              </Panel>
            )}

            <Panel title="訂單與訂閱">
              <div className="grid gap-3 md:grid-cols-2">
              {(center?.orders || []).map((order) => (
                <div key={order.id} className="rounded-2xl border border-white/10 bg-slate-950/25 p-4 text-sm text-slate-300">
                  {formatOrderMoney(order, siteSettings)} · {orderProductLabel(order)} · {order.status}
                </div>
              ))}
              {!(center?.orders || []).length && <div className="text-sm text-slate-400">暫無訂單。</div>}
              </div>
            </Panel>
          </div>
        )}

        {activeMemberTab === "account" && (
          <Panel title="帳號資料">
            <div className="grid max-w-2xl gap-3">
              <input value={currentUser.email} readOnly className="h-12 rounded-2xl border border-white/10 bg-white/[0.04] px-4 text-sm text-white/55 outline-none" />
              <input value={profileName} onChange={(event) => setProfileName(event.target.value)} placeholder="暱稱" className="h-12 rounded-2xl border border-white/10 bg-white/[0.06] px-4 text-sm text-white outline-none placeholder:text-white/30" />
              <input value={newPassword} onChange={(event) => setNewPassword(event.target.value)} type="password" placeholder="新密碼，不修改可留空" className="h-12 rounded-2xl border border-white/10 bg-white/[0.06] px-4 text-sm text-white outline-none placeholder:text-white/30" />
              <div className="grid gap-2 sm:grid-cols-2">
                <button disabled={loading} onClick={saveProfile} className="h-12 rounded-full bg-cyan-300 text-sm font-semibold text-slate-950 disabled:opacity-60">保存資料</button>
                <button onClick={onLogout} className="h-12 rounded-full border border-white/15 bg-white/5 text-sm font-semibold text-white transition hover:bg-white/10">登出</button>
              </div>
            </div>
          </Panel>
        )}
      </WorkspaceFrame>
      <RecordDetailModal record={selectedRecord} onClose={() => setSelectedRecord(null)} />
      <BaziProfileDetailModal profile={selectedBaziProfile} onClose={() => setSelectedBaziProfile(null)} />
    </>
  );
}

function RecordDetailModal({ record, onClose }) {
  if (!record) return null;
  const { item } = record;
  const result = item.testId === "city-development"
    ? normalizeCityDevelopmentResult(item.result || {})
    : item.result || {};
  const reportSections = Array.isArray(result.sections) ? result.sections : [];
  const interpretationItems = [
    ...(Array.isArray(result.interpretation) ? result.interpretation : []),
    ...(Array.isArray(result.suggestions) ? result.suggestions : [])
  ].filter(Boolean);

  return (
    <AnimatePresence>
      <motion.div
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        className="fixed inset-0 z-50 flex items-end justify-center bg-slate-950/75 px-4 pb-4 backdrop-blur-xl sm:items-center sm:p-6"
        onMouseDown={onClose}
      >
        <motion.div
          initial={{ opacity: 0, y: 24, scale: 0.98 }}
          animate={{ opacity: 1, y: 0, scale: 1 }}
          exit={{ opacity: 0, y: 18, scale: 0.98 }}
          onMouseDown={(event) => event.stopPropagation()}
          className="max-h-[88vh] w-full max-w-2xl overflow-y-auto rounded-[2rem] border border-white/10 bg-[#101a2c]/95 p-6 text-white shadow-2xl shadow-cyan-950/30 backdrop-blur-xl scrollbar-soft"
        >
          <div className="flex items-start justify-between gap-4">
            <div>
              <div className="text-sm text-cyan-100/70">{item.category}</div>
              <h3 className="mt-2 text-2xl font-semibold">{item.testTitle}</h3>
              <div className="mt-2 text-xs text-white/45">{new Date(item.createdAt).toLocaleString()}</div>
            </div>
            <button onClick={onClose} className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-white/10 text-white/70 transition hover:bg-white/15 hover:text-white">
              <X className="h-5 w-5" />
            </button>
          </div>

          <div className="mt-5 space-y-4">
            <div className="rounded-3xl border border-cyan-200/15 bg-cyan-300/10 p-5">
              <div className="text-sm text-cyan-100/75">{item.headline}</div>
              <p className="mt-3 text-sm leading-7 text-slate-300">{item.summary}</p>
            </div>
            <div className="grid gap-3 sm:grid-cols-2">
              {(item.highlights || []).map((highlight) => (
                <div key={highlight} className="rounded-2xl border border-white/10 bg-white/[0.06] p-4 text-sm leading-6 text-slate-300">{highlight}</div>
              ))}
            </div>
            {reportSections.length > 0 && (
              <ReportReader title="完整報告內容" sections={reportSections} />
            )}
            {interpretationItems.length > 0 && (
              <div className="rounded-3xl border border-white/10 bg-slate-950/25 p-5">
                <div className="mb-3 text-sm font-semibold text-cyan-100">重點建議</div>
                {interpretationItems.map((line) => (
                  <div key={line} className="mb-2 flex gap-3 text-sm leading-6 text-slate-300">
                    <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-cyan-200" />
                    {line}
                  </div>
                ))}
              </div>
            )}
            {item.testId === "city-development" && (
              <div className="rounded-3xl border border-white/10 bg-slate-950/25 p-5">
                <div className="mb-3 text-sm font-semibold text-cyan-100">推薦城市</div>
                <div className="grid gap-3 sm:grid-cols-2">
                  <div className="rounded-2xl bg-white/[0.05] p-4 text-sm text-slate-300">台灣境內：{(result.domestic || []).join("、")}</div>
                  <div className="rounded-2xl bg-white/[0.05] p-4 text-sm text-slate-300">海外 / 其他地區：{(result.overseas || []).join("、")}</div>
                </div>
              </div>
            )}
            <div className="flex flex-col gap-3 rounded-3xl border border-white/10 bg-white/[0.04] p-5 sm:flex-row sm:items-center sm:justify-between">
              <div>
                <div className="text-sm font-semibold text-white">想用新的狀態再看一次？</div>
                <p className="mt-1 text-xs leading-5 text-slate-400">重新測一次會建立新的測試記錄，舊報告仍會保留在會員中心。</p>
              </div>
              <a href="/preview.html#tests" className="inline-flex h-11 shrink-0 items-center justify-center rounded-full bg-cyan-300 px-5 text-sm font-semibold text-slate-950 transition hover:bg-cyan-200">
                重新測一次
              </a>
            </div>
          </div>
        </motion.div>
      </motion.div>
    </AnimatePresence>
  );
}

function WorkspaceFrame({ title, subtitle, badge, action, navItems, activeTab, onTabChange, children }) {
  return (
    <section className="mx-auto max-w-7xl px-6 py-10 lg:px-8">
      <div className="mb-6 flex flex-col justify-between gap-5 md:flex-row md:items-end">
        <div>
          {badge && <div className="mb-3 inline-flex items-center gap-2 text-sm font-medium text-cyan-200">{badge}</div>}
          <h2 className="text-3xl font-semibold text-white md:text-5xl">{title}</h2>
          {subtitle && <p className="mt-4 max-w-2xl text-slate-400">{subtitle}</p>}
        </div>
        {action}
      </div>

      <div className="grid gap-5 lg:grid-cols-[240px_1fr]">
        <aside className="lg:sticky lg:top-24 lg:self-start">
          <div className="scrollbar-soft flex gap-2 overflow-x-auto rounded-3xl border border-white/10 bg-white/[0.06] p-2 backdrop-blur lg:block lg:space-y-1 lg:overflow-visible">
            {navItems.map((item) => {
              const Icon = item.icon || Sparkles;
              const active = activeTab === item.id;
              return (
                <button
                  key={item.id}
                  onClick={() => onTabChange(item.id)}
                  className={cx(
                    "flex h-11 shrink-0 items-center gap-3 rounded-2xl px-4 text-left text-sm transition lg:w-full",
                    active ? "bg-cyan-300 text-slate-950" : "text-slate-300 hover:bg-white/[0.08] hover:text-white"
                  )}
                >
                  <Icon className="h-4 w-4" />
                  <span className="whitespace-nowrap">{item.label}</span>
                </button>
              );
            })}
          </div>
        </aside>

        <div className="min-w-0">{children}</div>
      </div>
    </section>
  );
}

function Panel({ title, description, children, action }) {
  return (
    <div className="rounded-3xl border border-white/10 bg-white/[0.06] p-5 backdrop-blur">
      <div className="mb-5 flex flex-col justify-between gap-3 md:flex-row md:items-start">
        <div>
          <h3 className="text-xl font-semibold text-white">{title}</h3>
          {description && <p className="mt-2 text-sm leading-6 text-slate-400">{description}</p>}
        </div>
        {action}
      </div>
      {children}
    </div>
  );
}

function AdminEditField({ title, description, children }) {
  return (
    <div className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
      <div className="mb-3">
        <div className="text-sm font-semibold text-cyan-100">{title}</div>
        {description && <div className="mt-1 text-xs leading-5 text-slate-500">{description}</div>}
      </div>
      {children}
    </div>
  );
}

function AdminTextInput({ value, onChange, placeholder = "", inputMode, type = "text" }) {
  return (
    <input
      value={value}
      onChange={(event) => onChange(event.target.value)}
      placeholder={placeholder}
      inputMode={inputMode}
      type={type}
      className="h-12 w-full rounded-2xl border border-white/10 bg-white/[0.06] px-4 text-sm text-white outline-none placeholder:text-white/30 focus:border-cyan-200/45"
    />
  );
}

function AdminTextarea({ value, onChange, placeholder = "", minHeight = "min-h-28" }) {
  return (
    <textarea
      value={value}
      onChange={(event) => onChange(event.target.value)}
      placeholder={placeholder}
      className={cx(minHeight, "w-full rounded-2xl border border-white/10 bg-white/[0.06] p-4 text-sm leading-6 text-white outline-none placeholder:text-white/30 focus:border-cyan-200/45")}
    />
  );
}

function AdminPromptEditor({ title, description, prompt, onChange, onSave, userPlaceholder }) {
  return (
    <Panel
      title={title}
      description={description}
      action={<button onClick={onSave} className="h-11 rounded-full bg-cyan-300 px-5 text-sm font-semibold text-slate-950">保存提示詞</button>}
    >
      <div className="grid gap-4">
        <AdminEditField title="角色設定" description="控制 AI 的身份、語氣、邊界與禁忌。">
          <AdminTextarea
            value={prompt?.system || ""}
            onChange={(value) => onChange({ ...(prompt || {}), system: value })}
            placeholder="例如：你是费曼的專業命理顧問，使用繁體中文，語氣溫和、專業、不恐嚇。"
            minHeight="min-h-40"
          />
        </AdminEditField>
        <AdminEditField title="輸出內容提示詞" description="控制實際展示給會員的內容結構、分析角度與輸出長度。">
          <AdminTextarea
            value={prompt?.userTemplate || ""}
            onChange={(value) => onChange({ ...(prompt || {}), userTemplate: value })}
            placeholder={userPlaceholder}
            minHeight="min-h-64"
          />
        </AdminEditField>
      </div>
    </Panel>
  );
}

function reportSectionContent(section = {}) {
  if (Array.isArray(section.content)) return section.content.join("\n");
  if (Array.isArray(section.body)) return section.body.join("\n");
  return String(section.content || section.body || section.text || "");
}

function formatReportBlocks(text = "") {
  const normalized = String(text || "").replace(/\r\n/g, "\n").trim();
  if (!normalized) return [];
  return normalized
    .split(/\n{2,}/)
    .flatMap((chunk) => {
      const lines = chunk.split("\n").map((line) => line.trim()).filter(Boolean);
      const isList = lines.length > 1 && lines.every((line) => /^[-•*]\s+|^\d+[.)、]\s+/.test(line));
      return isList ? [{ type: "list", lines }] : lines.length > 1 && lines.every((line) => line.length <= 18) ? [{ type: "quote", lines }] : lines.map((line) => ({ type: "text", text: line }));
    });
}

function ReportText({ children }) {
  const blocks = formatReportBlocks(children);
  if (!blocks.length) return null;
  return (
    <div className="fatemind-report-prose">
      {blocks.map((block, index) => {
        if (block.type === "list") {
          return (
            <ul key={index}>
              {block.lines.map((line) => <li key={line}>{line.replace(/^[-•*]\s+|^\d+[.)、]\s+/, "")}</li>)}
            </ul>
          );
        }
        if (block.type === "quote") {
          return (
            <blockquote key={index}>
              {block.lines.map((line) => <div key={line}>{line}</div>)}
            </blockquote>
          );
        }
        const text = block.text || "";
        if (/^(#{1,4}\s*)?(前置聲明|前置声明|結論|结论|命局|命盤|命盘|格局|事業|事业|財運|财运|婚戀|婚恋|子女|六親|六亲|健康|大運|大运|流年|建議|建议|風險|风险|核心|做功|體用|体用)/.test(text) || /^[一二三四五六七八九十]+[、.．]\s+/.test(text)) {
          return <h4 key={index}>{text.replace(/^#{1,4}\s*/, "")}</h4>;
        }
        return <p key={index}>{text}</p>;
      })}
    </div>
  );
}

function ReportReader({ title, summary, sections = [], meta }) {
  return (
    <article className="fatemind-report-reader">
      {(title || meta) && (
        <header className="fatemind-report-header">
          <div>
            {meta && <div className="fatemind-report-meta">{meta}</div>}
            {title && <h3>{title}</h3>}
          </div>
        </header>
      )}
      {summary && (
        <section className="fatemind-report-summary">
          <ReportText>{summary}</ReportText>
        </section>
      )}
      <div className="fatemind-report-sections">
        {(sections || []).map((section, index) => {
          const content = reportSectionContent(section);
          if (!content) return null;
          return (
            <section key={`${section.title || "section"}-${index}`} className="fatemind-report-section">
              <h3>{section.title || `章節 ${index + 1}`}</h3>
              <ReportText>{content}</ReportText>
            </section>
          );
        })}
      </div>
    </article>
  );
}

const legalDocumentOptions = [
  ["disclaimer", "免費聲明"],
  ["terms", "服務條款"],
  ["privacy", "隱私政策"],
  ["refund", "退款政策"]
];

function AdminRouteGate({ currentUser, onLogin }) {
  if (window.location.pathname !== "/admin" || currentUser?.role === "admin") return null;

  return (
    <section id="admin" className="mx-auto max-w-7xl px-6 py-16 lg:px-8">
      <div className="rounded-[2rem] border border-cyan-200/15 bg-cyan-300/10 p-8 backdrop-blur">
        <div className="mb-3 inline-flex items-center gap-2 text-sm font-medium text-cyan-200">
          <Crown className="h-4 w-4" />
          船長後台入口
        </div>
        <h2 className="text-3xl font-semibold text-white md:text-5xl">請使用管理員帳號登入</h2>
        <p className="mt-4 max-w-2xl text-slate-300">登入後可管理用戶、會員、測試項目、AI 提示詞、訂單與大師解答。</p>
        <button onClick={onLogin} className="mt-7 h-[52px] rounded-full bg-cyan-300 px-7 text-sm font-semibold text-slate-950">登入管理員帳號</button>
      </div>
    </section>
  );
}

const emptyAdminTestDraft = {
  title: "",
  desc: "",
  category: "發展",
  recommendationPriority: 0,
  reportPrompt: ""
};

function createAdminTestDraft(overrides = {}) {
  return { ...emptyAdminTestDraft, ...overrides };
}

function adminTestToDraft(test = {}, promptTemplate = "") {
  return createAdminTestDraft({
    title: test.title || "",
    desc: test.desc || "",
    category: test.category || "發展",
    recommendationPriority: Number(test.recommendationPriority || 0),
    reportPrompt: test.reportPrompt ?? promptTemplate ?? ""
  });
}

function adminTestPayload(draft = {}) {
  return {
    title: String(draft.title || "").trim(),
    desc: draft.desc || "",
    category: draft.category || "未分類",
    recommendationPriority: Number(draft.recommendationPriority || 0),
    reportPrompt: draft.reportPrompt || ""
  };
}

function AdminPanel({ currentUser }) {
  const [tests, setTests] = useState([]);
  const [prompts, setPrompts] = useState({});
  const [overview, setOverview] = useState(null);
  const [selectedAdminRecord, setSelectedAdminRecord] = useState(null);
  const [activeAdminTab, setActiveAdminTab] = useState("dashboard");
  const [editingTestId, setEditingTestId] = useState("");
  const [isCreatingTest, setIsCreatingTest] = useState(false);
  const [testDraft, setTestDraft] = useState(() => createAdminTestDraft());
  const [message, setMessage] = useState("");
  const [aiTestState, setAiTestState] = useState({ loading: false, result: null });

  function adminOverviewWithSystem(data) {
    if (!data) return data;
    const settings = data.settings || {};
    const provider = (settings.paymentProviders || []).find((item) => item.enabled) || (settings.paymentProviders || [])[0] || {};
    const blockers = [];
    if (settings.paymentMode === "live") {
      if (!provider.enabled) blockers.push("尚未啟用付款服務");
      if (!provider.checkoutUrl) blockers.push("尚未配置 Checkout URL");
      if (!provider.webhookSecret || provider.webhookSecret === "已配置") blockers.push("尚未配置可用的 Webhook Secret");
    }
    return {
      ...data,
      system: data.system || {
        appVersion: "0.3.0-tw-commerce",
        schemaVersion: 3,
        targetSchemaVersion: 3,
        database: {
          counts: {
            users: data.stats?.users || 0,
            tests: (tests || []).length,
            runs: data.stats?.testRuns || 0,
            orders: data.stats?.orders || 0,
            paymentEvents: 0
          },
          backups: []
        },
        payment: {
          ready: blockers.length === 0,
          mode: settings.paymentMode || "mock",
          provider: provider.id || "mock",
          providerName: provider.name || "本機測試付款",
          webhookUrl: `/api/billing/webhook/${provider.id || "mock"}`,
          blockers
        },
        migrations: [],
        updateChecklist: [
          "目前頁面已載入新版前端；若系統資料缺失，請重啟後端讓新 API 生效。",
          "更新前先在船長後台建立資料庫備份。",
          "部署新代碼後執行資料庫遷移，確認 schemaVersion 等於 targetSchemaVersion。",
          "付款改 live 前先用測試 Webhook 打一筆訂單，確認 paymentEvents 有入庫且訂單會自動履約。"
        ]
      }
    };
  }

  useEffect(() => {
    if (!currentUser || currentUser.role !== "admin") return;
    apiRequest("/api/tests").then((data) => setTests(data.tests || [])).catch(() => {});
    apiRequest("/api/prompts").then((data) => setPrompts(data.prompts || {})).catch(() => {});
    apiRequest("/api/admin/overview").then((data) => setOverview(adminOverviewWithSystem(data))).catch(() => {});
  }, [currentUser]);

  if (!currentUser || currentUser.role !== "admin") return null;

  function startEditTest(test) {
    setIsCreatingTest(false);
    setEditingTestId(test.id);
    setTestDraft(adminTestToDraft(test, prompts[test.promptKey || test.id]?.userTemplate));
    setMessage(`正在修改「${test.title}」`);
  }

  function startCreateTest() {
    setIsCreatingTest(true);
    setEditingTestId("");
    setTestDraft(createAdminTestDraft({ category: tests[0]?.category || "發展", recommendationPriority: 0 }));
    setMessage("正在新增測試");
  }

  async function updatePrompt(key, patch) {
    const data = await apiRequest(`/api/prompts/${encodeURIComponent(key)}`, { method: "PUT", body: patch });
    setPrompts({ ...prompts, [key]: data.prompt });
    setMessage("AI 提示詞已保存");
  }

  async function updateTest(testId, patch) {
    setMessage("正在保存測試...");
    try {
      const data = await apiRequest(`/api/tests/${encodeURIComponent(testId)}`, { method: "PUT", body: patch });
      const nextTests = tests.map((item) => (item.id === testId ? data.test : item));
      setTests(nextTests);
      setTestDraft((draft) => (editingTestId === testId ? adminTestToDraft(data.test, prompts[data.test.promptKey || data.test.id]?.userTemplate) : draft));
      broadcastTestsCatalogUpdated(nextTests);
      setMessage(`「${data.test.title}」已保存成功`);
      return data.test;
    } catch (error) {
      const hint = error.status === 401 || error.status === 403 ? "請重新登入船長後台後再試。" : "請稍後再試。";
      setMessage(`保存失敗：${error.message || "權限或連線異常"}。${hint}`);
      return null;
    }
  }

  async function addTest(draft = testDraft) {
    const title = String(draft.title || "").trim();
    if (!title) {
      setMessage("新增失敗：請先填寫測試標題。");
      return null;
    }
    setMessage("正在新增測試...");
    try {
      const data = await apiRequest("/api/tests", {
        method: "POST",
        body: {
          ...adminTestPayload(draft),
          id: title
            .toLowerCase()
            .replace(/[^\w\u4e00-\u9fa5]+/g, "-")
            .replace(/^-+|-+$/g, "")
            .slice(0, 48) || `test-${Date.now()}`,
          tag: draft.category || "測試",
          status: "published",
          freeEligible: true
        }
      });
      const nextTests = [...tests, data.test];
      setTests(nextTests);
      setIsCreatingTest(false);
      setEditingTestId(data.test.id);
      setTestDraft(adminTestToDraft(data.test, prompts[data.test.promptKey || data.test.id]?.userTemplate));
      await refreshOverview();
      broadcastTestsCatalogUpdated(nextTests);
      setMessage(`「${data.test.title}」已新增成功`);
      return data.test;
    } catch (error) {
      setMessage(`新增失敗：${error.message || "請重新登入船長後台後再試"}`);
      return null;
    }
  }

  async function deleteTest(testId) {
    setMessage("正在刪除測試...");
    try {
      await apiRequest(`/api/tests/${encodeURIComponent(testId)}`, { method: "DELETE" });
      const nextTests = tests.filter((item) => item.id !== testId);
      setTests(nextTests);
      if (editingTestId === testId) {
        setEditingTestId("");
        setTestDraft(createAdminTestDraft());
      }
      await refreshOverview();
      broadcastTestsCatalogUpdated(nextTests);
      setMessage("測試已刪除");
    } catch (error) {
      setMessage(`刪除失敗：${error.message || "請重新登入船長後台後再試"}`);
    }
  }

  async function saveTestDraft() {
    if (isCreatingTest) {
      await addTest(testDraft);
      return;
    }
    if (!editingTestId) {
      setMessage("請先在左側選擇一個測試，再修改測試標題、簡介、提示詞、分類與推薦指數。");
      return;
    }
    await updateTest(editingTestId, adminTestPayload(testDraft));
  }

  function updateTestDraft(key, value) {
    setTestDraft((draft) => ({ ...draft, [key]: value }));
  }

  async function refreshOverview() {
    const data = await apiRequest("/api/admin/overview");
    setOverview(adminOverviewWithSystem(data));
  }

  async function updateAdminUser(userId, patch) {
    const data = await apiRequest(`/api/admin/users/${encodeURIComponent(userId)}`, { method: "PUT", body: patch });
    setOverview({
      ...overview,
      users: overview.users.map((item) => (item.id === userId ? data.user : item))
    });
    setMessage("用戶資訊已保存");
  }

  async function deleteAdminUser(userId) {
    await apiRequest(`/api/admin/users/${encodeURIComponent(userId)}`, { method: "DELETE" });
    await refreshOverview();
    setMessage("用戶及其關聯數據已刪除");
  }

  async function updateSettings(patch) {
    await apiRequest("/api/admin/settings", { method: "PUT", body: patch });
    await refreshOverview();
    setMessage("網站配置已保存");
  }

  async function testAiConnection() {
    setAiTestState({ loading: true, result: null });
    try {
      const data = await apiRequest("/api/admin/ai/test", { method: "POST", body: {} });
      setAiTestState({ loading: false, result: data });
      setMessage(`模型連線成功：${data.providerName || data.provider || "AI"}`);
    } catch (error) {
      setAiTestState({
        loading: false,
        result: { ok: false, message: error.message || "模型連線失敗" }
      });
      setMessage(error.message || "模型連線失敗");
    }
  }

  function updateSettingValue(key, value) {
    setOverview((current) => ({ ...current, settings: { ...current.settings, [key]: value } }));
  }

  function updateSubscriptionPrice(productId, amount) {
    const priceCents = adminAmountToCents(amount);
    setOverview((current) => {
      const settings = current.settings || {};
      const priceKey = productId === "annual" ? "annualPriceCents" : "monthlyPriceCents";
      return {
        ...current,
        settings: {
          ...settings,
          [priceKey]: priceCents,
          commerceProducts: syncSubscriptionProductPrice(settings, productId, priceCents)
        }
      };
    });
  }

  async function createBackup() {
    const data = await apiRequest("/api/admin/system/backup", { method: "POST", body: {} });
    setOverview({ ...overview, system: data.system });
    setMessage(`資料庫備份已建立：${data.backup?.name || ""}`);
  }

  async function runMigration() {
    const data = await apiRequest("/api/admin/system/migrate", { method: "POST", body: {} });
    setOverview({ ...overview, system: data.system });
    setMessage(data.migration?.applied?.length ? `資料庫遷移完成：${data.migration.applied.join("、")}` : "資料庫已是最新版本");
  }

  async function grantMembership(userId) {
    await apiRequest("/api/admin/subscriptions", { method: "POST", body: { userId, days: 30, plan: "monthly" } });
    await refreshOverview();
    setMessage("已為用戶開通 30 天會員");
  }

  async function stopMembership(subscriptionId) {
    await apiRequest(`/api/admin/subscriptions/${encodeURIComponent(subscriptionId)}`, { method: "PUT", body: { status: "canceled" } });
    await refreshOverview();
    setMessage("會員已停用");
  }

  async function updateMasterQuestion(questionId, patch) {
    await apiRequest(`/api/admin/master-questions/${encodeURIComponent(questionId)}`, { method: "PUT", body: patch });
    await refreshOverview();
    setMessage("諮詢回覆已保存");
  }

  async function deleteMasterQuestion(questionId) {
    await apiRequest(`/api/admin/master-questions/${encodeURIComponent(questionId)}`, { method: "DELETE" });
    await refreshOverview();
    setMessage("諮詢記錄已刪除");
  }

  async function updateOrder(orderId, patch) {
    await apiRequest(`/api/admin/orders/${encodeURIComponent(orderId)}`, { method: "PUT", body: patch });
    await refreshOverview();
    setMessage("訂單已保存");
  }

  async function deleteOrder(orderId) {
    await apiRequest(`/api/admin/orders/${encodeURIComponent(orderId)}`, { method: "DELETE" });
    await refreshOverview();
    setMessage("訂單已刪除");
  }

  async function deleteRun(runId) {
    await apiRequest(`/api/admin/runs/${encodeURIComponent(runId)}`, { method: "DELETE" });
    await refreshOverview();
    setMessage("測試記錄已刪除");
  }

  async function updateReminder(reminderId, patch) {
    await apiRequest(`/api/admin/reminders/${encodeURIComponent(reminderId)}`, { method: "PUT", body: patch });
    await refreshOverview();
    setMessage("提醒已保存");
  }

  async function deleteReminder(reminderId) {
    await apiRequest(`/api/admin/reminders/${encodeURIComponent(reminderId)}`, { method: "DELETE" });
    await refreshOverview();
    setMessage("提醒已刪除");
  }

  const adminNav = [
    { id: "dashboard", label: "營運總覽", icon: Crown },
    { id: "users", label: "用戶會員", icon: UserRound },
    { id: "tests", label: "測試管理", icon: MapPinned },
    { id: "master", label: "大師解答", icon: Sparkles },
    { id: "consultationPrompt", label: "命理諮詢報告", icon: MessageCircle },
    { id: "dailyPrompt", label: "每日玄學提醒", icon: BellRing },
    { id: "records", label: "訂單記錄", icon: Coins },
    { id: "commerce", label: "支付接口", icon: Coins },
    { id: "ai", label: "AI 與 API", icon: BrainCircuit },
    { id: "legal", label: "法務內容", icon: BadgeCheck },
    { id: "settings", label: "網站設定", icon: ShipWheel },
    { id: "system", label: "系統更新", icon: BadgeCheck }
  ];
  const selectedTest = tests.find((item) => item.id === editingTestId);

  return (
    <>
    <WorkspaceFrame
      title="船長後台"
      subtitle="管理員維護測試項目、會員訂單、會員內容提示詞和模型 API。"
      badge={<><Crown className="h-4 w-4" /> 管理後台</>}
      navItems={adminNav}
      activeTab={activeAdminTab}
      onTabChange={setActiveAdminTab}
    >

      {message && <div className="mb-5 rounded-2xl border border-cyan-200/15 bg-cyan-300/10 p-4 text-sm text-cyan-100">{message}</div>}

      {activeAdminTab === "dashboard" && overview && (
        <div className="mb-5 grid gap-4 md:grid-cols-4">
          {[
            ["用戶數", overview.stats.users],
            ["會員數", overview.stats.activeMembers],
            ["營收", formatMoneyCents(overview.stats.revenueCents || 0, overview.settings?.currencySymbol || activeMarket.currencySymbol, overview.settings?.locale || activeMarket.locale)],
            ["測試記錄", overview.stats.testRuns]
          ].map(([label, value]) => (
            <div key={label} className="rounded-3xl border border-white/10 bg-white/[0.06] p-5 backdrop-blur">
              <div className="text-sm text-slate-400">{label}</div>
              <div className="mt-2 text-3xl font-semibold text-white">{value}</div>
            </div>
          ))}
        </div>
      )}

      <div className="grid gap-5">
        {activeAdminTab === "commerce" && overview?.settings && (
          <div className="grid gap-5">
            <Panel
              title="支付接口狀態"
              description="先用 mock 模式驗證轉化漏斗；切 live 前必須配置 Checkout URL、Webhook Secret，並用測試訂單確認自動履約。"
              action={<button onClick={() => updateSettings(overview.settings)} className="h-11 rounded-full bg-cyan-300 px-5 text-sm font-semibold text-slate-950">保存支付配置</button>}
            >
              <div className="grid gap-4 lg:grid-cols-3">
                {[
                  ["付款模式", overview.system?.payment?.mode || overview.settings.paymentMode || "mock"],
                  ["啟用服務", overview.system?.payment?.providerName || "本機測試付款"],
                  ["Webhook URL", overview.system?.payment?.webhookUrl || "/api/billing/webhook/mock"]
                ].map(([label, value]) => (
                  <div key={label} className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
                    <div className="text-xs text-white/40">{label}</div>
                    <div className="mt-2 break-all text-sm font-semibold text-white">{value}</div>
                  </div>
                ))}
              </div>
              <div className={cx("mt-4 rounded-2xl border p-4 text-sm leading-6", overview.system?.payment?.ready ? "border-emerald-300/20 bg-emerald-400/10 text-emerald-100" : "border-amber-300/20 bg-amber-400/10 text-amber-100")}>
                {overview.system?.payment?.ready ? "支付接口已具備上線條件。" : `待處理：${(overview.system?.payment?.blockers || ["mock 模式可先驗證"]).join("；")}`}
              </div>
            </Panel>

            <Panel title="商品與付款服務" description="商品欄位後三個 ID 用於後續接入 Lemon Squeezy/Paddle 的商品、變體或價格 ID。">
              <div className="grid gap-4">
                <div>
                  <div className="mb-2 text-sm font-semibold text-cyan-100">付款基礎</div>
                  <div className="grid gap-3 lg:grid-cols-4">
                    <select
                      value={overview.settings.paymentMode || "mock"}
                      onChange={(event) => setOverview({ ...overview, settings: { ...overview.settings, paymentMode: event.target.value } })}
                      className="h-12 rounded-2xl border border-white/10 bg-slate-950/70 px-4 text-sm text-white outline-none"
                    >
                      <option value="mock">mock 本機測試</option>
                      <option value="live">live 真實支付</option>
                    </select>
                    <input
                      value={overview.settings.publicBaseUrl || ""}
                      onChange={(event) => setOverview({ ...overview, settings: { ...overview.settings, publicBaseUrl: event.target.value } })}
                      placeholder="正式域名，例如 https://fatemind.ai"
                      className="h-12 rounded-2xl border border-white/10 bg-white/[0.06] px-4 text-sm text-white outline-none placeholder:text-white/30 lg:col-span-3"
                    />
                    <input
                      value={overview.settings.successUrl || ""}
                      onChange={(event) => setOverview({ ...overview, settings: { ...overview.settings, successUrl: event.target.value } })}
                      placeholder="付款成功返回路徑"
                      className="h-12 rounded-2xl border border-white/10 bg-white/[0.06] px-4 text-sm text-white outline-none placeholder:text-white/30 lg:col-span-2"
                    />
                    <input
                      value={overview.settings.cancelUrl || ""}
                      onChange={(event) => setOverview({ ...overview, settings: { ...overview.settings, cancelUrl: event.target.value } })}
                      placeholder="取消付款返回路徑"
                      className="h-12 rounded-2xl border border-white/10 bg-white/[0.06] px-4 text-sm text-white outline-none placeholder:text-white/30 lg:col-span-2"
                    />
                  </div>
                </div>
                <textarea
                  value={productsToText(overview.settings.commerceProducts || [])}
                  onChange={(event) => setOverview({ ...overview, settings: { ...overview.settings, commerceProducts: productsFromText(event.target.value) } })}
                  placeholder="商品配置：id｜type｜name｜priceCents｜durationDays｜credits｜status｜providerProductId｜providerVariantId｜providerPriceId｜description"
                  className="min-h-36 rounded-2xl border border-white/10 bg-white/[0.06] p-4 text-sm leading-6 text-white outline-none placeholder:text-white/30"
                />
                <textarea
                  value={paymentProvidersToText(overview.settings.paymentProviders || [])}
                  onChange={(event) => setOverview({ ...overview, settings: { ...overview.settings, paymentProviders: paymentProvidersFromText(event.target.value) } })}
                  placeholder="付款服務：id｜name｜on/off｜test/live｜checkoutUrl｜successUrl｜cancelUrl｜webhookSecret｜webhookToleranceSeconds｜notes"
                  className="min-h-36 rounded-2xl border border-white/10 bg-white/[0.06] p-4 text-sm leading-6 text-white outline-none placeholder:text-white/30"
                />
              </div>
            </Panel>

            <Panel title="最近支付事件" description="Webhook 入庫後會在這裡看到事件，系統用 providerEventId 去重，避免重複履約。">
              <div className="grid gap-3 lg:grid-cols-2">
                {(overview.system?.database?.counts?.paymentEvents || 0) === 0 && <div className="text-sm text-slate-400">暫無支付事件。正式接入後，成功付款、訂閱取消、退款等事件會入庫。</div>}
                {(overview.orders || []).slice(0, 8).map((order) => (
                  <div key={order.id} className="rounded-2xl border border-white/10 bg-slate-950/25 p-4 text-sm text-slate-300">
                    <div className="font-semibold text-white">{orderProductLabel(order)} · {formatOrderMoney(order, overview.settings)}</div>
                    <div className="mt-2 text-xs text-slate-400">{order.id} · {order.provider || "mock"} · {order.status}</div>
                  </div>
                ))}
              </div>
            </Panel>
          </div>
        )}

        {activeAdminTab === "system" && overview?.system && (
          <div className="grid gap-5">
            <Panel
              title="版本與資料庫更新"
              description="代碼更新和資料庫更新分開處理：先備份，再部署，再跑遷移。這樣可以安全地把本機 MVP 推到正式環境。"
              action={
                <div className="flex flex-wrap gap-2">
                  <button onClick={createBackup} className="h-11 rounded-full border border-white/15 bg-white/5 px-5 text-sm font-semibold text-white transition hover:bg-white/10">建立備份</button>
                  <button onClick={runMigration} className="h-11 rounded-full bg-cyan-300 px-5 text-sm font-semibold text-slate-950">執行遷移</button>
                </div>
              }
            >
              <div className="grid gap-4 md:grid-cols-4">
                {[
                  ["App 版本", overview.system.appVersion],
                  ["資料庫版本", `${overview.system.schemaVersion}/${overview.system.targetSchemaVersion}`],
                  ["訂單數", overview.system.database?.counts?.orders || 0],
                  ["支付事件", overview.system.database?.counts?.paymentEvents || 0]
                ].map(([label, value]) => (
                  <div key={label} className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
                    <div className="text-xs text-white/40">{label}</div>
                    <div className="mt-2 text-lg font-semibold text-white">{value}</div>
                  </div>
                ))}
              </div>
              <div className="mt-5 grid gap-4 lg:grid-cols-2">
                <div className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
                  <div className="mb-3 text-sm font-semibold text-cyan-100">更新流程</div>
                  {(overview.system.updateChecklist || []).map((item) => (
                    <div key={item} className="mb-2 flex gap-2 text-sm leading-6 text-slate-300">
                      <BadgeCheck className="mt-1 h-4 w-4 shrink-0 text-cyan-200" />
                      {item}
                    </div>
                  ))}
                </div>
                <div className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
                  <div className="mb-3 text-sm font-semibold text-cyan-100">最近備份</div>
                  {(overview.system.database?.backups || []).map((backup) => (
                    <div key={backup.name} className="border-t border-white/10 py-2 text-xs text-slate-400">
                      <div className="break-all text-white">{backup.name}</div>
                      <div>{new Date(backup.createdAt).toLocaleString()} · {(backup.size / 1024).toFixed(1)} KB</div>
                    </div>
                  ))}
                  {!(overview.system.database?.backups || []).length && <div className="text-sm text-slate-400">暫無備份。</div>}
                </div>
              </div>
            </Panel>

            <Panel title="遷移記錄" description="每次資料庫結構調整都會記錄在 migrationHistory，後續部署到伺服器時可追蹤是否已執行。">
              <div className="space-y-2">
                {(overview.system.migrations || []).slice(-10).reverse().map((migration, index) => (
                  <div key={`${migration.id}-${index}`} className="rounded-2xl border border-white/10 bg-slate-950/25 p-4 text-sm text-slate-300">
                    <div className="font-semibold text-white">{migration.id}</div>
                    <div className="mt-1 text-xs text-slate-500">{migration.appVersion || "--"} · {migration.appliedAt ? new Date(migration.appliedAt).toLocaleString() : "--"}</div>
                    {migration.backup && <div className="mt-1 text-xs text-cyan-100/70">備份：{migration.backup}</div>}
                  </div>
                ))}
              </div>
            </Panel>
          </div>
        )}

        {activeAdminTab === "settings" && overview?.settings && (
          <Panel
            title="網站設定"
            description="這裡只保留日常營運最常改的內容。支付 API、Webhook、模型 API 請到左側對應欄目設定。"
            action={<button onClick={() => updateSettings(overview.settings)} className="h-11 rounded-full bg-cyan-300 px-5 text-sm font-semibold text-slate-950">保存網站設定</button>}
          >
            <div className="grid gap-4 xl:grid-cols-2">
              <AdminEditField title="品牌名稱" description="前台和會員頁顯示的產品名稱。">
                <AdminTextInput value={overview.settings.brandName || ""} onChange={(value) => updateSettingValue("brandName", value)} placeholder="费曼" />
              </AdminEditField>

              <AdminEditField title="首頁市場標籤" description="例如：台灣首發驗證版。">
                <AdminTextInput value={overview.settings.launchTag || ""} onChange={(value) => updateSettingValue("launchTag", value)} placeholder="台灣首發驗證版" />
              </AdminEditField>

              <AdminEditField title="目標市場名稱" description="目前建議保持台灣市場，不要在前台使用容易引起誤解的地域文案。">
                <AdminTextInput value={overview.settings.marketName || ""} onChange={(value) => updateSettingValue("marketName", value)} placeholder="台灣首發市場" />
              </AdminEditField>

              <AdminEditField title="免費體驗次數" description="普通遊客可免費完成幾次測試。">
                <AdminTextInput value={overview.settings.freeTestLimit ?? 1} onChange={(value) => updateSettingValue("freeTestLimit", Number(value || 0))} inputMode="numeric" placeholder="1" />
              </AdminEditField>

              <AdminEditField title="月付會員價格" description="直接輸入新台幣金額，系統會同步到會員方案和商品價格。">
                <div className="grid gap-3 sm:grid-cols-[90px_1fr] sm:items-center">
                  <div className="rounded-2xl border border-cyan-200/15 bg-cyan-300/10 px-4 py-3 text-sm font-semibold text-cyan-100">NT$</div>
                  <AdminTextInput value={centsToAdminAmount(overview.settings.monthlyPriceCents || activeMarket.monthlyPriceCents)} onChange={(value) => updateSubscriptionPrice("monthly", value)} inputMode="numeric" placeholder="299" />
                </div>
              </AdminEditField>

              <AdminEditField title="年付會員價格" description="建議年付比月付 x 12 更優惠，用於提高留存和預收款效率。">
                <div className="grid gap-3 sm:grid-cols-[90px_1fr] sm:items-center">
                  <div className="rounded-2xl border border-cyan-200/15 bg-cyan-300/10 px-4 py-3 text-sm font-semibold text-cyan-100">NT$</div>
                  <AdminTextInput value={centsToAdminAmount(overview.settings.annualPriceCents || activeMarket.annualPriceCents)} onChange={(value) => updateSubscriptionPrice("annual", value)} inputMode="numeric" placeholder="2990" />
                </div>
              </AdminEditField>

              <AdminEditField title="會員權益文案" description="每行一條，會影響會員說明與付費轉化文案。">
                <AdminTextarea value={(overview.settings.memberBenefits || []).join("\n")} onChange={(value) => updateSettingValue("memberBenefits", value.split("\n").map((item) => item.trim()).filter(Boolean))} placeholder="完整命理報告&#10;每日玄學提醒&#10;24 小時 AI 玄學大師專屬解答" />
              </AdminEditField>

              <AdminEditField title="首發市場備註" description="每行一條，用於後台記錄目前市場策略。">
                <AdminTextarea value={(overview.settings.launchNotes || []).join("\n")} onChange={(value) => updateSettingValue("launchNotes", value.split("\n").map((item) => item.trim()).filter(Boolean))} placeholder="繁體中文優先&#10;新台幣計價&#10;信用卡與跨境收款先行" />
              </AdminEditField>
            </div>

            <div className="mt-5 rounded-2xl border border-cyan-200/15 bg-cyan-300/10 p-4 text-sm leading-6 text-cyan-100">
              <div className="font-semibold text-white">目前前台價格</div>
              <div className="mt-2">月付：{formatMoneyCents(overview.settings.monthlyPriceCents || activeMarket.monthlyPriceCents, overview.settings.currencySymbol || activeMarket.currencySymbol, overview.settings.locale || activeMarket.locale)}</div>
              <div>年付：{formatMoneyCents(overview.settings.annualPriceCents || activeMarket.annualPriceCents, overview.settings.currencySymbol || activeMarket.currencySymbol, overview.settings.locale || activeMarket.locale)}</div>
            </div>
          </Panel>
        )}

        {activeAdminTab === "legal" && overview?.settings && (
          <Panel
            title="法務內容"
            description="上線前可先準備免費聲明、服務條款、隱私政策與退款政策。這些內容會顯示在前台 Footer 連結頁。"
            action={<button onClick={() => updateSettings(overview.settings)} className="h-11 rounded-full bg-cyan-300 px-5 text-sm font-semibold text-slate-950">保存法務內容</button>}
          >
            <div className="grid gap-5">
              {legalDocumentOptions.map(([key, label]) => {
                const legalDocuments = overview.settings.legalDocuments || {};
                const document = legalDocuments[key] || {};
                const updateLegalDocument = (patch) => updateSettingValue("legalDocuments", {
                  ...legalDocuments,
                  [key]: { ...document, ...patch }
                });
                return (
                  <div key={key} className="rounded-3xl border border-white/10 bg-slate-950/25 p-5">
                    <div className="mb-4 flex flex-col justify-between gap-3 md:flex-row md:items-start">
                      <div>
                        <div className="text-lg font-semibold text-white">{label}</div>
                        <div className="mt-1 text-xs text-slate-500">前台路徑：/preview.html#legal-{key}</div>
                      </div>
                      <a href={`/preview.html#legal-${key}`} target="_blank" className="inline-flex h-10 items-center justify-center rounded-full border border-white/15 bg-white/5 px-4 text-xs font-semibold text-white transition hover:bg-white/10">
                        預覽頁面
                      </a>
                    </div>
                    <div className="grid gap-4 md:grid-cols-[1fr_180px]">
                      <AdminEditField title="頁面標題">
                        <AdminTextInput value={document.title || label} onChange={(value) => updateLegalDocument({ title: value })} placeholder={label} />
                      </AdminEditField>
                      <AdminEditField title="更新日期">
                        <AdminTextInput value={document.updatedAt || "2026-06-07"} onChange={(value) => updateLegalDocument({ updatedAt: value })} placeholder="2026-06-07" />
                      </AdminEditField>
                    </div>
                    <div className="mt-4">
                      <AdminEditField title="正文內容" description="使用自然段即可，前台會保留換行。正式上線前建議由法律或合規顧問確認。">
                        <AdminTextarea value={document.content || ""} onChange={(value) => updateLegalDocument({ content: value })} minHeight="min-h-56" placeholder="請輸入法務內容..." />
                      </AdminEditField>
                    </div>
                  </div>
                );
              })}
            </div>
          </Panel>
        )}

        {activeAdminTab === "users" && overview && (
          <div className="rounded-3xl border border-white/10 bg-white/[0.06] p-5 backdrop-blur">
            <h3 className="text-xl font-semibold text-white">用戶與會員管理</h3>
            <div className="mt-5 grid gap-4 lg:grid-cols-2">
              {overview.users.map((userItem) => (
                <div key={userItem.id} className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
                  <div className="grid gap-3">
                    <div>
                      <div className="text-sm font-semibold text-white">{userItem.email}</div>
                      <div className="mt-1 text-xs text-slate-500">註冊：{new Date(userItem.createdAt).toLocaleString()}</div>
                    </div>
                    <div className="grid gap-3 sm:grid-cols-2">
                      <input value={userItem.name} onChange={(event) => setOverview({ ...overview, users: overview.users.map((item) => (item.id === userItem.id ? { ...item, name: event.target.value } : item)) })} className="h-11 rounded-2xl border border-white/10 bg-white/[0.06] px-4 text-sm text-white outline-none" />
                      <select value={userItem.role} onChange={(event) => setOverview({ ...overview, users: overview.users.map((item) => (item.id === userItem.id ? { ...item, role: event.target.value } : item)) })} className="h-11 rounded-2xl border border-white/10 bg-slate-950/70 px-4 text-sm text-white outline-none">
                        <option value="user">user</option>
                        <option value="admin">admin</option>
                      </select>
                    </div>
                    <div className="grid gap-2 sm:grid-cols-3">
                      <button onClick={() => updateAdminUser(userItem.id, { name: userItem.name, role: userItem.role })} className="h-10 rounded-full border border-white/15 bg-white/5 px-4 text-sm font-semibold text-white transition hover:bg-white/10">保存</button>
                      <button onClick={() => grantMembership(userItem.id)} className="h-10 rounded-full bg-cyan-300 px-4 text-sm font-semibold text-slate-950">開通會員</button>
                      <button disabled={!userItem.membership?.id} onClick={() => stopMembership(userItem.membership.id)} className="h-10 rounded-full border border-rose-300/20 bg-rose-400/10 px-4 text-sm font-semibold text-rose-100 transition hover:bg-rose-400/20 disabled:opacity-40">停用會員</button>
                    </div>
                    <button disabled={userItem.id === currentUser.id} onClick={() => deleteAdminUser(userItem.id)} className="h-10 rounded-full border border-rose-300/20 bg-rose-400/10 px-4 text-sm font-semibold text-rose-100 transition hover:bg-rose-400/20 disabled:cursor-not-allowed disabled:opacity-40">
                      刪除用戶及關聯數據
                    </button>
                    <div className="grid grid-cols-2 gap-2 text-xs text-slate-400">
                      <div>會員：{userItem.membership.status}</div>
                      <div>測試：{userItem.runsCount}</div>
                    </div>
                  </div>
                </div>
              ))}
            </div>
          </div>
        )}

        {activeAdminTab === "master" && overview && (
          <div className="rounded-3xl border border-white/10 bg-white/[0.06] p-5 backdrop-blur">
            <h3 className="text-xl font-semibold text-white">專屬解答管理</h3>
            <div className="mt-5 grid gap-4 lg:grid-cols-2">
              {(overview.masterQuestions || []).map((item) => (
                <div key={item.id} className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
                  <div className="text-sm font-semibold text-white">{item.question}</div>
                  <textarea value={item.answer || ""} onChange={(event) => setOverview({ ...overview, masterQuestions: overview.masterQuestions.map((question) => (question.id === item.id ? { ...question, answer: event.target.value } : question)) })} className="mt-3 min-h-24 w-full rounded-2xl border border-white/10 bg-white/[0.06] p-4 text-sm leading-6 text-white outline-none" />
                  <div className="mt-3 grid gap-2 sm:grid-cols-2">
                    <select value={item.status} onChange={(event) => setOverview({ ...overview, masterQuestions: overview.masterQuestions.map((question) => (question.id === item.id ? { ...question, status: event.target.value } : question)) })} className="h-10 rounded-2xl border border-white/10 bg-slate-950/70 px-4 text-sm text-white outline-none">
                      <option value="queued">queued</option>
                      <option value="answered">answered</option>
                      <option value="closed">closed</option>
                    </select>
                    <button onClick={() => updateMasterQuestion(item.id, { answer: item.answer, status: item.status })} className="h-10 rounded-full bg-cyan-300 px-4 text-sm font-semibold text-slate-950">保存回覆</button>
                  </div>
                  <button onClick={() => deleteMasterQuestion(item.id)} className="mt-3 h-10 w-full rounded-full border border-rose-300/20 bg-rose-400/10 px-4 text-sm font-semibold text-rose-100 transition hover:bg-rose-400/20">刪除諮詢</button>
                </div>
              ))}
              {!(overview.masterQuestions || []).length && <div className="text-sm text-slate-400">暫無大師諮詢。</div>}
            </div>
          </div>
        )}

        {activeAdminTab === "consultationPrompt" && (
          <AdminPromptEditor
            title="命理諮詢報告"
            description="會員在「大師在線」提問時，系統會讀取這裡的提示詞生成一對一命理諮詢內容。"
            prompt={prompts["master-consultation-report"] || {}}
            onChange={(prompt) => setPrompts({ ...prompts, "master-consultation-report": prompt })}
            onSave={() => updatePrompt("master-consultation-report", prompts["master-consultation-report"] || {})}
            userPlaceholder={"根據會員問題、歷史測試記錄與命盤資訊，先給明確結論，再解釋命理依據，最後輸出可執行建議。請固定使用台灣繁體中文與台灣常用語感，避免恐嚇和絕對化判斷。"}
          />
        )}

        {activeAdminTab === "dailyPrompt" && (
          <AdminPromptEditor
            title="每日玄學提醒"
            description="會員中心的每日提醒會讀取這裡的提示詞，生成今日節奏、風險提醒與開運貼士。"
            prompt={prompts["daily-reminder"] || {}}
            onChange={(prompt) => setPrompts({ ...prompts, "daily-reminder": prompt })}
            onSave={() => updatePrompt("daily-reminder", prompts["daily-reminder"] || {})}
            userPlaceholder={"根據會員命盤、近期測試記錄與今日日期，生成 80-120 字繁體中文提醒。包含今日主題、需要留意的風險、適合做的事、不適合做的事與一條開運貼士。"}
          />
        )}

        {activeAdminTab === "records" && overview && (
          <div className="rounded-3xl border border-white/10 bg-white/[0.06] p-5 backdrop-blur">
            <h3 className="text-xl font-semibold text-white">營運記錄</h3>
            <div className="mt-5 grid gap-4 lg:grid-cols-2">
              <div className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
                <div className="mb-3 text-sm font-semibold text-cyan-100">訂單</div>
                {(overview.orders || []).slice(0, 5).map((order) => (
                  <div key={order.id} className="border-t border-white/10 py-2 text-xs text-slate-400">
                    <div>{order.id} · {formatOrderMoney(order, overview.settings)} · {orderProductLabel(order)}</div>
                    <div className="mt-2 grid grid-cols-[1fr_auto] gap-2">
	                      <select value={order.status} onChange={(event) => setOverview({ ...overview, orders: overview.orders.map((item) => (item.id === order.id ? { ...item, status: event.target.value } : item)) })} className="h-8 rounded-xl border border-white/10 bg-slate-950/70 px-2 text-xs text-white outline-none">
	                        <option value="pending">pending</option>
	                        <option value="paid">paid</option>
	                        <option value="paid_mock">paid_mock</option>
	                        <option value="fulfilled">fulfilled</option>
	                        <option value="refunded">refunded</option>
	                        <option value="closed">closed</option>
	                      </select>
                      <button onClick={() => updateOrder(order.id, { status: order.status })} className="rounded-full bg-white px-3 text-xs font-semibold text-slate-950">保存</button>
                    </div>
                    <button onClick={() => deleteOrder(order.id)} className="mt-2 text-rose-200 hover:text-rose-100">刪除訂單</button>
                  </div>
                ))}
              </div>
              <div className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
                <div className="mb-3 text-sm font-semibold text-cyan-100">測試記錄</div>
                {(overview.runs || []).slice(0, 5).map((run) => (
                  <div key={run.id} className="border-t border-white/10 py-2 text-xs text-slate-400">
                    <button onClick={() => setSelectedAdminRecord({ type: "run", item: run })} className="text-left text-cyan-100 hover:text-cyan-50">
                      {run.testTitle || run.testId} · {new Date(run.createdAt).toLocaleString()}
                    </button>
                    <div className="mt-1 line-clamp-2">{run.summary}</div>
                    <button onClick={() => deleteRun(run.id)} className="mt-2 text-rose-200 hover:text-rose-100">刪除記錄</button>
                  </div>
                ))}
              </div>
            </div>
            <div className="mt-4 rounded-2xl border border-white/10 bg-slate-950/25 p-4">
              <div className="mb-3 text-sm font-semibold text-cyan-100">每日提醒管理</div>
              <div className="grid gap-3 lg:grid-cols-2">
                {(overview.reminders || []).slice(0, 6).map((item) => (
                  <div key={item.id} className="rounded-2xl border border-white/10 bg-white/[0.04] p-3">
                    <div className="mb-2 text-xs text-white/45">{item.date} · {item.userId}</div>
                    <textarea value={item.content} onChange={(event) => setOverview({ ...overview, reminders: overview.reminders.map((reminder) => (reminder.id === item.id ? { ...reminder, content: event.target.value } : reminder)) })} className="min-h-20 w-full rounded-xl border border-white/10 bg-slate-950/60 p-3 text-xs leading-5 text-white outline-none" />
                    <div className="mt-2 grid grid-cols-2 gap-2">
                      <button onClick={() => updateReminder(item.id, { content: item.content })} className="h-9 rounded-full bg-white text-xs font-semibold text-slate-950">保存</button>
                      <button onClick={() => deleteReminder(item.id)} className="h-9 rounded-full border border-rose-300/20 bg-rose-400/10 text-xs font-semibold text-rose-100">刪除</button>
                    </div>
                  </div>
                ))}
                {!(overview.reminders || []).length && <div className="text-sm text-slate-400">暫無提醒。</div>}
              </div>
            </div>
          </div>
        )}

        {activeAdminTab === "tests" && (
          <div className="grid gap-5 xl:grid-cols-[minmax(0,1fr)_430px]">
            <div className="rounded-3xl border border-white/10 bg-white/[0.06] p-5 backdrop-blur">
              <div className="flex flex-col justify-between gap-4 md:flex-row md:items-center">
                <div>
                  <h3 className="text-xl font-semibold text-white">測試管理</h3>
                  <p className="mt-2 text-sm leading-6 text-slate-400">以列表方式管理資料庫中的測試項目。現在開放營運最常用的五項：測試標題、簡介、輸出報告提示詞、測試分類、推薦指數。</p>
                </div>
                <button onClick={startCreateTest} className="inline-flex h-11 shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-full bg-cyan-300 px-5 text-sm font-semibold text-slate-950 transition hover:bg-cyan-200">
                  <Sparkles className="h-4 w-4" />
                  新增
                </button>
              </div>

              <div className="mt-5 overflow-hidden rounded-2xl border border-white/10 bg-slate-950/25">
                <div className="hidden grid-cols-[minmax(0,1fr)_120px] border-b border-white/10 px-4 py-3 text-xs font-medium text-white/45 md:grid">
                  <div>測試內容</div>
                  <div className="text-right">操作</div>
                </div>
                <div className="divide-y divide-white/10">
                  {tests.map((test) => {
                    const active = editingTestId === test.id;
                    return (
                      <div key={test.id} className={cx("grid gap-4 px-4 py-5 transition md:grid-cols-[minmax(0,1fr)_120px] md:items-center", active ? "bg-cyan-300/10" : "hover:bg-white/[0.035]")}>
                        <div className="min-w-0">
                          <div className="flex flex-wrap items-center gap-2">
                            <div className="truncate text-base font-semibold text-white">{test.title || "未命名測試"}</div>
                            <span className="rounded-full bg-cyan-300/10 px-2.5 py-1 text-xs text-cyan-100">{test.category || "未分類"}</span>
                            <span className="rounded-full bg-white/10 px-2.5 py-1 text-xs text-white/60">推薦 {test.recommendationPriority || 0}</span>
                          </div>
                          <div className="mt-3 line-clamp-2 text-sm leading-6 text-slate-400">{test.desc || "尚未填寫簡介"}</div>
                          <div className="mt-3 flex flex-wrap gap-x-4 gap-y-1 text-xs text-white/40">
                            <span>ID：{test.id}</span>
                            <span>提示詞：{test.reportPrompt ? "已配置" : "未配置"}</span>
                          </div>
                        </div>
                        <div className="flex flex-wrap justify-start gap-2 md:justify-end">
                          <button onClick={() => startEditTest(test)} className="inline-flex h-9 items-center gap-1.5 rounded-full border border-white/15 bg-white/5 px-3 text-xs font-semibold text-white transition hover:bg-white/10">
                            <Pencil className="h-3.5 w-3.5" />
                            修改
                          </button>
                          <button onClick={() => deleteTest(test.id)} className="inline-flex h-9 items-center gap-1.5 rounded-full border border-rose-300/20 bg-rose-400/10 px-3 text-xs font-semibold text-rose-100 transition hover:bg-rose-400/20">
                            <Trash2 className="h-3.5 w-3.5" />
                            刪除
                          </button>
                        </div>
                      </div>
                    );
                  })}
                  {!tests.length && (
                    <div className="px-4 py-10 text-center text-sm text-slate-400">目前還沒有測試項目。</div>
                  )}
                </div>
              </div>
            </div>

            <div className="rounded-3xl border border-cyan-200/15 bg-cyan-300/10 p-5 backdrop-blur xl:sticky xl:top-24 xl:self-start">
              <div className="mb-5 flex items-start justify-between gap-4">
                <div>
                  <div className="text-xs font-medium text-cyan-100/70">{isCreatingTest ? "新增資料庫測試" : selectedTest ? `正在修改：${editingTestId}` : "尚未選擇測試"}</div>
                  <h3 className="mt-1 text-xl font-semibold text-white">{isCreatingTest ? "新增測試內容" : "測試營運內容"}</h3>
                  {selectedTest && <p className="mt-2 text-sm leading-6 text-cyan-50/70">{selectedTest.title}</p>}
                </div>
                {isCreatingTest && (
                  <button onClick={() => { setIsCreatingTest(false); setTestDraft(createAdminTestDraft()); }} className="rounded-full border border-white/15 bg-white/5 px-3 py-1.5 text-xs font-semibold text-white transition hover:bg-white/10">
                    取消新增
                  </button>
                )}
              </div>

              {selectedTest || isCreatingTest ? (
                <div className="grid gap-3">
                  <label className="grid gap-2 text-xs font-medium text-cyan-50/65">
                    測試標題
                    <input value={testDraft.title} onChange={(event) => updateTestDraft("title", event.target.value)} placeholder="例如：我到底適合創業還是打工？" className="h-11 rounded-2xl border border-white/10 bg-slate-950/35 px-4 text-sm font-semibold text-white outline-none placeholder:text-white/30" />
                  </label>
                  <div className="grid gap-3 sm:grid-cols-2">
                    <label className="grid gap-2 text-xs font-medium text-cyan-50/65">
                      測試分類
                      <input value={testDraft.category} onChange={(event) => updateTestDraft("category", event.target.value)} placeholder="例如：發展、事業、關係" className="h-11 rounded-2xl border border-white/10 bg-slate-950/35 px-4 text-sm text-white outline-none placeholder:text-white/30" />
                    </label>
                    <label className="grid gap-2 text-xs font-medium text-cyan-50/65">
                      推薦指數
                      <input value={testDraft.recommendationPriority} onChange={(event) => updateTestDraft("recommendationPriority", Number(event.target.value || 0))} placeholder="數值越大越優先" inputMode="numeric" className="h-11 rounded-2xl border border-white/10 bg-slate-950/35 px-4 text-sm text-white outline-none placeholder:text-white/30" />
                    </label>
                  </div>
                  <label className="grid gap-2 text-xs font-medium text-cyan-50/65">
                    簡介
                    <textarea value={testDraft.desc} onChange={(event) => updateTestDraft("desc", event.target.value)} placeholder="前台測試卡片上的一句話介紹" className="min-h-28 rounded-2xl border border-white/10 bg-slate-950/35 p-4 text-sm leading-6 text-white outline-none placeholder:text-white/30" />
                  </label>
                  <label className="grid gap-2 text-xs font-medium text-cyan-50/65">
                    輸出報告提示詞
                    <textarea value={testDraft.reportPrompt} onChange={(event) => updateTestDraft("reportPrompt", event.target.value)} placeholder="定義這個測試生成報告時的分析角度、輸出結構、語氣、需要引用的命盤資訊與風險邊界。" className="min-h-56 rounded-2xl border border-cyan-200/20 bg-slate-950/45 p-4 text-sm leading-6 text-white outline-none placeholder:text-cyan-50/35" />
                    <span className="text-xs leading-5 text-cyan-50/45">
                      新增測試時請在提示詞裡寫清楚：要直接判斷什麼、需要哪些章節、要看哪些年份/風險/建議。前台報告會按這份提示詞生成，不需要再改程式。
                    </span>
                  </label>
                  <button onClick={saveTestDraft} className="mt-2 h-[50px] rounded-full bg-cyan-300 text-sm font-semibold text-slate-950 transition hover:bg-cyan-200">
                    {isCreatingTest ? "新增到資料庫" : "保存到資料庫"}
                  </button>
                </div>
              ) : (
                <div className="rounded-2xl border border-white/10 bg-slate-950/35 p-5 text-sm leading-6 text-slate-300">
                  請先在左側選擇一個測試，或點擊「新增測試」建立新的測試入口。
                </div>
              )}
            </div>
          </div>
        )}

        {activeAdminTab === "ai" && <div className="rounded-3xl border border-white/10 bg-white/[0.06] p-5 backdrop-blur">
          <h3 className="text-xl font-semibold text-white">AI 提示詞</h3>
          {overview?.settings && (
            <div className="mt-5 rounded-2xl border border-cyan-200/15 bg-cyan-300/10 p-4">
              <div className="mb-3 text-sm font-semibold text-cyan-100">模型 API 配置</div>
              <p className="mb-4 text-xs leading-5 text-cyan-50/55">
                啟用並填入 API Key 後，前台測試報告會優先由大模型按「測試管理」裡的輸出提示詞生成；模型超時或失敗時，系統會自動使用本地兜底報告。
              </p>
              <div className="grid gap-3 lg:grid-cols-[220px_1fr_auto]">
                <select
                  value={overview.settings.activeAiProvider || "openai"}
                  onChange={(event) => setOverview({ ...overview, settings: { ...overview.settings, activeAiProvider: event.target.value } })}
                  className="h-11 rounded-2xl border border-white/10 bg-slate-950/70 px-4 text-sm text-white outline-none"
                >
                  {(overview.settings.aiProviders || []).map((provider) => <option key={provider.id} value={provider.id}>{provider.name}</option>)}
                </select>
                <button onClick={() => updateSettings(overview.settings)} className="h-11 rounded-full bg-cyan-300 px-5 text-sm font-semibold text-slate-950">保存目前模型配置</button>
                <button
                  onClick={testAiConnection}
                  disabled={aiTestState.loading}
                  className="h-11 rounded-full border border-cyan-200/30 bg-white/[0.06] px-5 text-sm font-semibold text-cyan-50 transition hover:bg-white/[0.1] disabled:cursor-not-allowed disabled:opacity-60"
                >
                  {aiTestState.loading ? "測試中..." : "測試連線"}
                </button>
              </div>
              {aiTestState.result && (
                <div className={cx("mt-4 rounded-2xl border p-4 text-sm leading-6", aiTestState.result.ok ? "border-emerald-300/20 bg-emerald-300/10 text-emerald-50" : "border-rose-300/25 bg-rose-400/10 text-rose-50")}>
                  <div className="font-semibold">{aiTestState.result.ok ? "連線成功" : "連線失敗"}</div>
                  <div className="mt-1 text-white/70">{aiTestState.result.message}</div>
                  {aiTestState.result.ok && (
                    <div className="mt-2 text-xs text-white/45">
                      {aiTestState.result.providerName || aiTestState.result.provider} · {aiTestState.result.model || "未指定模型"}
                    </div>
                  )}
                </div>
              )}
              <div className="mt-4 grid gap-4">
                {(overview.settings.aiProviders || []).map((provider, index) => (
                  <div key={provider.id} className="rounded-2xl border border-white/10 bg-slate-950/30 p-4">
                    <div className="mb-3 flex flex-col justify-between gap-3 sm:flex-row sm:items-center">
                      <div className="text-sm font-semibold text-white">{provider.name}</div>
                      <label className="flex items-center gap-2 text-xs text-slate-300">
                        <input
                          type="checkbox"
                          checked={Boolean(provider.enabled)}
                          onChange={(event) => {
                            const aiProviders = [...(overview.settings.aiProviders || [])];
                            aiProviders[index] = { ...provider, enabled: event.target.checked };
                            setOverview({ ...overview, settings: { ...overview.settings, aiProviders } });
                          }}
                          className="h-4 w-4 accent-cyan-300"
                        />
                        啟用
                      </label>
                    </div>
                    <div className="grid gap-3 lg:grid-cols-2">
                      {[
                        ["baseUrl", "API Base URL"],
                        ["model", "模型名稱，例如 gpt-4.1-mini / gemini-1.5-pro"],
                        ["apiKey", "API Key，服務端加密保存，公開介面會隱藏"],
                        ["notes", "用途備註"]
                      ].map(([key, placeholder]) => (
                        <input
                          key={key}
                          value={provider[key] || ""}
                          onChange={(event) => {
                            const aiProviders = [...(overview.settings.aiProviders || [])];
                            aiProviders[index] = { ...provider, [key]: event.target.value };
                            setOverview({ ...overview, settings: { ...overview.settings, aiProviders } });
                          }}
                          placeholder={placeholder}
                          type={key === "apiKey" ? "password" : "text"}
                          className="h-11 rounded-2xl border border-white/10 bg-white/[0.06] px-4 text-sm text-white outline-none placeholder:text-white/30"
                        />
                      ))}
                    </div>
                  </div>
                ))}
              </div>
              <div className="mt-4 rounded-2xl border border-amber-300/20 bg-amber-400/10 p-4">
                <div className="mb-2 text-sm font-semibold text-amber-100">AI 成本控制</div>
                <div className="mb-3 grid gap-2 text-xs leading-5 text-amber-50/70 sm:grid-cols-3">
                  <div>今日總調用：{overview.system?.aiUsage?.total ?? 0}</div>
                  <div>今日降級 / 失敗：{overview.system?.aiUsage?.failed ?? 0}</div>
                  <div>日期：{overview.system?.aiUsage?.today || "--"}</div>
                </div>
                <div className="mb-3 flex flex-wrap gap-2">
                  {Object.entries(overview.system?.aiUsage?.byFeature || {}).map(([feature, count]) => (
                    <span key={feature} className="rounded-full bg-white/10 px-3 py-1 text-xs text-amber-50">{feature}：{count}</span>
                  ))}
                </div>
                <textarea
                  value={JSON.stringify(overview.settings.aiCostControls || {}, null, 2)}
                  onChange={(event) => {
                    try {
                      const parsed = JSON.parse(event.target.value || "{}");
                      setOverview({ ...overview, settings: { ...overview.settings, aiCostControls: parsed } });
                    } catch {
                      setOverview({ ...overview, settings: { ...overview.settings, aiCostControlsDraft: event.target.value } });
                    }
                  }}
                  onBlur={(event) => {
                    try {
                      const parsed = JSON.parse(event.target.value || "{}");
                      setOverview({ ...overview, settings: { ...overview.settings, aiCostControls: parsed } });
                    } catch {
                      setMessage("AI 成本控制 JSON 格式錯誤，請檢查逗號和括號。");
                    }
                  }}
                  className="min-h-64 w-full rounded-2xl border border-white/10 bg-slate-950/45 p-4 font-mono text-xs leading-5 text-white outline-none placeholder:text-white/30"
                />
                <div className="mt-3 flex justify-end">
                  <button onClick={() => updateSettings(overview.settings)} className="h-10 rounded-full bg-white px-5 text-sm font-semibold text-slate-950">保存成本策略</button>
                </div>
              </div>
            </div>
          )}
          <div className="mt-5 space-y-5">
            {Object.entries(prompts)
              .filter(([key]) => !["master-consultation-report", "daily-reminder"].includes(key))
              .sort(([keyA], [keyB]) => {
                const order = {};
                return (order[keyA] ?? 9) - (order[keyB] ?? 9) || keyA.localeCompare(keyB);
              })
              .map(([key, prompt]) => (
              <div key={key} className="rounded-2xl border border-white/10 bg-slate-950/25 p-4">
                <div className="mb-3 text-sm font-semibold text-cyan-100">
                  {key === "master-consultation-report" ? "命理諮詢報告 / 大師在線提示詞" : key === "daily-reminder" ? "每日玄學提醒提示詞" : key}
                </div>
                <textarea
                  value={prompt.system}
                  onChange={(event) => setPrompts({ ...prompts, [key]: { ...prompt, system: event.target.value } })}
                  placeholder="System Prompt"
                  className="min-h-28 w-full rounded-2xl border border-white/10 bg-white/[0.06] p-4 text-sm leading-6 text-white outline-none"
                />
                <textarea
                  value={prompt.userTemplate || ""}
                  onChange={(event) => setPrompts({ ...prompts, [key]: { ...prompt, userTemplate: event.target.value } })}
                  placeholder="User Template / 報告生成結構"
                  className="mt-3 min-h-24 w-full rounded-2xl border border-white/10 bg-white/[0.06] p-4 text-sm leading-6 text-white outline-none placeholder:text-white/30"
                />
                <button onClick={() => updatePrompt(key, prompt)} className="mt-3 h-10 rounded-full bg-white px-5 text-sm font-semibold text-slate-950">保存提示詞</button>
              </div>
            ))}
            {!Object.entries(prompts).filter(([key]) => !["master-consultation-report", "daily-reminder"].includes(key)).length && (
              <div className="rounded-2xl border border-white/10 bg-slate-950/25 p-4 text-sm leading-6 text-slate-400">
                會員內容提示詞已移到左側「命理諮詢報告」與「每日玄學提醒」。這裡只保留模型 API 和其他系統提示詞。
              </div>
            )}
          </div>
        </div>}
      </div>
      <RecordDetailModal record={selectedAdminRecord} onClose={() => setSelectedAdminRecord(null)} />
    </WorkspaceFrame>
    </>
  );
}

function App() {
  const [loginOpen, setLoginOpen] = useState(false);
  const [paywall, setPaywall] = useState({ open: false, message: "" });
  const [currentUser, setCurrentUser] = useState(getStoredSession()?.user || null);
  const [siteSettings, setSiteSettings] = useState(activeMarket);
  const [routeHash, setRouteHash] = useState(window.location.hash.replace("#", ""));
  const currentPath = window.location.pathname;
  const isAdminRoute = currentPath === "/admin";
  const isMemberRoute = currentPath === "/member";
  const isMembershipRoute = currentPath === "/membership";
  const isMasterRoute = currentPath === "/master" || routeHash === "master";
  const isLegalRoute = currentPath.startsWith("/legal/") || routeHash.startsWith("legal-");

  useEffect(() => {
    const syncHashRoute = () => setRouteHash(window.location.hash.replace("#", ""));
    window.addEventListener("hashchange", syncHashRoute);
    return () => window.removeEventListener("hashchange", syncHashRoute);
  }, []);

  useEffect(() => {
    apiRequest("/api/settings")
      .then((data) => setSiteSettings({ ...activeMarket, ...(data.settings || {}) }))
      .catch(() => setSiteSettings(activeMarket));
    apiRequest("/api/auth/me")
      .then((data) => {
        if (data.user) {
          const stored = getStoredSession();
          setStoredSession({ token: stored?.token, user: data.user });
          setCurrentUser(data.user);
          return;
        }
        setStoredSession(null);
        setCurrentUser(null);
      })
      .catch(() => {});
  }, []);

  useEffect(() => {
    const targetId = isAdminRoute ? "admin" : isMemberRoute ? "member" : isMasterRoute ? "master" : "";
    if (!targetId) return;
    window.setTimeout(() => document.getElementById(targetId)?.scrollIntoView({ behavior: "smooth", block: "start" }), 500);
  }, [currentUser?.role, isAdminRoute, isMemberRoute, isMasterRoute]);

  useEffect(() => {
    if (isAdminRoute && currentUser?.role !== "admin") {
      const timer = window.setTimeout(() => setLoginOpen(true), 700);
      return () => window.clearTimeout(timer);
    }
  }, [currentUser?.role, isAdminRoute]);

  const openPaywall = (message, plan = "monthly") => setPaywall({ open: true, message, plan });
  const handleUserUpdate = (user) => {
    setCurrentUser(user);
    const stored = getStoredSession();
    setStoredSession({ token: stored?.token, user });
  };

  const handleLogout = async () => {
    try {
      await apiRequest("/api/auth/logout", { method: "POST" });
    } catch {
      // 本機會话清理優先，介面失敗也不阻塞退出。
    }
    setStoredSession(null);
    setCurrentUser(null);
    if (isAdminRoute || isMemberRoute || isMasterRoute) window.location.href = "/";
  };

  const mainContent = isAdminRoute ? (
    <>
      <AdminRouteGate currentUser={currentUser} onLogin={() => setLoginOpen(true)} />
      <AdminPanel currentUser={currentUser} />
    </>
  ) : isMemberRoute ? (
    <MemberCenter currentUser={currentUser} onLogin={() => setLoginOpen(true)} onSubscribe={openPaywall} onUserUpdate={handleUserUpdate} onLogout={handleLogout} siteSettings={siteSettings} />
  ) : isMembershipRoute ? (
    <MembershipPage currentUser={currentUser} onLogin={() => setLoginOpen(true)} onSubscribe={openPaywall} onUserUpdate={handleUserUpdate} siteSettings={siteSettings} />
  ) : isMasterRoute ? (
    <MasterOnlinePage currentUser={currentUser} onLogin={() => setLoginOpen(true)} onSubscribe={openPaywall} siteSettings={siteSettings} />
  ) : isLegalRoute ? (
    <LegalPage siteSettings={siteSettings} />
  ) : (
    <>
      <Hero onLogin={() => setLoginOpen(true)} />
      <TestsSection
        currentUser={currentUser}
        onLogin={() => setLoginOpen(true)}
        onSubscribe={openPaywall}
        onUserUpdate={handleUserUpdate}
      />
      <MembershipPage currentUser={currentUser} onLogin={() => setLoginOpen(true)} onSubscribe={openPaywall} onUserUpdate={handleUserUpdate} siteSettings={siteSettings} />
    </>
  );

  return (
    <div className="min-h-screen overflow-hidden bg-[#08111f] text-white">
      <div className="pointer-events-none fixed inset-0 opacity-70">
        <div className="absolute left-[-12%] top-[-18%] h-[520px] w-[520px] rounded-full bg-cyan-500/20 blur-3xl" />
        <div className="absolute right-[-10%] top-[12%] h-[460px] w-[460px] rounded-full bg-violet-500/20 blur-3xl" />
        <div className="absolute bottom-[-10%] left-[25%] h-[480px] w-[480px] rounded-full bg-blue-500/20 blur-3xl" />
      </div>
      <div className="pointer-events-none fixed inset-0 bg-[radial-gradient(circle_at_50%_0%,rgba(255,255,255,.08),transparent_42rem)]" />

      <Header currentUser={currentUser} onLogin={() => setLoginOpen(true)} onLogout={handleLogout} />
      <main className="relative z-10">
        {mainContent}
      </main>
      <Footer />
      <LoginModal open={loginOpen} onClose={() => setLoginOpen(false)} onAuthenticated={handleUserUpdate} />
      <PaywallModal
        open={paywall.open}
        message={paywall.message}
        initialPlan={paywall.plan}
        currentUser={currentUser}
        siteSettings={siteSettings}
        onClose={() => setPaywall({ open: false, message: "" })}
        onLogin={() => setLoginOpen(true)}
        onSubscribed={(user) => {
          setCurrentUser(user);
          const stored = getStoredSession();
          setStoredSession({ token: stored?.token, user });
        }}
      />
    </div>
  );
}

createRoot(document.getElementById("root")).render(<App />);
