// Auth + MyPage + Cart pages
const { useState: useStateA, useEffect: useEffectA } = React;

const AUTH_API_BASE = window.JAFUN_AUTH_API_BASE || window.JAFUN_QUOTE_API_BASE || 'http://localhost:3001';
const AUTH_TOKEN_KEY = 'jafun-auth-token';
const AUTH_USER_KEY = 'jafun-auth-user';
const AUTH_EXPIRES_KEY = 'jafun-auth-expires-at';
const AUTH_STORED_AT_KEY = 'jafun-auth-stored-at';
const AUTH_LOGOUT_AT_KEY = 'jafun-auth-logout-at';
const AUTH_SESSION_MEMORY_KEY = '__JAFUN_AUTH_SESSION_MEMORY__';
const TAIWAN_CITIES = ['台北市', '新北市', '桃園市', '台中市', '台南市', '高雄市', '基隆市', '新竹市', '嘉義市', '新竹縣', '苗栗縣', '彰化縣', '南投縣', '雲林縣', '嘉義縣', '屏東縣', '宜蘭縣', '花蓮縣', '台東縣', '澎湖縣', '金門縣', '連江縣'];
const MEMBER_PAGE_TABS = [
  ['orders', '我的訂單'],
  ['subscriptions', '訂閱管理'],
  ['purchases', '代購紀錄'],
  ['shipments', '合併出貨'],
  ['fav', '收藏商品'],
  ['address', '收件資料'],
  ['settings', '帳戶設定'],
];

function normalizeMemberPageTab(tab) {
  return MEMBER_PAGE_TABS.some(([key]) => key === tab) ? tab : 'orders';
}

async function authRequest(path, { method = 'GET', body, token } = {}) {
  const headers = { 'Content-Type': 'application/json' };
  if (token) headers.Authorization = `Bearer ${token}`;
  let response;
  try {
    response = await fetch(`${AUTH_API_BASE}${path}`, {
      method,
      headers,
      body: body ? JSON.stringify(body) : undefined,
    });
  } catch (error) {
    throw new Error(toTraditionalAuthError(error?.message, '目前無法連線到會員服務，請稍後再試。'));
  }
  if (!response.ok) {
    let message = '目前無法處理會員操作，請稍後再試。';
    try {
      const payload = await response.json();
      message = Array.isArray(payload.message) ? payload.message.join('、') : (payload.message || message);
    } catch (error) {
      message = await response.text() || message;
    }
    throw new Error(toTraditionalAuthError(message));
  }
  return response.json();
}

function toTraditionalAuthError(message, fallback = '目前無法處理會員操作，請稍後再試。') {
  const text = String(message || '').trim();
  if (!text) return fallback;
  if (/Failed to fetch|NetworkError|Load failed|fetch failed/i.test(text)) {
    return '目前無法連線到會員服務，請稍後再試。';
  }
  if (/Unexpected token|JSON/i.test(text)) {
    return '會員服務回應格式異常，請稍後再試。';
  }
  if (/Email 或密碼不正確|帳號或密碼|invalid (email or )?password|invalid credentials/i.test(text)) {
    return '帳號或密碼錯誤，請重新輸入。';
  }
  if (/Unauthorized|Forbidden|401|403/i.test(text)) {
    return '登入狀態已失效，請重新登入。';
  }
  const validationMap = [
    [/recipientName/i, '請填寫收件人姓名。'],
    [/phone/i, '請填寫正確的手機號碼。'],
    [/postalCode/i, '請填寫正確的郵遞區號。'],
    [/city/i, '請選擇縣市。'],
    [/district/i, '請確認區域欄位。'],
    [/addressLine/i, '請填寫詳細地址。'],
    [/isDefault/i, '預設地址設定格式不正確。'],
    [/email/i, '請輸入有效的 Email。'],
    [/password/i, '請確認密碼格式。'],
    [/must be|should not|is not|must contain|longer than|shorter than|boolean|string/i, fallback],
  ];
  const matched = validationMap.find(([pattern]) => pattern.test(text));
  if (matched) return matched[1];
  return text;
}

// 運費計算「單一來源」：購物車一律呼叫報價頁的 window.taiwanAirShippingEstimate(pages.jsx),
// 避免報價頁與購物車各算各的而金額不一致；比照報價邏輯先加 0.1kg 包裝緩衝再計費。
const cartAirShippingTWD = (weightKg) =>
  Number(window.taiwanAirShippingEstimate((Number(weightKg) || 0) + 0.1).internationalShippingEstimateTWD) || 0;

function cartEffectiveQuantity(item) {
  const qty = Math.max(1, Number(item?.qty) || 1);
  if (item?.quotePricing?.quantity) {
    return Math.max(1, Number(item.quotePricing.quantity) || 1) * qty;
  }
  return qty;
}

function cartQuotePricingForDisplay(item) {
  const pricing = item?.quotePricing;
  if (!pricing) return null;
  const effectiveQuantity = cartEffectiveQuantity(item);
  const baseQuantity = Math.max(1, Number(pricing.quantity) || 1);
  const unitPriceJPY = Math.max(1, Number(pricing.unitPriceJPY || 0));
  const exchangeRate = Number(pricing.exchangeRate || 0.222);
  const itemSubtotalJPY = unitPriceJPY * effectiveQuantity;
  const itemSubtotalTWD = Math.round(itemSubtotalJPY * exchangeRate);
  const serviceFeeRate = Number(pricing.serviceFeeRate || 0.08);
  const serviceFeeMinimumTWD = Math.max(0, Number(pricing.serviceFeeMinimumTWD || 80));
  const serviceFeeTWD = Math.max(serviceFeeMinimumTWD, Math.round(itemSubtotalTWD * serviceFeeRate));
  const baseWeightKg = Number(pricing.internationalShippingEstimatedWeightKg || pricing.internationalShippingBillableWeightKg || 0);
  const shippingWeightKg = baseWeightKg ? (baseWeightKg / baseQuantity) * effectiveQuantity : 0;
  const internationalShippingEstimateTWD = shippingWeightKg
    ? cartAirShippingTWD(shippingWeightKg)
    : Math.round(Number(pricing.internationalShippingEstimateTWD || 0) * Math.max(1, Number(item?.qty) || 1));
  return {
    itemSubtotalTWD,
    serviceFeeTWD,
    internationalShippingEstimateTWD,
    totalTWD: itemSubtotalTWD + serviceFeeTWD + internationalShippingEstimateTWD,
  };
}

function cartLineSubtotal(item) {
  const quote = cartQuotePricingForDisplay(item);
  if (quote) return quote.itemSubtotalTWD;
  return Math.max(0, Number(item?.price) || 0) * Math.max(1, Number(item?.qty) || 1);
}

function cartItemDisplayTotal(item) {
  return cartLineSubtotal(item);
}

function cartTotals(items) {
  let subtotalTWD = 0;
  let serviceFeeTWD = 0;
  let quoteShippingTWD = 0;
  let catalogFeeBaseTWD = 0;
  let catalogWeightKg = 0;
  let extraShippingTWD = 0;

  (items || []).forEach(item => {
    const quote = cartQuotePricingForDisplay(item);
    if (quote) {
      subtotalTWD += quote.itemSubtotalTWD;
      serviceFeeTWD += quote.serviceFeeTWD;
      quoteShippingTWD += quote.internationalShippingEstimateTWD;
      return;
    }
    const lineSubtotal = cartLineSubtotal(item);
    const quantity = Math.max(1, Number(item?.qty) || 1);
    subtotalTWD += lineSubtotal;
    catalogFeeBaseTWD += item?.feeIncluded ? 0 : lineSubtotal;
    if (!item?.shippingIncluded) {
      catalogWeightKg += Math.max(0.1, Number(item?.weightKg || 1)) * quantity;
      extraShippingTWD += Math.max(0, Math.round(Number(item?.extraShippingTWD || 0))) * quantity;
    }
  });

  if (catalogFeeBaseTWD > 0) {
    serviceFeeTWD += Math.round(catalogFeeBaseTWD * 0.08);
  }

  const catalogShippingTWD = catalogWeightKg > 0 ? cartAirShippingTWD(catalogWeightKg) : 0;
  const shippingTWD = quoteShippingTWD + catalogShippingTWD + extraShippingTWD;

  return {
    subtotalTWD,
    serviceFeeTWD,
    shippingTWD,
    baseTotalTWD: subtotalTWD + serviceFeeTWD + shippingTWD,
  };
}

function authStorageTargets() {
  const targets = [];
  try {
    if (window.localStorage) targets.push(window.localStorage);
  } catch (error) {}
  try {
    if (window.sessionStorage) targets.push(window.sessionStorage);
  } catch (error) {}
  return targets;
}

function removeAuthSessionFromStorage(storage) {
  try {
    storage.removeItem(AUTH_TOKEN_KEY);
    storage.removeItem(AUTH_USER_KEY);
    storage.removeItem(AUTH_EXPIRES_KEY);
    storage.removeItem(AUTH_STORED_AT_KEY);
  } catch (error) {}
}

function authLogoutAt() {
  try {
    return Number(window.localStorage?.getItem(AUTH_LOGOUT_AT_KEY) || 0) || 0;
  } catch (error) {
    return 0;
  }
}

function normalizeAuthSession(token, user, expiresAt = '', storedAt = '') {
  const isBackendToken = /^[a-f0-9]{64}$/i.test(token || '');
  const isKnownUserShape = Boolean(user?.id && user?.memberCode && user?.email);
  const isExpired = expiresAt ? Date.parse(expiresAt) <= Date.now() : false;
  const sessionStoredAt = Number(storedAt) || 0;
  const logoutAt = authLogoutAt();
  if (!token || !user || !isBackendToken || !isKnownUserShape || isExpired) return null;
  if (user.provider === 'line' && user.email === 'line-dev@jafun.local') return null;
  if (logoutAt && sessionStoredAt <= logoutAt) return null;
  return { token, user, expiresAt, storedAt: storedAt || '' };
}

function readAuthSessionFromStorage(storage) {
  try {
    const token = storage.getItem(AUTH_TOKEN_KEY);
    const user = JSON.parse(storage.getItem(AUTH_USER_KEY) || 'null');
    const expiresAt = storage.getItem(AUTH_EXPIRES_KEY) || '';
    const storedAt = storage.getItem(AUTH_STORED_AT_KEY) || '';
    const session = normalizeAuthSession(token, user, expiresAt, storedAt);
    if (!session) removeAuthSessionFromStorage(storage);
    return session;
  } catch (error) {
    removeAuthSessionFromStorage(storage);
    return null;
  }
}

function storeAuthSession(auth) {
  const storedAt = auth?.storedAt || String(Math.max(Date.now(), authLogoutAt() + 1));
  const session = normalizeAuthSession(auth?.token, auth?.user, auth?.expiresAt || '', storedAt);
  if (!session) return;
  let stored = false;
  authStorageTargets().forEach(storage => {
    try {
      storage.setItem(AUTH_TOKEN_KEY, session.token);
      storage.setItem(AUTH_USER_KEY, JSON.stringify(session.user));
      storage.setItem(AUTH_EXPIRES_KEY, session.expiresAt || '');
      storage.setItem(AUTH_STORED_AT_KEY, session.storedAt || storedAt);
      stored = true;
    } catch (error) {}
  });
  window[AUTH_SESSION_MEMORY_KEY] = session;
  if (!stored) {
    console.warn('JaFun member session is kept in this tab only because browser storage is unavailable.');
  }
}

function readAuthSession() {
  for (const storage of authStorageTargets()) {
    const session = readAuthSessionFromStorage(storage);
    if (session) {
      window[AUTH_SESSION_MEMORY_KEY] = session;
      return session;
    }
  }
  const memorySession = normalizeAuthSession(
    window[AUTH_SESSION_MEMORY_KEY]?.token,
    window[AUTH_SESSION_MEMORY_KEY]?.user,
    window[AUTH_SESSION_MEMORY_KEY]?.expiresAt || '',
    window[AUTH_SESSION_MEMORY_KEY]?.storedAt || '',
  );
  if (memorySession) return memorySession;
  clearAuthSession();
  return null;
}

function clearAuthSession(options = {}) {
  authStorageTargets().forEach(removeAuthSessionFromStorage);
  window[AUTH_SESSION_MEMORY_KEY] = null;
  if (options.broadcast) {
    try {
      window.localStorage?.setItem(AUTH_LOGOUT_AT_KEY, String(Date.now()));
    } catch (error) {}
  }
}

function memberInitial(user) {
  return (user?.name || user?.email || 'J').trim().slice(0, 1).toUpperCase();
}

function formatTwd(value) {
  return `NT$ ${Number(value || 0).toLocaleString()}`;
}

function formatMemberDate(value) {
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return '';
  return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`;
}

function formatMemberSubscriptionDate(value) {
  return formatMemberDate(value) || '待通知';
}

const ORDER_SHIPPING_STEPS = [
  { key: 'ordered', label: '已下單' },
  { key: 'japan_warehouse_received', label: '日本倉庫收貨' },
  { key: 'packed', label: '集貨打包' },
  { key: 'air_shipping', label: '空運配送中' },
  { key: 'taiwan_delivery', label: '台灣配送中' },
  { key: 'delivered', label: '台灣送達' },
];

function shippingStatusFromOrder(order = {}) {
  if (order.status === 'delivered') return 'delivered';
  if (order.status === 'shipped') return 'air_shipping';
  return 'ordered';
}

function normalizeOrderShipping(order = {}) {
  const shipping = order.shipping || {};
  const validStatus = ORDER_SHIPPING_STEPS.some(step => step.key === shipping.status);
  return {
    status: validStatus ? shipping.status : shippingStatusFromOrder(order),
    trackingNumber: shipping.trackingNumber || '',
    carrier: shipping.carrier || '',
    packageCount: shipping.packageCount || '',
    weightKg: shipping.weightKg || '',
    shippedAt: shipping.shippedAt || '',
    deliveredAt: shipping.deliveredAt || '',
    updatedAt: shipping.updatedAt || order.updatedAt || order.createdAt,
    note: shipping.note || '',
  };
}

function shippingStepDate(order, shipping, stepKey) {
  if (stepKey === 'ordered') return formatMemberDate(order.createdAt);
  if (stepKey === 'air_shipping' && shipping.shippedAt) return formatMemberDate(shipping.shippedAt);
  if (stepKey === 'delivered' && shipping.deliveredAt) return formatMemberDate(shipping.deliveredAt);
  return stepKey === shipping.status ? formatMemberDate(shipping.updatedAt) : '';
}

const PAYMENT_SUCCESS_KEY = 'jafun-last-payment-success';
const MEMBER_SUBSCRIPTION_PLANS = [
  { months: 12, label: '最划算', sub: '12 個月方案', monthly: 1380, save: 1440, total: 16560 },
  { months: 6, label: '熱門', sub: '6 個月方案', monthly: 1420, save: 480, total: 8520 },
  { months: 3, label: '短期', sub: '3 個月方案', monthly: 1450, save: 150, total: 4350 },
  { months: 1, label: '嘗鮮', sub: '1 個月方案', monthly: 1500, save: 0, total: 1500 },
];

function readPaymentSuccess() {
  try {
    const parsed = JSON.parse(window.sessionStorage.getItem(PAYMENT_SUCCESS_KEY) || 'null');
    if (!parsed?.orderNumber || !parsed?.expiresAt || Date.parse(parsed.expiresAt) < Date.now()) {
      window.sessionStorage.removeItem(PAYMENT_SUCCESS_KEY);
      return null;
    }
    return parsed;
  } catch (error) {
    window.sessionStorage.removeItem(PAYMENT_SUCCESS_KEY);
    return null;
  }
}

function storePaymentSuccess(result) {
  if (!result?.orderNumber) return;
  const expiresAt = new Date(Date.now() + 2 * 60 * 60 * 1000).toISOString();
  window.sessionStorage.setItem(PAYMENT_SUCCESS_KEY, JSON.stringify({ ...result, expiresAt }));
}

function clearPaymentSuccess() {
  window.sessionStorage.removeItem(PAYMENT_SUCCESS_KEY);
}

function paymentSuccessFromOrder(order) {
  if (!order?.orderNumber) return null;
  return {
    orderNumber: order.orderNumber,
    orderId: order.id,
    orderType: order.orderType || (order.userId ? 'member' : 'guest'),
    status: order.status || 'paid',
    paymentStatus: order.paymentStatus || 'paid',
    totalTWD: order.totals?.totalTWD || order.totalTWD || 0,
    contactEmail: order.contactEmail || order.recipient?.email || '',
    recipientName: order.recipient?.name || '',
    itemCount: Array.isArray(order.items) ? order.items.reduce((sum, item) => sum + Number(item.quantity || 0), 0) : 0,
    firstItemName: order.items?.[0]?.name || 'JaFun 訂單',
    confirmedAt: new Date().toISOString(),
  };
}

function EmptyMemberState({ title, desc, action }) {
  return (
    <div style={{ background: '#fff', border: '1px solid var(--jf-line)', padding: 34, textAlign: 'center' }}>
      <div style={{ fontFamily: 'var(--serif)', fontSize: 20, letterSpacing: 2, marginBottom: 8 }}>{title}</div>
      <div style={{ fontFamily: 'var(--serif)', fontSize: 14, color: 'var(--jf-mute-2)', lineHeight: 1.9, marginBottom: action ? 22 : 0 }}>{desc}</div>
      {action}
    </div>
  );
}

function MemberPanelNotice({ children, tone = 'mute' }) {
  return (
    <div style={{ background: 'var(--jf-paper-2)', border: '1px solid var(--jf-line)', padding: 18, color: tone === 'error' ? 'var(--jf-red)' : 'var(--jf-mute-2)', fontFamily: 'var(--serif)', fontSize: 13, lineHeight: 1.8 }}>
      {children}
    </div>
  );
}

function AuthPage({ t, onNav, mode: initialMode, onAuthSuccess }) {
  const [mode, setMode] = useStateA(initialMode || 'login');
  const isLogin = mode === 'login';
  const isRegister = mode === 'register';
  const isForgot = mode === 'forgot';
  const isReset = mode === 'reset';
  const [name, setName] = useStateA('');
  const [email, setEmail] = useStateA('');
  const [password, setPassword] = useStateA('');
  const [remember, setRemember] = useStateA(true);
  const [resetToken, setResetToken] = useStateA('');
  const [loading, setLoading] = useStateA(false);
  const [message, setMessage] = useStateA('');
  const [error, setError] = useStateA('');

  useEffectA(() => {
    setMode(initialMode || 'login');
  }, [initialMode]);

  useEffectA(() => {
    const hashQuery = window.location.hash.includes('?') ? window.location.hash.slice(window.location.hash.indexOf('?') + 1) : '';
    const params = new URLSearchParams(window.location.search.replace(/^\?/, ''));
    new URLSearchParams(hashQuery).forEach((value, key) => {
      if (!params.has(key)) params.set(key, value);
    });
    const token = params.get('resetToken');
    if (token) {
      setResetToken(token);
      setMode('reset');
    }
  }, []);

  const title = isForgot ? '忘記密碼' : isReset ? '設定新密碼' : isLogin ? '會員登入' : '加入會員';
  const eyebrow = isForgot || isReset ? 'ACCOUNT RECOVERY' : isLogin ? 'WELCOME BACK' : 'JOIN JAFUN';
  const jp = isForgot ? 'パ ス ワ ー ド 再 設 定' : isReset ? '新 し い パ ス ワ ー ド' : isLogin ? 'ロ グ イ ン' : '会員登録';

  const finishAuth = (auth) => {
    storeAuthSession(auth);
    onAuthSuccess?.(auth);
    onNav('mypage');
  };

  const handleLineLogin = async () => {
    setLoading(true);
    setError('');
    setMessage('');
    try {
      const returnUrl = new URL('/mypage', window.location.origin);
      const config = await authRequest(`/auth/line/login-url?returnUrl=${encodeURIComponent(returnUrl.toString())}`);
      if (config.configured && config.url) {
        window.location.href = config.url;
        return;
      }
      if (config.devLoginEndpoint) {
        const auth = await authRequest(config.devLoginEndpoint, {
          method: 'POST',
          body: { displayName: 'LINE 使用者', email: 'line-dev@jafun.local' },
        });
        finishAuth(auth);
        return;
      }
      throw new Error('LINE Login 尚未完成設定，請聯繫管理員。');
    } catch (err) {
      setError(err.message || 'LINE 登入失敗。');
    } finally {
      setLoading(false);
    }
  };

  const handleSubmit = async (event) => {
    event.preventDefault();
    setLoading(true);
    setError('');
    setMessage('');
    try {
      if (isRegister) {
        const auth = await authRequest('/auth/register', {
          method: 'POST',
          body: { name, email, password },
        });
        finishAuth(auth);
      } else if (isLogin) {
        const auth = await authRequest('/auth/login', {
          method: 'POST',
          body: { email, password, remember },
        });
        finishAuth(auth);
      } else if (isForgot) {
        const result = await authRequest('/auth/password/forgot', {
          method: 'POST',
          body: { email },
        });
        setResetToken(result.devResetToken || '');
        setMode('reset');
        setMessage(result.devResetToken ? '本機測試環境已產生重設代碼，請輸入新密碼。' : result.message);
      } else if (isReset) {
        const result = await authRequest('/auth/password/reset', {
          method: 'POST',
          body: { token: resetToken, password },
        });
        setPassword('');
        setMode('login');
        setMessage(result.message || '密碼已更新，請重新登入。');
      }
    } catch (err) {
      setError(err.message || '操作失敗，請稍後再試。');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div data-screen-label={isLogin ? '07 Login' : isRegister ? '08 Register' : '08 Password Recovery'}>
      <section style={{ paddingTop: 120, paddingBottom: 80, minHeight: '100vh', background: 'var(--jf-paper-2)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <div style={{ width: 460, background: '#fff', padding: 50, boxShadow: '0 30px 60px -20px rgba(0,0,0,.15)' }}>
          <div style={{ textAlign: 'center', marginBottom: 36 }}>
            <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 5, color: 'var(--jf-red)', marginBottom: 12 }}>
              {eyebrow}
            </div>
            <h2 style={{ fontFamily: 'var(--serif)', fontSize: 32, margin: 0, letterSpacing: 2, fontWeight: 500 }}>
              {title}
            </h2>
            <div style={{ fontFamily: 'var(--serif)', fontSize: 12, color: 'var(--jf-mute)', letterSpacing: 4, marginTop: 6 }}>
              {jp}
            </div>
          </div>

          {(isLogin || isRegister) && (
            <>
              <button disabled={loading} style={{ width: '100%', padding: '14px 0', background: '#06c755', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10, fontFamily: 'var(--sans)', fontSize: 14, letterSpacing: 2, fontWeight: 600, marginBottom: 12, opacity: loading ? .65 : 1 }} onClick={handleLineLogin}>
                <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M24 10.314C24 4.943 18.615.572 12 .572S0 4.943 0 10.314c0 4.811 4.27 8.842 10.035 9.608c1.291-.539 6.916-4.078 9.436-6.975C23.176 14.393 24 12.458 24 10.314" /></svg>
                使用 LINE {isLogin ? '登入' : '註冊'}
              </button>

              <div style={{ display: 'flex', alignItems: 'center', gap: 12, margin: '24px 0', color: 'var(--jf-mute)', fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 2 }}>
                <div style={{ flex: 1, height: 1, background: 'var(--jf-line)' }} />
                <span>或使用 EMAIL</span>
                <div style={{ flex: 1, height: 1, background: 'var(--jf-line)' }} />
              </div>
            </>
          )}

          <form onSubmit={handleSubmit}>
          {isRegister && (
            <div style={{ marginBottom: 14 }}>
              <label style={{ display: 'block', fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 2, color: 'var(--jf-mute)', marginBottom: 6 }}>姓名</label>
              <input value={name} onChange={event => setName(event.target.value)} type="text" placeholder="王小明" required style={{ width: '100%', padding: '12px 14px', border: '1px solid var(--jf-line)', fontFamily: 'var(--serif)', fontSize: 14, outline: 'none' }} />
            </div>
          )}
          {(isLogin || isRegister || isForgot) && (
          <div style={{ marginBottom: 14 }}>
            <label style={{ display: 'block', fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 2, color: 'var(--jf-mute)', marginBottom: 6 }}>Email</label>
            <input value={email} onChange={event => setEmail(event.target.value)} type="email" placeholder="you@email.com" required style={{ width: '100%', padding: '12px 14px', border: '1px solid var(--jf-line)', fontFamily: 'var(--serif)', fontSize: 14, outline: 'none' }} />
          </div>
          )}
          {isReset && (
            <div style={{ marginBottom: 14 }}>
              <label style={{ display: 'block', fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 2, color: 'var(--jf-mute)', marginBottom: 6 }}>重設代碼</label>
              <input value={resetToken} onChange={event => setResetToken(event.target.value)} placeholder="reset token" required style={{ width: '100%', padding: '12px 14px', border: '1px solid var(--jf-line)', fontFamily: 'var(--sans)', fontSize: 12, outline: 'none' }} />
            </div>
          )}
          {(isLogin || isRegister || isReset) && (
          <div style={{ marginBottom: 18 }}>
            <label style={{ display: 'block', fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 2, color: 'var(--jf-mute)', marginBottom: 6 }}>密碼</label>
            <input value={password} onChange={event => setPassword(event.target.value)} type="password" placeholder="至少 8 碼" required minLength="8" style={{ width: '100%', padding: '12px 14px', border: '1px solid var(--jf-line)', fontFamily: 'var(--serif)', fontSize: 14, outline: 'none' }} />
          </div>
          )}

          {isLogin && (
            <div style={{ display: 'flex', justifyContent: 'space-between', fontFamily: 'var(--sans)', fontSize: 11, color: 'var(--jf-mute)', marginBottom: 18 }}>
              <label style={{ display: 'flex', gap: 6, alignItems: 'center' }}><input type="checkbox" checked={remember} onChange={event => setRemember(event.target.checked)} /> 記住我</label>
              <a onClick={() => { setMode('forgot'); setError(''); setMessage(''); }} style={{ cursor: 'pointer' }}>忘記密碼？</a>
            </div>
          )}

          {(message || error) && (
            <div style={{ marginBottom: 16, padding: 12, background: 'var(--jf-paper-2)', color: error ? 'var(--jf-red)' : 'var(--jf-mute-2)', fontFamily: 'var(--serif)', fontSize: 13, lineHeight: 1.8 }}>
              {error || message}
            </div>
          )}

          <button type="submit" disabled={loading} className="btn btn-primary" style={{ width: '100%', justifyContent: 'center', padding: '14px 0', opacity: loading ? .7 : 1 }}>
            {loading ? '處理中...' : isForgot ? '寄送重設密碼信' : isReset ? '設定新密碼' : isLogin ? '登入' : '註冊並登入'}
          </button>
          </form>

          <div style={{ textAlign: 'center', marginTop: 24, fontFamily: 'var(--serif)', fontSize: 13, color: 'var(--jf-mute-2)' }}>
            {isForgot || isReset ? '想起密碼了？' : isLogin ? '還沒有帳號？' : '已有帳號？'}
            <a onClick={() => { setMode(isLogin ? 'register' : 'login'); setError(''); setMessage(''); }} style={{ marginLeft: 8, color: 'var(--jf-red)', cursor: 'pointer', borderBottom: '1px solid currentColor' }}>
              {isLogin ? '立即註冊' : '前往登入'}
            </a>
          </div>
        </div>
      </section>
    </div>
  );
}

function MyPage({ t, onNav, authSession, onLogout, onSessionUpdate, initialTab = 'orders' }) {
  const tabs = MEMBER_PAGE_TABS;
  const [account, setAccount] = useStateA(null);
  const [accountLoading, setAccountLoading] = useStateA(Boolean(authSession?.token));
  const [accountError, setAccountError] = useStateA('');
  const [claimOrderNumber, setClaimOrderNumber] = useStateA('');
  const [claimLoading, setClaimLoading] = useStateA(false);
  const [claimNotice, setClaimNotice] = useStateA(null);
  const user = account?.user || authSession?.user;
  const [tab, setTab] = useStateA(() => normalizeMemberPageTab(initialTab));
  const [subscriptionNotice, setSubscriptionNotice] = useStateA(null);
  const [subscriptionActionLoading, setSubscriptionActionLoading] = useStateA('');
  const [cancelTargetSubscription, setCancelTargetSubscription] = useStateA(null);
  const [planChangeOpen, setPlanChangeOpen] = useStateA(false);
  const [selectedPlanMonths, setSelectedPlanMonths] = useStateA(6);
  const [settingsForm, setSettingsForm] = useStateA({ name: '', email: '' });
  const [settingsSaving, setSettingsSaving] = useStateA(false);
  const [settingsNotice, setSettingsNotice] = useStateA('');
  const [settingsError, setSettingsError] = useStateA('');
  const emptyAddressForm = {
    recipientName: user?.name || '',
    phone: '',
    postalCode: '',
    city: '台北市',
    district: '',
    addressLine: '',
    note: '',
    isDefault: true,
  };
  const [addressFormOpen, setAddressFormOpen] = useStateA(false);
  const [editingAddressId, setEditingAddressId] = useStateA('');
  const [addressForm, setAddressForm] = useStateA(emptyAddressForm);
  const [addressSaving, setAddressSaving] = useStateA(false);
  const [addressNotice, setAddressNotice] = useStateA('');
  const [addressError, setAddressError] = useStateA('');
  const [addressDeletingId, setAddressDeletingId] = useStateA('');
  const [localFavorites, setLocalFavorites] = useStateA(() => window.readFavoriteProducts?.() || []);
  const [expandedOrderId, setExpandedOrderId] = useStateA('');
  const [accountRefreshTick, setAccountRefreshTick] = useStateA(0);
  let browserSubscription = null;
  try {
    browserSubscription = JSON.parse(window.sessionStorage.getItem('jafun-subscription-plan') || 'null');
  } catch (error) {
    browserSubscription = null;
  }
  useEffectA(() => {
    setTab(normalizeMemberPageTab(initialTab));
  }, [initialTab]);

  useEffectA(() => {
    const refreshFavorites = () => setLocalFavorites(window.readFavoriteProducts?.() || []);
    window.addEventListener('jafun:favorites-changed', refreshFavorites);
    window.addEventListener('storage', refreshFavorites);
    return () => {
      window.removeEventListener('jafun:favorites-changed', refreshFavorites);
      window.removeEventListener('storage', refreshFavorites);
    };
  }, []);

  useEffectA(() => {
    if (!authSession?.token) return;
    const result = window.completePendingFavoriteProduct?.();
    if (Array.isArray(result?.favorites)) {
      setLocalFavorites(result.favorites);
    }
    // Pull any order placed as a guest into this member account. Show the returned
    // account immediately, and bump the refresh tick so the authoritative account
    // fetch below re-runs after the claim has persisted (avoids a last-writer race).
    let active = true;
    Promise.resolve(window.completePendingGuestOrder?.()).then(claim => {
      if (!active || !claim) return;
      if (claim.account) setAccount(claim.account);
      if (claim.claimed) setAccountRefreshTick(tick => tick + 1);
    });
    return () => { active = false; };
  }, [authSession?.token]);

  useEffectA(() => {
    if (!authSession?.token) {
      setAccount(null);
      setAccountLoading(false);
      setAccountError('');
      return;
    }

    let active = true;
    setAccountLoading(true);
    setAccountError('');
    authRequest('/auth/account', { token: authSession.token })
      .then(payload => {
        if (!active) return;
        setAccount(payload);
        storeAuthSession({
          token: authSession.token,
          user: payload.user,
          expiresAt: authSession.expiresAt,
        });
      })
      .catch(error => {
        if (!active) return;
        setAccountError(error.message || '會員資料讀取失敗。');
      })
      .finally(() => {
        if (active) setAccountLoading(false);
      });

    return () => {
      active = false;
    };
  }, [authSession?.token, accountRefreshTick]);

  useEffectA(() => {
    if (!user) return;
    setSettingsForm({
      name: user.name || '',
      email: user.email || '',
    });
  }, [user?.id, user?.name, user?.email]);

  const refreshAccount = () => {
    if (!authSession?.token) return;
    setAccountLoading(true);
    setAccountError('');
    authRequest('/auth/account', { token: authSession.token })
      .then(payload => setAccount(payload))
      .catch(error => setAccountError(error.message || '會員資料讀取失敗。'))
      .finally(() => setAccountLoading(false));
  };

  const saveSettings = (event) => {
    event.preventDefault();
    if (!authSession?.token) return;
    setSettingsSaving(true);
    setSettingsNotice('');
    setSettingsError('');
    authRequest('/auth/profile', {
      method: 'PATCH',
      token: authSession.token,
      body: {
        name: settingsForm.name,
        email: settingsForm.email,
      },
    })
      .then(payload => {
        setAccount(payload);
        const nextSession = {
          token: authSession.token,
          user: payload.user,
          expiresAt: authSession.expiresAt,
        };
        storeAuthSession(nextSession);
        onSessionUpdate?.(nextSession);
        setSettingsNotice('帳戶資料已更新。');
      })
      .catch(error => setSettingsError(error.message || '帳戶資料更新失敗。'))
      .finally(() => setSettingsSaving(false));
  };

  const addressFormFrom = (address = {}) => ({
    recipientName: address.recipientName || user?.name || '',
    phone: address.phone || '',
    postalCode: address.postalCode || '',
    city: address.city || '台北市',
    district: address.district || '',
    addressLine: address.addressLine || '',
    note: address.note || '',
    isDefault: Boolean(address.isDefault),
  });

  const updateAddressForm = (patch) => {
    setAddressForm(form => ({ ...form, ...patch }));
  };

  const openNewAddressForm = () => {
    setEditingAddressId('');
    setAddressForm({
      recipientName: user?.name || '',
      phone: '',
      postalCode: '',
      city: '台北市',
      district: '',
      addressLine: '',
      note: '',
      isDefault: addresses.length === 0,
    });
    setAddressNotice('');
    setAddressError('');
    setAddressFormOpen(true);
  };

  const openEditAddressForm = (address) => {
    setEditingAddressId(address.id);
    setAddressForm(addressFormFrom(address));
    setAddressNotice('');
    setAddressError('');
    setAddressFormOpen(true);
  };

  const cancelAddressForm = () => {
    setAddressFormOpen(false);
    setEditingAddressId('');
    setAddressError('');
    setAddressNotice('');
  };

  const saveAddress = (event) => {
    event.preventDefault();
    if (!authSession?.token) return;
    setAddressSaving(true);
    setAddressNotice('');
    setAddressError('');
    const path = editingAddressId
      ? `/auth/addresses/${encodeURIComponent(editingAddressId)}`
      : '/auth/addresses';
    authRequest(path, {
      method: editingAddressId ? 'PATCH' : 'POST',
      token: authSession.token,
      body: addressForm,
    })
      .then(payload => {
        setAccount(payload);
        setAddressFormOpen(false);
        setEditingAddressId('');
        setAddressNotice(editingAddressId ? '收件地址已更新。' : '收件地址已新增。');
      })
      .catch(error => setAddressError(error.message || '收件地址儲存失敗，請稍後再試。'))
      .finally(() => setAddressSaving(false));
  };

  const setAddressDefault = (address) => {
    if (!authSession?.token || !address?.id || address.isDefault) return;
    setAddressSaving(true);
    setAddressNotice('');
    setAddressError('');
    authRequest(`/auth/addresses/${encodeURIComponent(address.id)}/default`, {
      method: 'POST',
      token: authSession.token,
    })
      .then(payload => {
        setAccount(payload);
        setAddressNotice('預設收件地址已更新。');
      })
      .catch(error => setAddressError(error.message || '預設地址更新失敗，請稍後再試。'))
      .finally(() => setAddressSaving(false));
  };

  const deleteAddress = (address) => {
    if (!authSession?.token || !address?.id) return;
    const ok = window.confirm(`確定刪除「${address.recipientName || '這筆'}」收件地址嗎？刪除後下次結帳不會再自動帶入。`);
    if (!ok) return;
    setAddressDeletingId(address.id);
    setAddressNotice('');
    setAddressError('');
    authRequest(`/auth/addresses/${encodeURIComponent(address.id)}`, {
      method: 'DELETE',
      token: authSession.token,
    })
      .then(payload => {
        setAccount(payload);
        setAddressNotice('收件地址已刪除。');
      })
      .catch(error => setAddressError(error.message || '收件地址刪除失敗，請稍後再試。'))
      .finally(() => setAddressDeletingId(''));
  };

  const handleSubscriptionPaymentManage = (subscription) => {
    if (!authSession?.token || !subscription?.id) return;
    setSubscriptionActionLoading('portal');
    setSubscriptionNotice(null);
    authRequest('/payments/stripe/subscription-portal', {
      method: 'POST',
      token: authSession.token,
      body: { subscriptionId: subscription.id },
    })
      .then(payload => {
        if (payload.url) {
          window.location.href = payload.url;
          return;
        }
        setSubscriptionNotice({
          tone: 'mute',
          text: payload.message || '這筆訂閱目前沒有可線上管理的付款資料。您仍可在會員中心取消下次續訂，或透過 LINE 官方協助。',
        });
      })
      .catch(error => {
        setSubscriptionNotice({
          tone: 'error',
          text: error.message || '付款管理入口暫時無法開啟，請稍後再試。',
        });
      })
      .finally(() => setSubscriptionActionLoading(''));
  };

  const openSubscriptionPlanChange = (subscription) => {
    setSelectedPlanMonths(subscription?.months && subscription.status !== 'cancelled' ? subscription.months : 6);
    setPlanChangeOpen(open => !open);
    setSubscriptionNotice(null);
    setCancelTargetSubscription(null);
  };

  const checkoutSubscriptionPlanChange = () => {
    if (!authSession?.token || !user) return;
    const plan = MEMBER_SUBSCRIPTION_PLANS.find(item => item.months === selectedPlanMonths);
    if (!plan) return;
    // 只支援升級;降級/取消需退刷,請洽客服。
    if (savedSubscription && plan.months <= savedSubscription.months) {
      setSubscriptionNotice({
        tone: 'error',
        text: '變更方案僅支援升級到更長期的方案；若要改成較短方案或取消，因涉及退刷費用，請洽客服協助。',
      });
      return;
    }

    setSubscriptionActionLoading('change-plan');
    setSubscriptionNotice(null);
    authRequest('/payments/stripe/subscription-checkout-sessions', {
      method: 'POST',
      token: authSession.token,
      body: {
        months: plan.months,
        changeFromSubscriptionId: savedSubscription?.id,
      },
    })
      .then(payload => {
        if (!payload.url) {
          throw new Error('付款頁面建立失敗，請稍後再試。');
        }
        window.sessionStorage.setItem('jafun-subscription-plan', JSON.stringify({
          ...plan,
          ...(payload.plan || {}),
          subscribedAt: new Date().toISOString(),
          paymentStatus: 'pending',
          checkoutMode: 'member',
          memberId: user.id,
          memberLinked: true,
          customerEmail: payload.customerEmail || '',
          stripeSessionId: payload.sessionId,
          changeFromSubscriptionId: savedSubscription?.id,
          changeFromMonths: savedSubscription?.months,
        }));
        window.location.href = payload.url;
      })
      .catch(error => {
        setSubscriptionNotice({
          tone: 'error',
          text: error.message || '變更方案付款頁面建立失敗，請稍後再試。',
        });
      })
      .finally(() => setSubscriptionActionLoading(''));
  };

  const claimGuestOrder = () => {
    const orderNumber = claimOrderNumber.trim();
    if (!authSession?.token || !orderNumber) {
      setClaimNotice({ tone: 'error', text: '請輸入訂單編號。' });
      return;
    }
    setClaimLoading(true);
    setClaimNotice(null);
    authRequest('/auth/orders/claim', {
      method: 'POST',
      token: authSession.token,
      body: { orderNumber },
    })
      .then(payload => {
        if (payload.account) setAccount(payload.account);
        setClaimOrderNumber('');
        setClaimNotice({ tone: 'success', text: `已將訂單 ${payload.order?.orderNumber || orderNumber} 綁定到您的會員帳號。` });
      })
      .catch(error => {
        setClaimNotice({ tone: 'error', text: error.message || '綁定失敗，請確認訂單編號或洽客服。' });
      })
      .finally(() => setClaimLoading(false));
  };

  const handleSubscriptionCancel = (subscription) => {
    if (!subscription?.id) return;
    setCancelTargetSubscription(subscription);
    setSubscriptionNotice({
      tone: 'confirm',
      text: '確定要取消下次續訂嗎？已付款的本期食箱仍會依原進度處理。',
    });
  };

  const confirmSubscriptionCancel = () => {
    const subscription = cancelTargetSubscription;
    if (!authSession?.token || !subscription?.id) return;
    setSubscriptionActionLoading('cancel');
    authRequest(`/auth/subscriptions/${encodeURIComponent(subscription.id)}/cancel`, {
      method: 'POST',
      token: authSession.token,
    })
      .then(payload => {
        if (payload.account) setAccount(payload.account);
        setCancelTargetSubscription(null);
        setSubscriptionNotice({
          tone: 'success',
          text: '已取消下次續訂。已付款的本期食箱仍會依原進度處理。',
        });
      })
      .catch(error => {
        setSubscriptionNotice({
          tone: 'error',
          text: error.message || '取消續訂失敗，請稍後再試。',
        });
      })
      .finally(() => setSubscriptionActionLoading(''));
  };

  if (!user) {
    return (
      <div data-screen-label="09 MyPage">
        <section className="page-hero">
          <div className="crumb">HOME / 會員中心</div>
          <h1>請先登入</h1>
          <div className="jp">マ イ ペ ー ジ — LOGIN REQUIRED</div>
          <p className="desc">登入後可以查看訂單、訂閱、LINE 代購紀錄與收件資料。</p>
          <div style={{ display: 'flex', gap: 12, marginTop: 28 }}>
            <button className="btn btn-primary" onClick={() => onNav('login')}>會員登入</button>
            <button className="btn btn-ghost" onClick={() => onNav('register')}>加入會員</button>
          </div>
        </section>
      </div>
    );
  }

  const summary = account?.summary || { cumulativeSpendTWD: 0, points: 0, activeOrderCount: 0 };
  const orders = account?.orders || [];
  const assistedPurchases = account?.assistedPurchases || [];
  const mergeShipments = (account?.shipments || []).filter(shipment => shipment.type === 'merge_request');
  const favorites = account?.favorites || [];
  const addresses = account?.addresses || [];
  const accountSubscriptions = account?.subscriptions || [];
  const savedSubscription = accountSubscriptions.find(subscription => subscription.status === 'active' && subscription.paymentStatus === 'paid')
    || accountSubscriptions[0]
    || (browserSubscription?.memberLinked && (!browserSubscription.memberId || browserSubscription.memberId === user?.id) ? (browserSubscription.subscription || browserSubscription) : null);
  const favoriteIds = Array.from(new Set([
    ...localFavorites.map(favorite => window.favoriteProductId?.(favorite)).filter(Boolean),
    ...favorites.map(favorite => String(favorite.productId || '').trim()).filter(Boolean),
  ]));
  const favoriteProducts = favoriteIds
    .map(productId => (
      (window.PRODUCTS || []).find(product => String(product.id) === String(productId) || String(product.sku) === String(productId) || String(product.slug) === String(productId))
      || localFavorites.find(product => window.favoriteProductId?.(product) === productId)
    ))
    .filter(Boolean);
  const toggleFavoriteFromMember = (product, nextFavorite) => {
    const result = window.toggleFavoriteProduct?.(product, nextFavorite);
    const productId = result?.productId || window.favoriteProductId?.(product);
    if (productId) {
      setAccount(current => {
        if (!current) return current;
        const currentFavorites = current.favorites || [];
        const exists = currentFavorites.some(favorite => String(favorite.productId) === String(productId));
        return {
          ...current,
          favorites: nextFavorite
            ? (exists ? currentFavorites : [{ userId: user.id, productId, createdAt: new Date().toISOString() }, ...currentFavorites])
            : currentFavorites.filter(favorite => String(favorite.productId) !== String(productId)),
        };
      });
    }
    return result;
  };
  const orderStatusLabel = {
    pending: ['待付款', 'var(--jf-mute-2)'],
    paid: ['已付款', 'var(--jf-red)'],
    processing: ['處理中', 'var(--jf-red)'],
    shipped: ['空運中', 'var(--jf-red)'],
    delivered: ['已送達', 'var(--jf-mute)'],
    cancelled: ['已取消', 'var(--jf-mute)'],
  };

  return (
    <div data-screen-label="09 MyPage">
      <section className="page-hero">
        <div className="crumb">HOME / 會員中心</div>
        <div className="member-hero-card">
          <div className="member-avatar">
            {user.avatarUrl ? <img src={user.avatarUrl} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} /> : memberInitial(user)}
          </div>
          <div className="member-identity">
            <div className="member-code-line">
              <span>會員編號</span>
              <strong>#{user.memberCode}</strong>
            </div>
            <div className="member-profile-meta">
              <span className={user.lineLinked ? 'is-line-linked' : ''}>{user.lineLinked ? '● 已綁定 LINE' : '● 尚未綁定 LINE'}</span>
              <span className="member-profile-email">{user.email}</span>
            </div>
            <div className="member-stat-grid">
              <div className="member-stat-card">
                <span>累積消費</span>
                <strong>{formatTwd(summary.cumulativeSpendTWD)}</strong>
              </div>
              <div className="member-stat-card">
                <span>JaFun 點數</span>
                <strong>{summary.points}</strong>
              </div>
            </div>
          </div>
        </div>
      </section>

      <section className="section section-narrow" style={{ paddingTop: 60 }}>
        <div className="member-layout">
          {/* Sidebar */}
          <aside className="member-sidebar">
            <div className="member-tabs">
              {tabs.map(([k, v]) => (
                <button
                  key={k}
                  type="button"
                  className={`member-tab ${tab === k ? 'is-active' : ''}`}
                  onClick={() => setTab(k)}
                >
                  {v}
                </button>
              ))}
              <button type="button" className="member-tab member-tab-logout" onClick={() => onLogout?.()}>登出</button>
            </div>
          </aside>

          {/* Content */}
          <div className="member-content">
            {accountLoading && (
              <MemberPanelNotice>正在更新會員資料...</MemberPanelNotice>
            )}
            {accountError && (
              <MemberPanelNotice tone="error">{accountError}</MemberPanelNotice>
            )}

            {tab === 'orders' && (
              <>
                <h2 style={{ fontFamily: 'var(--serif)', fontSize: 26, margin: '0 0 24px', letterSpacing: 2 }}>我的訂單</h2>
                <div style={{ marginBottom: 22, padding: '16px 18px', background: 'var(--jf-paper-2)', border: '1px solid var(--jf-line)' }}>
                  <div style={{ fontFamily: 'var(--serif)', fontSize: 14, color: 'var(--jf-ink)', marginBottom: 6 }}>綁定訪客訂單</div>
                  <div style={{ fontFamily: 'var(--serif)', fontSize: 12, color: 'var(--jf-mute-2)', lineHeight: 1.7, marginBottom: 12 }}>
                    曾以訪客身分下單？輸入訂單編號即可綁定到會員帳號（該訂單的 Email 需與你的會員 Email 相同；不同請洽客服）。
                  </div>
                  <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
                    <input
                      value={claimOrderNumber}
                      onChange={e => setClaimOrderNumber(e.target.value)}
                      onKeyDown={e => { if (e.key === 'Enter') claimGuestOrder(); }}
                      placeholder="訂單編號，例如 JF-260622-7340"
                      style={{ flex: '1 1 220px', padding: '10px 12px', border: '1px solid var(--jf-line)', fontFamily: 'var(--serif)', fontSize: 14 }}
                    />
                    <button className="btn btn-primary" disabled={claimLoading} onClick={claimGuestOrder} style={{ padding: '10px 22px', opacity: claimLoading ? .7 : 1 }}>
                      {claimLoading ? '綁定中...' : '綁定訂單'}
                    </button>
                  </div>
                  {claimNotice && (
                    <div style={{ marginTop: 10, fontFamily: 'var(--serif)', fontSize: 13, color: claimNotice.tone === 'success' ? 'var(--jf-ink)' : 'var(--jf-red)' }}>
                      {claimNotice.text}
                    </div>
                  )}
                </div>
                {!accountLoading && orders.length === 0 && (
                  <EmptyMemberState
                    title="尚無訂單"
                    desc="完成商城購物或 LINE 代購結帳後，訂單會顯示在這裡。"
                    action={<button className="btn btn-primary" onClick={() => onNav('shop')}>前往日本商城</button>}
                  />
                )}
                {orders.map(order => {
                  const primaryItem = order.items[0] || {};
                  const status = orderStatusLabel[order.status] || ['處理中', 'var(--jf-red)'];
                  const shipping = normalizeOrderShipping(order);
                  const shippingIndex = ORDER_SHIPPING_STEPS.findIndex(step => step.key === shipping.status);
                  const expanded = expandedOrderId === order.id;
                  return (
                    <div key={order.id} className="member-order-card">
                      <div className="member-order-summary">
                        <img src={primaryItem.image || JAFUN_IMAGE_FALLBACKS[0]} alt="" />
                        <div>
                          <div className="member-order-meta">
                            <span>#{order.orderNumber}</span><span>·</span><span>{formatMemberDate(order.createdAt)}</span>
                            <span style={{ color: status[1], fontWeight: 600 }}>● {status[0]}</span>
                          </div>
                          <div style={{ fontFamily: 'var(--serif)', fontSize: 16, marginBottom: 4 }}>{primaryItem.name || 'JaFun 訂單'} 等 {order.items.length} 件商品</div>
                          <div style={{ fontFamily: 'var(--serif)', fontSize: 18, color: 'var(--jf-red)', fontWeight: 600 }}>{formatTwd(order.totals.totalTWD)}</div>
                        </div>
                        <div className="member-order-actions">
                          <button
                            className="btn btn-ghost"
                            onClick={() => setExpandedOrderId(expanded ? '' : order.id)}
                          >
                            {expanded ? '收合詳情' : '查看詳情'}
                          </button>
                          <button
                            type="button"
                            onClick={() => setExpandedOrderId(expanded ? '' : order.id)}
                            className="member-order-track-link"
                          >
                            {expanded ? '收合物流' : '追蹤物流 →'}
                          </button>
                        </div>
                      </div>
                      {expanded && (
                        <div className="member-order-detail">
                          <div className="member-order-items">
                            {order.items.map((item, index) => (
                              <div key={`${order.id}-${item.cartId || item.sku || index}`} className="member-order-item">
                                <img src={item.image || JAFUN_IMAGE_FALLBACKS[index % JAFUN_IMAGE_FALLBACKS.length]} alt="" />
                                <div>
                                  <strong>{item.name}</strong>
                                  <span>{item.optionSummary || item.sourceLabel || '日本商品'}</span>
                                </div>
                                <em>× {item.quantity}</em>
                              </div>
                            ))}
                          </div>
                          <div className="member-order-logistics">
                            <div className="member-order-logistics-head">
                              <div>
                                <span>物流狀態</span>
                                <strong>{ORDER_SHIPPING_STEPS[Math.max(shippingIndex, 0)]?.label || '已下單'}</strong>
                              </div>
                              <small>後台出貨人員更新後，這裡會同步顯示最新狀態。</small>
                            </div>
                            <div className="member-order-shipping-timeline">
                              {ORDER_SHIPPING_STEPS.map((step, index) => {
                                const done = index < shippingIndex || shipping.status === 'delivered';
                                const current = index === shippingIndex && shipping.status !== 'delivered';
                                return (
                                  <div key={step.key} className={`member-order-shipping-step ${done ? 'done' : ''} ${current ? 'current' : ''}`}>
                                    <i>{index + 1}</i>
                                    <strong>{step.label}</strong>
                                    <span>{shippingStepDate(order, shipping, step.key) || (index > shippingIndex ? '待更新' : '')}</span>
                                  </div>
                                );
                              })}
                            </div>
                            <div className="member-order-shipping-info">
                              <div><span>追蹤碼</span><strong>{shipping.trackingNumber || '尚未提供'}</strong></div>
                              <div><span>物流商</span><strong>{shipping.carrier || 'JaFun 出貨團隊'}</strong></div>
                              <div><span>包裹</span><strong>{shipping.packageCount ? `${shipping.packageCount} 件` : '待確認'}</strong></div>
                              <div><span>重量</span><strong>{shipping.weightKg ? `${shipping.weightKg} kg` : '待確認'}</strong></div>
                            </div>
                            {shipping.note && <div className="member-order-shipping-note">{shipping.note}</div>}
                          </div>
                        </div>
                      )}
                    </div>
                  );
                })}
              </>
            )}

            {tab === 'subscriptions' && (
              <>
                <h2 style={{ fontFamily: 'var(--serif)', fontSize: 26, margin: '0 0 24px', letterSpacing: 2 }}>訂閱管理</h2>
                <div style={{ background: '#fff', border: '1px solid var(--jf-line)', padding: 30 }}>
	                  {savedSubscription ? (
	                    <>
	                      <div style={{ display: 'flex', justifyContent: 'space-between', gap: 24, alignItems: 'flex-start' }}>
	                        <div>
	                          <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 3, color: savedSubscription.status === 'cancelled' ? 'var(--jf-mute-2)' : '#06c755', marginBottom: 8 }}>
                              {savedSubscription.status === 'cancelled' ? '已取消續訂' : '訂閱中'}
                            </div>
	                          <div style={{ fontFamily: 'var(--serif)', fontSize: 22, letterSpacing: 2, marginBottom: 8 }}>{savedSubscription.months} 個月日本伴手禮食箱</div>
	                          <div style={{ fontFamily: 'var(--serif)', fontSize: 14, color: 'var(--jf-mute-2)', lineHeight: 1.9 }}>
	                            總額 NT$ {Number(savedSubscription.totalTWD ?? savedSubscription.total ?? 0).toLocaleString()} · 每月 NT$ {Number(savedSubscription.monthlyTWD ?? savedSubscription.monthly ?? 0).toLocaleString()}<br />
	                            下次截單日 {formatMemberSubscriptionDate(savedSubscription.nextCutoffDate)} · 預計 {formatMemberSubscriptionDate(savedSubscription.nextShipmentDate)} 從日本出貨
	                            {savedSubscription.subscriptionNumber && <><br />訂閱編號 {savedSubscription.subscriptionNumber}</>}
                              {savedSubscription.cancelledAt && <><br />取消時間 {formatMemberSubscriptionDate(savedSubscription.cancelledAt)}</>}
	                          </div>
	                        </div>
	                        <span style={{ padding: '6px 12px', background: 'var(--jf-paper-2)', color: 'var(--jf-red)', fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 2 }}>
                          固定期數
                        </span>
                      </div>
                      <div style={{ marginTop: 24, display: 'flex', gap: 10, flexWrap: 'wrap' }}>
                        <button
                          className="btn btn-primary"
                          disabled={subscriptionActionLoading === 'portal'}
                          onClick={() => handleSubscriptionPaymentManage(savedSubscription)}
                          style={{ opacity: subscriptionActionLoading === 'portal' ? .7 : 1 }}
                        >
                          {subscriptionActionLoading === 'portal' ? '開啟中...' : '管理付款'}
                        </button>
                        <button
                          className="btn btn-ghost"
                          disabled={savedSubscription.status === 'cancelled' || subscriptionActionLoading === 'cancel'}
                          onClick={() => handleSubscriptionCancel(savedSubscription)}
                          style={{ opacity: savedSubscription.status === 'cancelled' || subscriptionActionLoading === 'cancel' ? .55 : 1 }}
                        >
                          {subscriptionActionLoading === 'cancel' ? '取消中...' : savedSubscription.status === 'cancelled' ? '已取消續訂' : '取消續訂'}
                        </button>
                        <button className="btn btn-ghost" onClick={() => openSubscriptionPlanChange(savedSubscription)}>
                          {planChangeOpen ? '收合方案' : '變更方案'}
                        </button>
                      </div>
                      {planChangeOpen && (
                        <div style={{ marginTop: 28, padding: 24, background: 'var(--jf-paper-2)', border: '1px solid var(--jf-line)' }}>
                          <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 3, color: 'var(--jf-red)', marginBottom: 8 }}>CHANGE PLAN</div>
                          <div style={{ fontFamily: 'var(--serif)', fontSize: 22, letterSpacing: 2, marginBottom: 8 }}>在這裡變更訂閱方案</div>
                          <div style={{ fontFamily: 'var(--serif)', fontSize: 13, color: 'var(--jf-mute-2)', lineHeight: 1.8, marginBottom: 18 }}>
                            選好新方案後會開啟信用卡付款頁。付款確認後，會員中心會更新成新的方案，原方案會保留在紀錄中。
                          </div>
                          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: 10 }}>
                            {MEMBER_SUBSCRIPTION_PLANS.map(plan => {
                              const selected = selectedPlanMonths === plan.months;
                              const current = savedSubscription.status !== 'cancelled' && savedSubscription.months === plan.months;
                              const isUpgrade = plan.months > savedSubscription.months;
                              const isDowngrade = plan.months < savedSubscription.months;
                              const diffTWD = Math.max(0, plan.total - (savedSubscription.totalTWD || 0));
                              return (
                                <button
                                  key={plan.months}
                                  type="button"
                                  disabled={!isUpgrade}
                                  onClick={() => { if (isUpgrade) setSelectedPlanMonths(plan.months); }}
                                  className={selected ? 'btn btn-primary' : 'btn btn-ghost'}
                                  style={{
                                    justifyContent: 'flex-start',
                                    alignItems: 'flex-start',
                                    flexDirection: 'column',
                                    gap: 8,
                                    padding: 18,
                                    minHeight: 126,
                                    textAlign: 'left',
                                    cursor: isUpgrade ? 'pointer' : 'not-allowed',
                                    opacity: isUpgrade || selected ? 1 : .55,
                                  }}
                                >
                                  <span style={{ fontFamily: 'var(--sans)', fontSize: 10, letterSpacing: 2, opacity: .78 }}>
                                    {current ? '目前方案' : isDowngrade ? '需洽客服' : plan.label}
                                  </span>
                                  <span style={{ fontFamily: 'var(--serif)', fontSize: 22, letterSpacing: 1 }}>{plan.sub}</span>
                                  {isUpgrade ? (
                                    <span style={{ fontFamily: 'var(--serif)', fontSize: 13, opacity: .92 }}>
                                      升級只需補差價 <strong>NT$ {diffTWD.toLocaleString()}</strong>
                                      <span style={{ display: 'block', opacity: .7, fontSize: 12 }}>升級後總額 NT$ {plan.total.toLocaleString()}</span>
                                    </span>
                                  ) : (
                                    <span style={{ fontFamily: 'var(--serif)', fontSize: 13, opacity: .82 }}>
                                      {current
                                        ? `目前方案 · 已付 NT$ ${(savedSubscription.totalTWD || 0).toLocaleString()}`
                                        : '降級需退刷，請洽客服'}
                                    </span>
                                  )}
                                </button>
                              );
                            })}
                          </div>
                          <div style={{ marginTop: 18, display: 'flex', justifyContent: 'space-between', gap: 18, alignItems: 'center', flexWrap: 'wrap' }}>
                            <div style={{ fontFamily: 'var(--serif)', fontSize: 13, color: 'var(--jf-mute-2)', lineHeight: 1.8 }}>
                              付款時填寫的 Email 會作為訂閱通知信箱，付款成功後會更新訂閱紀錄。
                            </div>
                            <button
                              type="button"
                              className="btn btn-primary"
                              disabled={subscriptionActionLoading === 'change-plan' || !(selectedPlanMonths > savedSubscription.months)}
                              onClick={checkoutSubscriptionPlanChange}
                              style={{ padding: '12px 26px', opacity: (subscriptionActionLoading === 'change-plan' || !(selectedPlanMonths > savedSubscription.months)) ? .6 : 1 }}
                            >
                              {subscriptionActionLoading === 'change-plan' ? '建立付款頁面中...' : selectedPlanMonths > savedSubscription.months ? `前往付款補差價` : '請選擇升級方案'}
                            </button>
                          </div>
                        </div>
                      )}
                    </>
                  ) : (
                    <>
                      <div style={{ fontFamily: 'var(--serif)', fontSize: 20, letterSpacing: 2, marginBottom: 8 }}>尚未啟用訂閱</div>
                      <div style={{ fontFamily: 'var(--serif)', fontSize: 14, color: 'var(--jf-mute-2)', lineHeight: 1.9, marginBottom: 22 }}>
                        選擇 1、3、6、12 個月方案後，付款完成會在這裡顯示出貨狀態與續訂管理入口。
                      </div>
                      <button className="btn btn-primary" onClick={() => onNav('subscribe')}>前往訂閱方案</button>
                    </>
                  )}
                  {subscriptionNotice && (
                    <div style={{ marginTop: 22, padding: 18, background: subscriptionNotice.tone === 'success' ? '#f4fbf6' : 'var(--jf-paper-2)', border: subscriptionNotice.tone === 'success' ? '1px solid #bfe6cb' : 'none', fontFamily: 'var(--serif)', fontSize: 13, color: subscriptionNotice.tone === 'error' ? 'var(--jf-red)' : subscriptionNotice.tone === 'success' ? '#1b7f3a' : 'var(--jf-mute-2)', lineHeight: 1.8 }}>
                      {subscriptionNotice.text}
                      {subscriptionNotice.tone === 'confirm' && (
                        <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', marginTop: 14 }}>
                          <button
                            type="button"
                            className="btn btn-primary"
                            disabled={subscriptionActionLoading === 'cancel'}
                            onClick={confirmSubscriptionCancel}
                            style={{ padding: '10px 22px', opacity: subscriptionActionLoading === 'cancel' ? .7 : 1 }}
                          >
                            {subscriptionActionLoading === 'cancel' ? '取消中...' : '確認取消續訂'}
                          </button>
                          <button
                            type="button"
                            className="btn btn-ghost"
                            onClick={() => { setCancelTargetSubscription(null); setSubscriptionNotice(null); }}
                            style={{ padding: '10px 22px' }}
                          >
                            先不要
                          </button>
                        </div>
                      )}
                      {subscriptionNotice.tone !== 'success' && subscriptionNotice.tone !== 'confirm' && (
                        <div style={{ marginTop: 10 }}>
                          <a href="https://line.me/R/ti/p/@659mpzle" target="_blank" rel="noreferrer" style={{ color: 'var(--jf-red)', borderBottom: '1px solid currentColor' }}>加入 LINE 官方協助處理</a>
                        </div>
                      )}
                    </div>
                  )}
                </div>
              </>
            )}

            {tab === 'purchases' && (
              <>
                <h2 style={{ fontFamily: 'var(--serif)', fontSize: 26, margin: '0 0 24px', letterSpacing: 2 }}>LINE 代購紀錄</h2>
                {assistedPurchases.length === 0 ? (
                  <EmptyMemberState
                    title="尚無 LINE 代購紀錄"
                    desc="貼上日本 Amazon、樂天、UNIQLO 或 GU 商品網址取得報價後，代購流程會記錄在這裡。"
                    action={<button className="btn btn-primary" onClick={() => onNav('line')}>貼上商品網址</button>}
                  />
                ) : (
                  <div style={{ background: '#fff', border: '1px solid var(--jf-line)' }}>
                    {assistedPurchases.map(item => (
                      <div key={item.id} style={{ padding: '20px 24px', borderBottom: '1px solid var(--jf-line)', display: 'grid', gridTemplateColumns: '110px 1.6fr 1fr 1fr 100px', gap: 16, alignItems: 'center', fontFamily: 'var(--serif)', fontSize: 14 }}>
                        <span style={{ fontFamily: 'var(--sans)', fontSize: 12, color: 'var(--jf-mute)' }}>#{item.id}</span>
                        <span style={{ lineHeight: 1.5 }}>{item.title || '-'}</span>
                        <span>{formatMemberDate(item.createdAt)}</span>
                        <span style={{ color: 'var(--jf-mute-2)' }}>{item.source}</span>
                        <span style={{ color: 'var(--jf-red)', fontFamily: 'var(--sans)', fontSize: 12, fontWeight: 600 }}>● {item.status}</span>
                      </div>
                    ))}
                  </div>
                )}
              </>
            )}

            {tab === 'shipments' && (
              <MergeShipmentSection
                orders={orders}
                mergeShipments={mergeShipments}
                authSession={authSession}
                onAccountUpdate={setAccount}
                onNav={onNav}
              />
            )}

            {tab === 'fav' && (
              <>
                <h2 style={{ fontFamily: 'var(--serif)', fontSize: 26, margin: '0 0 24px', letterSpacing: 2 }}>收藏商品</h2>
                {favoriteProducts.length === 0 ? (
                  <EmptyMemberState
                    title="尚無收藏商品"
                    desc="點選商品收藏後，會保存在這裡。"
                    action={<button className="btn btn-primary" onClick={() => onNav('shop')}>瀏覽商品</button>}
                  />
                ) : (
                  <div className="product-grid" style={{ gridTemplateColumns: 'repeat(3, 1fr)' }}>
                    {favoriteProducts.map(p => (
                      <ProductCard
                        key={window.favoriteProductId?.(p) || p.id}
                        product={p}
                        favorite
                        onToggleFavorite={toggleFavoriteFromMember}
                        onClick={() => onNav('product', { product: p })}
                      />
                    ))}
                  </div>
                )}
              </>
            )}

            {tab === 'address' && (
              <>
                <div className="member-section-head">
                  <div>
                    <h2 style={{ fontFamily: 'var(--serif)', fontSize: 26, margin: '0 0 8px', letterSpacing: 2 }}>收件資料</h2>
                    <p>管理常用台灣收件地址，會員結帳時會優先帶入預設地址。</p>
                  </div>
                  <button className="btn btn-primary" type="button" onClick={openNewAddressForm}>新增收件地址</button>
                </div>
                {(addressNotice || addressError) && (
                  <div className={`member-settings-message ${addressError ? 'is-error' : ''}`} style={{ margin: '0 0 18px' }}>
                    {addressError || addressNotice}
                  </div>
                )}
                {addressFormOpen && (
                  <form className="member-address-form" onSubmit={saveAddress}>
                    <div className="member-address-form-head">
                      <div>
                        <span>{editingAddressId ? 'EDIT ADDRESS' : 'NEW ADDRESS'}</span>
                        <strong>{editingAddressId ? '編輯收件地址' : '新增常用收件地址'}</strong>
                      </div>
                      <button type="button" onClick={cancelAddressForm}>取消</button>
                    </div>
                    <div className="member-address-form-grid">
                      <Field label="收件人姓名">
                        <input value={addressForm.recipientName} onChange={event => updateAddressForm({ recipientName: event.target.value })} placeholder="王小明" required style={inputStyle} />
                      </Field>
                      <Field label="手機">
                        <input value={addressForm.phone} onChange={event => updateAddressForm({ phone: event.target.value })} placeholder="09xx xxx xxx" required style={inputStyle} />
                      </Field>
                      <Field label="縣市">
                        <select value={addressForm.city} onChange={event => updateAddressForm({ city: event.target.value })} style={inputStyle}>
                          {TAIWAN_CITIES.map(city => <option key={city} value={city}>{city}</option>)}
                        </select>
                      </Field>
                      <Field label="區域">
                        <input value={addressForm.district} onChange={event => updateAddressForm({ district: event.target.value })} placeholder="大安區" style={inputStyle} />
                      </Field>
                      <Field label="郵遞區號">
                        <input value={addressForm.postalCode} onChange={event => updateAddressForm({ postalCode: event.target.value.replace(/\D/g, '').slice(0, 5) })} placeholder="106" style={inputStyle} />
                      </Field>
                      <Field label="詳細地址">
                        <input value={addressForm.addressLine} onChange={event => updateAddressForm({ addressLine: event.target.value })} placeholder="請填寫路名、巷弄、門牌、樓層" required style={inputStyle} />
                      </Field>
                    </div>
                    <Field label="配送備註（選填）">
                      <textarea value={addressForm.note} onChange={event => updateAddressForm({ note: event.target.value })} placeholder="例如：平日白天可收件、管理室代收" style={{ ...inputStyle, minHeight: 78, resize: 'vertical' }} />
                    </Field>
                    <label className="member-address-default-check">
                      <input type="checkbox" checked={addressForm.isDefault} onChange={event => updateAddressForm({ isDefault: event.target.checked })} />
                      <span>設為預設收件地址</span>
                    </label>
                    <div className="member-address-form-actions">
                      <button type="submit" className="btn btn-primary" disabled={addressSaving}>
                        {addressSaving ? '儲存中…' : editingAddressId ? '儲存變更' : '新增地址'}
                      </button>
                      <button type="button" className="btn btn-ghost" onClick={cancelAddressForm}>取消</button>
                    </div>
                  </form>
                )}
                {addresses.length === 0 ? (
                  <EmptyMemberState
                    title="尚無收件地址"
                    desc="新增常用收件地址後，會保存在這裡方便下次結帳使用。"
                    action={<div style={{ display: 'flex', gap: 10, justifyContent: 'center', flexWrap: 'wrap' }}>
                      <button className="btn btn-primary" onClick={openNewAddressForm}>新增收件地址</button>
                    </div>}
                  />
                ) : (
                  <div className="member-address-list">
                    {addresses.map(address => (
                      <div key={address.id} className={`member-address-card ${address.isDefault ? 'is-default' : ''}`}>
                        <div className="member-address-main">
                          <div className="member-address-title">
                            <strong>{address.recipientName}</strong>
                            <span>{address.phone}</span>
                            {address.isDefault && <em>預設地址</em>}
                          </div>
                          <div className="member-address-line">
                            {address.postalCode && <span>{address.postalCode}</span>}
                            <span>{address.city}{address.district || ''}{address.addressLine}</span>
                          </div>
                          {address.note && <div className="member-address-note">{address.note}</div>}
                        </div>
                        <div className="member-address-actions">
                          {!address.isDefault && (
                            <button type="button" className="btn btn-ghost" disabled={addressSaving} onClick={() => setAddressDefault(address)}>設為預設</button>
                          )}
                          <button type="button" className="btn btn-ghost" onClick={() => openEditAddressForm(address)}>編輯</button>
                          <button type="button" className="btn btn-ghost" disabled={addressDeletingId === address.id} onClick={() => deleteAddress(address)}>
                            {addressDeletingId === address.id ? '刪除中…' : '刪除'}
                          </button>
                        </div>
                      </div>
                    ))}
                  </div>
                )}
              </>
            )}

            {tab === 'settings' && (
              <>
                <h2 style={{ fontFamily: 'var(--serif)', fontSize: 26, margin: '0 0 24px', letterSpacing: 2 }}>帳戶設定</h2>
                <form className="member-settings-form" onSubmit={saveSettings}>
                  <div className="member-settings-row">
                    <label>姓名</label>
                    <input value={settingsForm.name} onChange={event => setSettingsForm(form => ({ ...form, name: event.target.value }))} required maxLength="80" />
                  </div>
                  <div className="member-settings-row">
                    <label>Email</label>
                    <input value={settingsForm.email} onChange={event => setSettingsForm(form => ({ ...form, email: event.target.value }))} type="email" required maxLength="160" />
                  </div>
                  <div className="member-settings-row is-readonly">
                    <label>會員編號</label>
                    <span>{user.memberCode}</span>
                  </div>
                  <div className="member-settings-row is-readonly">
                    <label>LINE</label>
                    <span>{user.lineLinked ? '已綁定 ✓' : '尚未綁定'}</span>
                  </div>
                  <div className="member-settings-row is-readonly">
                    <label>語言</label>
                    <span>繁體中文</span>
                  </div>
                  {(settingsNotice || settingsError) && (
                    <div className={`member-settings-message ${settingsError ? 'is-error' : ''}`}>
                      {settingsError || settingsNotice}
                    </div>
                  )}
                  <div className="member-settings-actions">
                    <button type="submit" className="btn btn-primary" disabled={settingsSaving}>
                      {settingsSaving ? '儲存中…' : '儲存帳戶資料'}
                    </button>
                    <button type="button" className="btn btn-ghost" onClick={() => setSettingsForm({ name: user.name || '', email: user.email || '' })}>
                      取消變更
                    </button>
                  </div>
                </form>
              </>
            )}
          </div>
        </div>
      </section>
    </div>
  );
}

function CartPage({
  t,
  onNav,
  cartItems = [],
  onUpdateCartQty,
  onRemoveFromCart,
  onClearCart,
  authSession,
  initialCheckout = '',
  initialAuthStatus = '',
  initialAuthMessage = '',
}) {
  const items = cartItems;
  const [checkout, setCheckout] = useStateA(false);
  const [paying, setPaying] = useStateA(false);
  const [done, setDone] = useStateA(false);
  const [stripeNotice, setStripeNotice] = useStateA(null);
  const [lineAuthNotice, setLineAuthNotice] = useStateA(() => (
    initialAuthStatus
      ? { status: initialAuthStatus, message: initialAuthMessage || '' }
      : null
  ));
  const [paymentResult, setPaymentResult] = useStateA(() => readPaymentSuccess());
  const [couponCode, setCouponCode] = useStateA('');
  const [couponLoading, setCouponLoading] = useStateA(false);
  const [couponError, setCouponError] = useStateA('');
  const [appliedCoupon, setAppliedCoupon] = useStateA(null);
  const calculatedTotals = cartTotals(items);
  const subtotal = calculatedTotals.subtotalTWD;
  const fee = calculatedTotals.serviceFeeTWD;
  const shipping = calculatedTotals.shippingTWD;
  const baseTotal = calculatedTotals.baseTotalTWD;
  const discount = appliedCoupon?.discountTWD || appliedCoupon?.totals?.discountTWD || 0;
  const total = items.length ? Number(appliedCoupon?.totals?.totalTWD ?? Math.max(1, baseTotal - discount)) : 0;
  const itemCount = items.reduce((sum, item) => sum + item.qty, 0);
  const cartSignature = items.map(item => `${item.cartId || item.id}:${item.qty}:${item.price}`).join('|');
  const showingPaymentSuccess = Boolean(paymentResult && !items.length && !checkout);

  const dismissPaymentSuccess = (nextPage) => {
    clearPaymentSuccess();
    setPaymentResult(null);
    if (nextPage) onNav(nextPage);
  };

  const orderDraft = (code) => ({
    couponCode: code || undefined,
    contactEmail: authSession?.user?.email || undefined,
    items: items.map(item => ({
      cartId: item.cartId || item.id,
      productId: String(item.id || ''),
      categoryIds: item.categoryIds || [],
      categorySlugs: item.categorySlugs || [],
      sku: item.sku || '',
      sourceUrl: item.sourceUrl || '',
      sourceType: item.sourceType || '',
      feeIncluded: !!item.feeIncluded,
      shippingIncluded: !!item.shippingIncluded,
      selectedOptions: item.selectedOptions || {},
      name: item.name,
      japaneseName: item.jname || '',
      image: item.img || '',
      sourceLabel: item.sourceLabel || '',
      optionSummary: item.optionSummary || '',
      quantity: cartEffectiveQuantity(item),
      unitPriceTWD: item.price,
      extraShippingTWD: Math.max(0, Math.round(Number(item.extraShippingTWD || 0))),
      weightKg: Math.max(0.1, Number(item.weightKg || 1)),
    })),
    totals: {
      subtotalTWD: subtotal,
      serviceFeeTWD: fee,
      shippingTWD: shipping,
      discountTWD: 0,
      totalTWD: baseTotal,
    },
  });

  const applyCoupon = async () => {
    const nextCode = couponCode.trim().toUpperCase();
    setCouponError('');
    if (!items.length) {
      setCouponError('購物車目前沒有商品。');
      return;
    }
    if (!nextCode) {
      setCouponError('請輸入優惠碼。');
      return;
    }
    setCouponLoading(true);
    try {
      const payload = await authRequest('/coupons/validate', {
        method: 'POST',
        token: authSession?.token,
        body: {
          couponCode: nextCode,
          order: orderDraft(nextCode),
        },
      });
      setAppliedCoupon({
        code: payload.coupon.code,
        discountType: payload.coupon.discountType,
        discountTWD: payload.coupon.discountTWD || payload.totals?.discountTWD || 0,
        totals: payload.totals,
      });
      setCouponCode(payload.coupon.code);
    } catch (error) {
      setAppliedCoupon(null);
      setCouponError(error.message || '優惠碼無法套用。');
    } finally {
      setCouponLoading(false);
    }
  };

  useEffectA(() => {
    if (appliedCoupon) {
      setAppliedCoupon(null);
      setCouponError('購物車已更新，請重新套用優惠碼。');
    }
  }, [cartSignature]);

  useEffectA(() => {
    if (!initialAuthStatus) return;
    setLineAuthNotice({ status: initialAuthStatus, message: initialAuthMessage || '' });
  }, [initialAuthStatus, initialAuthMessage]);

  useEffectA(() => {
    if (initialCheckout === 'cart' && items.length) {
      setCheckout(true);
    }
  }, [initialCheckout, items.length]);

  useEffectA(() => {
    const hash = window.location.hash || '';
    const hashQuery = hash.includes('?') ? hash.slice(hash.indexOf('?') + 1) : '';
    const params = new URLSearchParams(window.location.search.replace(/^\?/, ''));
    new URLSearchParams(hashQuery).forEach((value, key) => {
      if (!params.has(key)) params.set(key, value);
    });
    const status = params.get('stripe_checkout');
    const sessionId = params.get('session_id');
    const cleanCartUrl = () => {
      ['stripe_checkout', 'session_id', 'order'].forEach(key => params.delete(key));
      const query = params.toString();
      window.history.replaceState(null, '', `/cart${query ? `?${query}` : ''}`);
    };
    if (status === 'cancel') {
      clearPaymentSuccess();
      setPaymentResult(null);
      setStripeNotice({ tone: 'error', text: '付款已取消，購物車內容已保留，可以重新結帳。' });
      cleanCartUrl();
      return undefined;
    }
    if (status !== 'success') return undefined;
    if (!sessionId) {
      setStripeNotice({ tone: 'error', text: '付款回傳缺少交易資訊，請聯繫客服確認訂單。' });
      cleanCartUrl();
      return undefined;
    }

    let active = true;
    setStripeNotice({ tone: 'loading', text: '正在確認信用卡付款狀態...' });
    authRequest(`/payments/stripe/checkout-sessions/${encodeURIComponent(sessionId)}/confirm`)
      .then(payload => {
        if (!active) return;
        if (payload.paymentStatus === 'paid') {
          const nextPaymentResult = paymentSuccessFromOrder(payload.order);
          if (nextPaymentResult) {
            storePaymentSuccess(nextPaymentResult);
            setPaymentResult(nextPaymentResult);
          }
          setStripeNotice({ tone: 'success', text: `付款完成，訂單 ${payload.order?.orderNumber || ''} 已成立。` });
          onClearCart?.();
        } else {
          setStripeNotice({ tone: 'error', text: '付款服務尚未回報完成，請稍後重新整理或聯繫客服確認。' });
        }
      })
      .catch(error => {
        if (!active) return;
        setStripeNotice({ tone: 'error', text: error.message || '付款確認失敗，請聯繫客服確認訂單。' });
      })
      .finally(() => {
        if (active) cleanCartUrl();
      });
    return () => { active = false; };
  }, []);

  return (
    <div data-screen-label="10 Cart">
      <section className="page-hero">
        <div className="crumb">HOME / {showingPaymentSuccess ? '結帳完成' : '購物車'}</div>
        <h1>{showingPaymentSuccess ? '付款完成' : '購物車'}</h1>
        <div className="jp">{showingPaymentSuccess ? `CHECKOUT SUCCESS · ${paymentResult.orderNumber}` : `カ ー ト · ${itemCount} 件商品`}</div>
      </section>

      {showingPaymentSuccess ? (
      <PaymentSuccessPanel
          result={paymentResult}
          authSession={authSession}
          onDismiss={dismissPaymentSuccess}
        />
      ) : (
      <>
      <section className="section section-narrow" style={{ paddingTop: 60 }}>
        {stripeNotice && (
          <div style={{
            marginBottom: 24,
            padding: '16px 20px',
            border: '1px solid var(--jf-line)',
            background: stripeNotice.tone === 'success' ? '#f4fbf5' : 'var(--jf-paper-2)',
            color: stripeNotice.tone === 'success' ? '#247a3b' : stripeNotice.tone === 'loading' ? 'var(--jf-mute-2)' : 'var(--jf-red)',
            fontFamily: 'var(--serif)',
            fontSize: 14,
            lineHeight: 1.8,
          }}>
            {stripeNotice.text}
          </div>
        )}
        <div style={{ display: 'grid', gridTemplateColumns: '1.6fr 1fr', gap: 60, alignItems: 'start' }}>
          {/* Items */}
          <div>
            <div style={{ background: '#fff', border: '1px solid var(--jf-line)' }}>
              <div style={{ padding: '16px 24px', borderBottom: '1px solid var(--jf-line)', display: 'grid', gridTemplateColumns: '90px 1fr 100px 110px 40px', gap: 20, fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 2, color: 'var(--jf-mute)' }}>
                <span></span><span>商品</span><span style={{ textAlign: 'center' }}>數量</span><span style={{ textAlign: 'right' }}>小計</span><span></span>
              </div>
              {items.map((it) => (
                <div key={it.cartId || it.id} style={{ padding: 24, borderBottom: '1px solid var(--jf-line)', display: 'grid', gridTemplateColumns: '90px 1fr 100px 110px 40px', gap: 20, alignItems: 'center' }}>
                  <img src={it.img} alt="" style={{ width: 90, height: 90, objectFit: 'cover' }} />
                  <div>
                    <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 2, color: 'var(--jf-red)', marginBottom: 6 }}>{it.region}</div>
                    <div style={{ fontFamily: 'var(--serif)', fontSize: 16 }}>{it.name}</div>
                    <div style={{ fontFamily: 'var(--serif)', fontSize: 12, color: 'var(--jf-mute)', marginTop: 4 }}>{it.jname}</div>
                    {it.optionSummary && <div style={{ fontFamily: 'var(--sans)', fontSize: 11, color: 'var(--jf-mute)', marginTop: 6 }}>{it.optionSummary}</div>}
                    {(it.feeIncluded || it.shippingIncluded) && (
                      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 8 }}>
                        {it.feeIncluded && <span style={{ padding: '3px 8px', background: 'var(--jf-paper-2)', color: 'var(--jf-red)', fontFamily: 'var(--sans)', fontSize: 10, letterSpacing: 1 }}>服務費已含</span>}
                        {it.shippingIncluded && <span style={{ padding: '3px 8px', background: 'var(--jf-paper-2)', color: 'var(--jf-red)', fontFamily: 'var(--sans)', fontSize: 10, letterSpacing: 1 }}>國際運費已含</span>}
                      </div>
                    )}
                    <div style={{ fontFamily: 'var(--serif)', fontSize: 14, color: 'var(--jf-mute-2)', marginTop: 8 }}>
                      NT$ {cartItemDisplayTotal(it).toLocaleString('zh-TW')}
                    </div>
                  </div>
                  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', border: '1px solid var(--jf-line)' }}>
                    <button onClick={() => onUpdateCartQty?.(it.cartId, Math.max(1, it.qty - 1))} style={{ width: 30, height: 32 }}>−</button>
                    <span style={{ fontFamily: 'var(--serif)', width: 36, textAlign: 'center' }}>{it.qty}</span>
                    <button onClick={() => onUpdateCartQty?.(it.cartId, it.qty + 1)} style={{ width: 30, height: 32 }}>+</button>
                  </div>
                  <div style={{ textAlign: 'right', fontFamily: 'var(--serif)', fontSize: 18, color: 'var(--jf-red)', fontWeight: 600 }}>NT$ {cartItemDisplayTotal(it).toLocaleString('zh-TW')}</div>
                  <button onClick={() => onRemoveFromCart?.(it.cartId)} style={{ color: 'var(--jf-mute)', fontSize: 18 }}>×</button>
                </div>
              ))}
              {items.length === 0 && (
                <div style={{ padding: 60, textAlign: 'center', fontFamily: 'var(--serif)', color: 'var(--jf-mute)' }}>購物車目前是空的</div>
              )}
            </div>

            <div style={{ marginTop: 20, display: 'flex', gap: 10 }}>
              <input value={couponCode} onChange={e => { setCouponCode(e.target.value.toUpperCase()); setCouponError(''); }} placeholder="輸入優惠碼" style={{ flex: 1, padding: 12, border: '1px solid var(--jf-line)', fontFamily: 'var(--serif)', fontSize: 14, outline: 'none', textTransform: 'uppercase' }} />
              <button className="btn btn-ghost" disabled={couponLoading || !items.length} onClick={applyCoupon} style={{ opacity: couponLoading || !items.length ? .55 : 1, cursor: couponLoading || !items.length ? 'not-allowed' : 'pointer' }}>{couponLoading ? '驗證中' : '套用'}</button>
            </div>
            {(couponError || appliedCoupon) && (
              <div style={{ marginTop: 10, padding: '10px 12px', background: appliedCoupon ? '#f4fbf5' : 'var(--jf-paper-2)', color: appliedCoupon ? '#247a3b' : 'var(--jf-red)', fontFamily: 'var(--serif)', fontSize: 13, lineHeight: 1.7 }}>
                {appliedCoupon ? `已套用優惠碼 ${appliedCoupon.code}，折抵 NT$ ${Number(discount).toLocaleString()}。` : couponError}
              </div>
            )}
            <button onClick={() => onNav('shop')} style={{ marginTop: 24, fontFamily: 'var(--sans)', fontSize: 12, letterSpacing: 2, color: 'var(--jf-mute)', borderBottom: '1px solid var(--jf-mute)' }}>← 繼續購物</button>
          </div>

          {/* Summary */}
          <div style={{ position: 'sticky', top: 100, background: '#fff', border: '1px solid var(--jf-line)', padding: 30 }}>
            <h3 style={{ fontFamily: 'var(--serif)', fontSize: 22, margin: '0 0 20px', letterSpacing: 2, fontWeight: 500 }}>訂單摘要</h3>
            <div style={{ display: 'grid', gap: 14, paddingBottom: 18, borderBottom: '1px solid var(--jf-line)', fontFamily: 'var(--serif)', fontSize: 14 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: 'var(--jf-mute)' }}>商品小計</span><span>NT$ {subtotal.toLocaleString('zh-TW')}</span></div>
              <div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: 'var(--jf-mute)' }}>代購手續費 8%</span><span>NT$ {fee.toLocaleString('zh-TW')}</span></div>
              <div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: 'var(--jf-mute)' }}>空運運費（日本）</span><span>NT$ {shipping.toLocaleString('zh-TW')}</span></div>
              {appliedCoupon && <div style={{ display: 'flex', justifyContent: 'space-between', color: 'var(--jf-red)' }}><span>優惠碼 {appliedCoupon.code}</span><span>- NT$ {Number(discount).toLocaleString()}</span></div>}
            </div>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', padding: '20px 0' }}>
              <span style={{ fontFamily: 'var(--serif)', fontSize: 16 }}>總計</span>
              <span style={{ fontFamily: 'var(--serif)', fontSize: 32, color: 'var(--jf-red)', fontWeight: 600 }}>NT$ {total.toLocaleString('zh-TW')}</span>
            </div>
            <div style={{ fontFamily: 'var(--sans)', fontSize: 11, color: 'var(--jf-mute)', marginBottom: 16, lineHeight: 1.7 }}>
              ● 預計 3-5 個工作天送達<br />
              ● 日本倉庫合併出貨<br />
              ● 商品到倉專人攝影驗貨
            </div>
            <button className="btn btn-primary" disabled={!items.length} style={{ width: '100%', justifyContent: 'center', padding: '14px 0', opacity: items.length ? 1 : .45, cursor: items.length ? 'pointer' : 'not-allowed' }} onClick={() => items.length && setCheckout(true)}>前往結帳</button>
            <div style={{ marginTop: 14, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, fontFamily: 'var(--sans)', fontSize: 10, letterSpacing: 2, color: 'var(--jf-mute)' }}>
              <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 2L4 6v6c0 5 3.5 9 8 10 4.5-1 8-5 8-10V6l-8-4z" /></svg>
              SSL 加密付款
              <span style={{ display: 'inline-flex', gap: 4, marginLeft: 4 }}>
                <span style={{ padding: '2px 5px', background: '#1a1f36', color: '#fff', fontSize: 8, fontWeight: 700, letterSpacing: 0.5 }}>VISA</span>
                <span style={{ padding: '2px 5px', background: '#eb001b', color: '#fff', fontSize: 8, fontWeight: 700, letterSpacing: 0.5 }}>MC</span>
                <span style={{ padding: '2px 5px', background: '#006fcf', color: '#fff', fontSize: 8, fontWeight: 700, letterSpacing: 0.5 }}>AMEX</span>
                <span style={{ padding: '2px 5px', background: '#fb8e1c', color: '#fff', fontSize: 8, fontWeight: 700, letterSpacing: 0.5 }}>JCB</span>
              </span>
            </div>
          </div>
        </div>
      </section>

      {checkout && (
        <CheckoutModal total={total} subtotal={subtotal} shipping={shipping} fee={fee}
          coupon={appliedCoupon}
          lineAuthNotice={lineAuthNotice}
          onClose={() => { setCheckout(false); setPaying(false); setDone(false); }}
          paying={paying} setPaying={setPaying} done={done} setDone={setDone}
          onNav={onNav} onClearCart={onClearCart} cartItems={items} authSession={authSession} />
      )}
      </>
      )}
    </div>
  );
}

function PaymentSuccessPanel({ result, authSession, onDismiss }) {
  const isMemberOrder = result?.orderType === 'member' || Boolean(authSession?.token);
  const confirmedAt = result?.confirmedAt ? formatMemberDate(result.confirmedAt) : '';
  return (
    <section className="section section-narrow" style={{ paddingTop: 60 }}>
      <div style={{ background: '#fff', border: '1px solid var(--jf-line)', overflow: 'hidden' }}>
        <div className="payment-success-grid">
          <div style={{ padding: '54px 58px' }}>
            <div style={{ width: 74, height: 74, borderRadius: '50%', background: '#1c8f3a', color: '#fff', display: 'grid', placeItems: 'center', marginBottom: 28 }}>
              <svg width="34" height="34" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3"><path d="M5 12l5 5L20 7" /></svg>
            </div>
            <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 5, color: 'var(--jf-red)', marginBottom: 12 }}>PAYMENT COMPLETE</div>
            <h2 style={{ fontFamily: 'var(--serif)', fontSize: 34, lineHeight: 1.35, fontWeight: 500, letterSpacing: 2, margin: '0 0 14px' }}>付款完成，訂單已成立</h2>
            <p style={{ fontFamily: 'var(--serif)', fontSize: 15, lineHeight: 2, color: 'var(--jf-mute-2)', margin: '0 0 26px', maxWidth: 620 }}>
              感謝您的訂購。JaFun 已收到付款完成通知，後續會確認商品庫存與寄送進度。請保存訂單編號，若 Email 通知尚未收到，也可以用這組編號聯繫客服查詢。
            </p>
            <div className="payment-success-stats">
              <div style={{ padding: 18, borderRight: '1px solid var(--jf-line)' }}>
                <div style={{ fontFamily: 'var(--sans)', fontSize: 10, letterSpacing: 3, color: 'var(--jf-mute)', marginBottom: 6 }}>訂單編號</div>
                <div style={{ fontFamily: 'var(--sans)', fontSize: 14, letterSpacing: 1.5, color: 'var(--jf-red)' }}>{result.orderNumber}</div>
              </div>
              <div style={{ padding: 18, borderRight: '1px solid var(--jf-line)' }}>
                <div style={{ fontFamily: 'var(--sans)', fontSize: 10, letterSpacing: 3, color: 'var(--jf-mute)', marginBottom: 6 }}>付款狀態</div>
                <div style={{ fontFamily: 'var(--serif)', fontSize: 16 }}>已付款</div>
              </div>
              <div style={{ padding: 18 }}>
                <div style={{ fontFamily: 'var(--sans)', fontSize: 10, letterSpacing: 3, color: 'var(--jf-mute)', marginBottom: 6 }}>付款金額</div>
                <div style={{ fontFamily: 'var(--serif)', fontSize: 18, color: 'var(--jf-red)', fontWeight: 600 }}>{formatTwd(result.totalTWD)}</div>
              </div>
            </div>
            <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
              {isMemberOrder ? (
                <button className="btn btn-primary" onClick={() => onDismiss('mypage')}>查看會員訂單</button>
              ) : (
                <button className="btn btn-primary" onClick={() => onDismiss('register')}>加入會員接收後續優惠</button>
              )}
              <button className="btn btn-ghost" onClick={() => onDismiss('shop')}>繼續逛日本商城</button>
              <button className="btn btn-ghost" onClick={() => onDismiss('line')}>回到 LINE 代購</button>
            </div>
          </div>

          <aside className="payment-success-side" style={{ background: 'var(--jf-paper-2)', borderLeft: '1px solid var(--jf-line)', padding: '46px 34px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
            <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 4, color: 'var(--jf-red)', marginBottom: 18 }}>NEXT STEPS</div>
            {[
              ['01', '訂單成立', confirmedAt || '已完成'],
              ['02', '商品庫存確認', 'JaFun 工作人員處理'],
              ['03', '到倉驗貨', '攝影確認後打包'],
              ['04', '空運寄往台灣', '3-5 個工作天'],
            ].map((step, index) => (
              <div key={step[0]} style={{ display: 'grid', gridTemplateColumns: '42px 1fr', gap: 14, padding: '16px 0', borderBottom: index < 3 ? '1px solid var(--jf-line)' : 'none' }}>
                <div style={{ width: 34, height: 34, borderRadius: '50%', background: index === 0 ? 'var(--jf-red)' : '#fff', color: index === 0 ? '#fff' : 'var(--jf-mute)', border: '1px solid var(--jf-line)', display: 'grid', placeItems: 'center', fontFamily: 'var(--sans)', fontSize: 11 }}>{step[0]}</div>
                <div>
                  <div style={{ fontFamily: 'var(--serif)', fontSize: 15, marginBottom: 4 }}>{step[1]}</div>
                  <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 1, color: 'var(--jf-mute)' }}>{step[2]}</div>
                </div>
              </div>
            ))}
            {!isMemberOrder && (
              <div style={{ marginTop: 24, padding: 16, background: '#fff', border: '1px solid var(--jf-line)', fontFamily: 'var(--serif)', fontSize: 13, lineHeight: 1.8, color: 'var(--jf-mute-2)' }}>
                這次是訪客結帳。請保存訂單編號；註冊會員後，後續訂單可集中在會員中心追蹤。
              </div>
            )}
          </aside>
        </div>
      </div>
    </section>
  );
}

const MERGE_SHIPPING_STATUS_LABELS = {
  processing: '處理中',
  warehouse_received: '日本倉庫收貨',
  packing: '集貨打包',
  in_transit: '空運運送中',
  delivered: '台灣送達',
};

function MergeShipmentSection({ orders, mergeShipments, authSession, onAccountUpdate, onNav }) {
  const [selected, setSelected] = useStateA([]);
  const [note, setNote] = useStateA('');
  const [submitting, setSubmitting] = useStateA(false);
  const [message, setMessage] = useStateA('');
  const [error, setError] = useStateA('');

  const mergedOrderNumbers = new Set(
    mergeShipments
      .filter(shipment => shipment.status !== '已取消')
      .flatMap(shipment => shipment.orderNumbers || []),
  );
  // 可合併：已成立、未取消、尚未送達、且未在其他合併出貨單中的訂單
  const eligibleOrders = orders.filter(order =>
    order.status !== 'cancelled'
    && order.shipping?.status !== 'delivered'
    && !mergedOrderNumbers.has(order.orderNumber));

  const toggleOrder = (orderNumber) => {
    setMessage('');
    setError('');
    setSelected(current => current.includes(orderNumber)
      ? current.filter(item => item !== orderNumber)
      : [...current, orderNumber]);
  };

  const submit = async () => {
    if (selected.length < 2) {
      setError('請至少勾選兩筆訂單才能合併出貨。');
      return;
    }
    setSubmitting(true);
    setError('');
    setMessage('');
    try {
      const payload = await authRequest('/auth/shipments/merge', {
        method: 'POST',
        token: authSession?.token,
        body: { orderNumbers: selected, note: note.trim() || undefined },
      });
      if (payload.account) onAccountUpdate(payload.account);
      setSelected([]);
      setNote('');
      setMessage(`合併出貨申請已送出（單號 ${payload.shipment?.id || ''}），打包完成後會通知你確認運費。`);
    } catch (err) {
      setError(err.message || '合併出貨申請失敗，請稍後再試。');
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <>
      <h2 style={{ fontFamily: 'var(--serif)', fontSize: 26, margin: '0 0 10px', letterSpacing: 2 }}>合併出貨</h2>
      <p style={{ fontFamily: 'var(--serif)', fontSize: 14, color: 'var(--jf-mute-2)', lineHeight: 1.9, margin: '0 0 24px' }}>
        勾選多筆已成立的代購、商城訂單，商品在日本倉庫到齊後會合併打包、一次寄出，
        通常比分開寄送更省國際運費。（訂閱食箱為每月固定出貨、運費已內含，不列入此合併。）
      </p>

      {message && (
        <div style={{ marginBottom: 18, padding: 14, background: '#eef7ef', color: '#22662b', fontFamily: 'var(--serif)', fontSize: 14, lineHeight: 1.8 }}>{message}</div>
      )}
      {error && (
        <div style={{ marginBottom: 18, padding: 14, background: 'var(--jf-paper-2)', color: 'var(--jf-red)', fontFamily: 'var(--serif)', fontSize: 14, lineHeight: 1.8 }}>{error}</div>
      )}

      {eligibleOrders.length === 0 ? (
        <EmptyMemberState
          title="目前沒有可合併的訂單"
          desc="當你有兩筆以上未出貨的訂單時，可以在這裡勾選合併出貨。"
          action={<button className="btn btn-primary" onClick={() => onNav('shop')}>去逛逛商城</button>}
        />
      ) : (
        <>
          <div style={{ background: '#fff', border: '1px solid var(--jf-line)', marginBottom: 18 }}>
            {eligibleOrders.map(order => {
              const checked = selected.includes(order.orderNumber);
              const firstItem = order.items?.[0];
              return (
                <label key={order.orderNumber} style={{ display: 'grid', gridTemplateColumns: '28px 130px 1fr 110px', gap: 14, alignItems: 'center', padding: '18px 20px', borderBottom: '1px solid var(--jf-line)', cursor: 'pointer', background: checked ? 'var(--jf-paper-2)' : 'transparent' }}>
                  <input type="checkbox" checked={checked} onChange={() => toggleOrder(order.orderNumber)} style={{ width: 17, height: 17, accentColor: 'var(--jf-red)' }} />
                  <span style={{ fontFamily: 'var(--sans)', fontSize: 12, color: 'var(--jf-mute)' }}>#{order.orderNumber}</span>
                  <span style={{ fontFamily: 'var(--serif)', fontSize: 14, lineHeight: 1.5 }}>
                    {firstItem ? `${firstItem.name}${(order.items?.length || 1) > 1 ? ` 等 ${order.items.length} 件` : ''}` : '-'}
                    <small style={{ display: 'block', fontFamily: 'var(--sans)', fontSize: 11, color: 'var(--jf-mute)', marginTop: 4 }}>{formatMemberDate(order.createdAt)} · NT$ {Number(order.totals?.totalTWD || 0).toLocaleString()}</small>
                  </span>
                  <span style={{ fontFamily: 'var(--sans)', fontSize: 11, color: 'var(--jf-mute-2)', textAlign: 'right' }}>{MERGE_SHIPPING_STATUS_LABELS[order.shipping?.status] || order.shipping?.status || order.status}</span>
                </label>
              );
            })}
          </div>
          <textarea
            value={note}
            onChange={e => setNote(e.target.value)}
            placeholder="備註（選填）：例如希望等某筆訂單到齊再一起寄"
            style={{ width: '100%', minHeight: 64, padding: '12px 14px', border: '1px solid var(--jf-line)', fontFamily: 'var(--serif)', fontSize: 13, boxSizing: 'border-box', resize: 'vertical', marginBottom: 14 }}
          />
          <button className="btn btn-primary" disabled={submitting || selected.length < 2} onClick={submit} style={{ width: '100%', justifyContent: 'center', padding: '14px 0' }}>
            {submitting ? '送出申請中...' : `送出合併出貨申請${selected.length ? `（已選 ${selected.length} 筆）` : ''}`}
          </button>
          <div style={{ marginTop: 10, fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 1, color: 'var(--jf-mute)', textAlign: 'center' }}>
            至少勾選 2 筆訂單；合併後實際運費以日本倉庫打包重量計算
          </div>
        </>
      )}

      {mergeShipments.length > 0 && (
        <div style={{ marginTop: 36 }}>
          <h3 style={{ fontFamily: 'var(--serif)', fontSize: 19, margin: '0 0 14px', letterSpacing: 1 }}>合併出貨申請紀錄</h3>
          <div style={{ background: '#fff', border: '1px solid var(--jf-line)' }}>
            {mergeShipments.map(shipment => (
              <div key={shipment.id} style={{ padding: '16px 20px', borderBottom: '1px solid var(--jf-line)', display: 'grid', gridTemplateColumns: '130px 1fr 110px 110px', gap: 14, alignItems: 'center', fontFamily: 'var(--serif)', fontSize: 13 }}>
                <span style={{ fontFamily: 'var(--sans)', fontSize: 11, color: 'var(--jf-mute)' }}>#{shipment.id}</span>
                <span style={{ lineHeight: 1.6 }}>{(shipment.orderNumbers || []).join('、')}</span>
                <span style={{ color: 'var(--jf-mute-2)' }}>{formatMemberDate(shipment.createdAt)}</span>
                <span style={{ color: 'var(--jf-red)', fontFamily: 'var(--sans)', fontSize: 12, fontWeight: 600 }}>● {shipment.status}</span>
              </div>
            ))}
          </div>
        </div>
      )}
    </>
  );
}

function CheckoutModal({ total, subtotal, shipping, fee, coupon, lineAuthNotice = null, onClose, paying, setPaying, done, setDone, onNav, onClearCart, cartItems = [], authSession }) {
  const initialModalSession = authSession?.token ? authSession : (window.readAuthSession?.() || null);
  const returningFromLineAuth = Boolean(lineAuthNotice?.status);
  const [modalAuthSession, setModalAuthSession] = useStateA(initialModalSession);
  const activeAuthSession = authSession?.token ? authSession : modalAuthSession;
  const [step, setStep] = useStateA(returningFromLineAuth ? 'entry' : (initialModalSession?.token ? 'recipient' : 'entry'));
  const [checkoutMode, setCheckoutMode] = useStateA(initialModalSession?.token ? 'member' : 'guest');
  const [email, setEmail] = useStateA(initialModalSession?.user?.email || '');
  const [recipientName, setRecipientName] = useStateA('');
  const [phone, setPhone] = useStateA('');
  const [city, setCity] = useStateA('台北市');
  const [district, setDistrict] = useStateA('');
  const [postalCode, setPostalCode] = useStateA('');
  const [addressLine, setAddressLine] = useStateA('');
  const [deliveryNote, setDeliveryNote] = useStateA('');
  const [orderError, setOrderError] = useStateA('');
  const [fieldErrors, setFieldErrors] = useStateA({});
  const [createdOrder, setCreatedOrder] = useStateA(null);
  const [lineLoading, setLineLoading] = useStateA(false);
  const [authNotice, setAuthNotice] = useStateA(lineAuthNotice);
  const [savedAddresses, setSavedAddresses] = useStateA([]);
  const [selectedAddressId, setSelectedAddressId] = useStateA('');
  const [addressAutoFilled, setAddressAutoFilled] = useStateA(false);

  const applySavedAddress = (address) => {
    if (!address) return;
    setSelectedAddressId(address.id || '');
    setRecipientName(address.recipientName || '');
    setPhone(address.phone || '');
    setCity(address.city || '台北市');
    setDistrict(address.district || '');
    setPostalCode(address.postalCode || '');
    setAddressLine(address.addressLine || '');
    setDeliveryNote(address.note || '');
  };

  useEffectA(() => {
    const storedSession = authSession?.token ? authSession : (window.readAuthSession?.() || null);
    if (!storedSession?.token) return;
    setModalAuthSession(storedSession);
    setCheckoutMode('member');
    setEmail(storedSession.user?.email || '');
    setStep(current => (lineAuthNotice?.status ? current : (current === 'entry' ? 'recipient' : current)));
  }, [authSession?.token, authSession?.user?.email, lineAuthNotice?.status]);

  useEffectA(() => {
    if (lineAuthNotice) setAuthNotice(lineAuthNotice);
  }, [lineAuthNotice?.status, lineAuthNotice?.message]);

  useEffectA(() => {
    if (activeAuthSession?.token) return undefined;
    const storedSession = window.readAuthSession?.() || null;
    if (!storedSession?.token) return undefined;
    let disposed = false;
    authRequest('/auth/me', { token: storedSession.token })
      .then(payload => {
        if (disposed) return;
        const nextSession = {
          token: storedSession.token,
          user: payload.user,
          expiresAt: storedSession.expiresAt || '',
          storedAt: storedSession.storedAt || '',
        };
        storeAuthSession(nextSession);
        setModalAuthSession(nextSession);
        setCheckoutMode('member');
        setEmail(payload.user?.email || '');
        setStep(current => (lineAuthNotice?.status ? current : (current === 'entry' ? 'recipient' : current)));
      })
      .catch(() => {
        // Leave the modal in guest checkout mode when a stale local token is found.
      });
    return () => {
      disposed = true;
    };
  }, [activeAuthSession?.token, lineAuthNotice?.status]);

  useEffectA(() => {
    if (!activeAuthSession?.token) {
      setSavedAddresses([]);
      setSelectedAddressId('');
      setAddressAutoFilled(false);
      return undefined;
    }
    let disposed = false;
    authRequest('/auth/account', { token: activeAuthSession.token })
      .then(payload => {
        if (disposed) return;
        const addresses = payload.addresses || [];
        setSavedAddresses(addresses);
        const preferred = addresses.find(address => address.isDefault) || addresses[0];
        if (preferred && !addressAutoFilled && !recipientName && !addressLine) {
          applySavedAddress(preferred);
          setAddressAutoFilled(true);
        }
      })
      .catch(() => {
        if (!disposed) setSavedAddresses([]);
      });
    return () => {
      disposed = true;
    };
  }, [activeAuthSession?.token]);

  const lineLogin = async () => {
    setLineLoading(true);
    setOrderError('');
    setAuthNotice(null);
    try {
      const returnUrl = new URL(window.location.origin + '/cart');
      returnUrl.searchParams.set('checkout', 'cart');
      returnUrl.searchParams.set('authAction', 'cart-checkout');
      const config = await authRequest(`/auth/line/login-url?returnUrl=${encodeURIComponent(returnUrl.toString())}`);
      if (config.configured && config.url) {
        window.location.href = config.url;
        return;
      }
      onClose();
      onNav('login');
    } catch (error) {
      setOrderError(error.message || 'LINE 登入目前無法啟動，請改用訪客結帳。');
    } finally {
      setLineLoading(false);
    }
  };

  const orderBody = () => ({
    couponCode: coupon?.code || undefined,
    contactEmail: email,
    recipient: {
      name: recipientName,
      phone,
      email,
      country: 'TW',
      city,
      district,
      postalCode,
      addressLine,
      note: deliveryNote,
    },
    items: cartItems.map(item => ({
      cartId: item.cartId || item.id,
      productId: String(item.id || ''),
      categoryIds: item.categoryIds || [],
      categorySlugs: item.categorySlugs || [],
      sku: item.sku || '',
      sourceUrl: item.sourceUrl || '',
      sourceType: item.sourceType || '',
      feeIncluded: !!item.feeIncluded,
      shippingIncluded: !!item.shippingIncluded,
      selectedOptions: item.selectedOptions || {},
      name: item.name,
      japaneseName: item.jname || '',
      image: item.img || '',
      sourceLabel: item.sourceLabel || '',
      optionSummary: item.optionSummary || '',
      quantity: cartEffectiveQuantity(item),
      unitPriceTWD: item.price,
      extraShippingTWD: Math.max(0, Math.round(Number(item.extraShippingTWD || 0))),
      weightKg: Math.max(0.1, Number(item.weightKg || 1)),
    })),
    totals: {
      subtotalTWD: subtotal,
      serviceFeeTWD: fee,
      shippingTWD: shipping,
      discountTWD: coupon?.discountTWD || coupon?.totals?.discountTWD || 0,
      totalTWD: total,
    },
  });

  const submit = async (e) => {
    e.preventDefault();
    setOrderError('');
    const errors = window.validateTaiwanRecipient({ email, recipientName, phone, district, addressLine });
    setFieldErrors(errors);
    if (Object.keys(errors).length) {
      setOrderError('請依紅字提示確認收件資料後再送出。');
      return;
    }
    setPaying(true);
    try {
      const payload = await authRequest('/payments/stripe/checkout-sessions', {
        method: 'POST',
        token: activeAuthSession?.token,
        body: { order: orderBody() },
      });
      if (!payload.url) {
        throw new Error('付款頁面建立失敗，請稍後再試。');
      }
      // Guest checkout: remember this order so it can be auto-bound to the member
      // account if the visitor registers/logs in afterwards.
      if (!activeAuthSession?.token) {
        window.writePendingGuestOrder?.(payload.order);
      }
      window.location.href = payload.url;
    } catch (error) {
      setOrderError(error.message || '信用卡結帳建立失敗，請稍後再試。');
      setPaying(false);
    }
  };

  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(20,20,20,.55)', zIndex: 100, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20, overflowY: 'auto', backdropFilter: 'blur(4px)' }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{ width: 980, height: 'calc(100vh - 40px)', maxHeight: 'calc(100vh - 40px)', background: '#fff', display: 'grid', gridTemplateColumns: '1.05fr 1fr', boxShadow: '0 40px 80px -20px rgba(0,0,0,.4)', overflow: 'hidden', minHeight: 0, position: 'relative' }}>
        <button
          type="button"
          aria-label="關閉結帳視窗"
          onClick={onClose}
          style={{ position: 'absolute', top: 14, right: 16, zIndex: 5, width: 36, height: 36, display: 'grid', placeItems: 'center', background: '#fff', border: '1px solid var(--jf-line)', borderRadius: '50%', cursor: 'pointer', fontSize: 16, color: 'var(--jf-ink)', lineHeight: 1 }}
        >
          ✕
        </button>

        {/* LEFT — order summary */}
        <div style={{ background: 'var(--jf-paper-2)', padding: '40px 44px', display: 'flex', flexDirection: 'column', minHeight: 0, overflowY: 'auto', WebkitOverflowScrolling: 'touch' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 28, fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 3, color: 'var(--jf-mute)' }}>
            <span style={{ cursor: 'pointer' }} onClick={onClose}>← 返回購物車</span>
          </div>
          <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 4, color: 'var(--jf-mute)', marginBottom: 8 }}>支付給 JAFUN</div>
          <div style={{ fontFamily: 'var(--serif)', fontSize: 44, fontWeight: 500, marginBottom: 4 }}>NT$ {total.toLocaleString()}</div>
          <div style={{ fontFamily: 'var(--serif)', fontSize: 12, color: 'var(--jf-mute)', letterSpacing: 2, marginBottom: 36 }}>JaFun 從日本買任何東西</div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8, marginBottom: 28, fontFamily: 'var(--sans)', fontSize: 10, letterSpacing: 1 }}>
            <span style={{ padding: '8px 10px', background: step === 'entry' ? 'var(--jf-ink)' : '#fff', color: step === 'entry' ? '#fff' : 'var(--jf-mute)', border: '1px solid var(--jf-line)', textAlign: 'center' }}>1 身分選擇</span>
            <span style={{ padding: '8px 10px', background: step === 'recipient' ? 'var(--jf-ink)' : '#fff', color: step === 'recipient' ? '#fff' : 'var(--jf-mute)', border: '1px solid var(--jf-line)', textAlign: 'center' }}>2 收件資料</span>
            <span style={{ padding: '8px 10px', background: done ? '#06c755' : '#fff', color: done ? '#fff' : 'var(--jf-mute)', border: '1px solid var(--jf-line)', textAlign: 'center' }}>3 信用卡付款</span>
          </div>

          <div style={{ flex: 1, display: 'grid', gap: 14, fontFamily: 'var(--serif)', fontSize: 13 }}>
            <div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: 'var(--jf-mute)' }}>商品小計</span><span>NT$ {subtotal.toLocaleString()}</span></div>
            <div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: 'var(--jf-mute)' }}>代購手續費 8%</span><span>NT$ {fee.toLocaleString()}</span></div>
            <div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: 'var(--jf-mute)' }}>空運運費</span><span>NT$ {shipping.toLocaleString()}</span></div>
            {coupon && <div style={{ display: 'flex', justifyContent: 'space-between', color: 'var(--jf-red)' }}><span>優惠碼 {coupon.code}</span><span>- NT$ {Number(coupon.discountTWD || coupon.totals?.discountTWD || 0).toLocaleString()}</span></div>}
            <div style={{ height: 1, background: 'var(--jf-line)', margin: '6px 0' }} />
            <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 16 }}><span>應付金額</span><span style={{ color: 'var(--jf-red)', fontWeight: 600 }}>NT$ {total.toLocaleString()}</span></div>
          </div>

          <div style={{ marginTop: 30, paddingTop: 22, borderTop: '1px solid var(--jf-line)', display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 12, fontFamily: 'var(--sans)', fontSize: 10, letterSpacing: 2, color: 'var(--jf-mute)' }}>
            <span style={{ display: 'flex', gap: 12 }}>
              <span style={{ cursor: 'pointer' }} onClick={() => { onClose(); onNav('terms'); }}>條款</span>
              <span style={{ cursor: 'pointer' }} onClick={() => { onClose(); onNav('privacy'); }}>隱私權</span>
            </span>
          </div>
        </div>

        {/* RIGHT — payment form */}
        <div style={{ padding: '40px 44px', overflowY: 'auto', minHeight: 0, position: 'relative', WebkitOverflowScrolling: 'touch' }}>
          {done ? (
            <div style={{ textAlign: 'center', padding: '60px 20px' }}>
              <div style={{ width: 72, height: 72, borderRadius: '50%', background: '#06c755', display: 'inline-grid', placeItems: 'center', marginBottom: 24 }}>
                <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="3"><path d="M5 12l5 5L20 7" /></svg>
              </div>
              <h2 style={{ fontFamily: 'var(--serif)', fontSize: 28, margin: '0 0 12px', letterSpacing: 2 }}>付款完成</h2>
              <div style={{ fontFamily: 'var(--serif)', fontSize: 14, color: 'var(--jf-mute-2)', lineHeight: 1.8, marginBottom: 28 }}>
                訂單已成立，收據將寄至 <strong style={{ color: 'var(--jf-ink)' }}>{email}</strong><br />
                訂單編號 <span style={{ fontFamily: 'var(--sans)', fontSize: 12, letterSpacing: 2, color: 'var(--jf-red)' }}>#{createdOrder?.orderNumber || 'LOCAL-CHECKOUT'}</span>
              </div>
              {!activeAuthSession?.token && (
                <div style={{ padding: 14, background: 'var(--jf-paper-2)', color: 'var(--jf-mute-2)', fontFamily: 'var(--serif)', fontSize: 13, lineHeight: 1.8, marginBottom: 18 }}>
                  這次是訪客結帳，訂單通知會寄到 Email。下次用 LINE 註冊登入，可以累積會員專屬優惠與到貨通知。
                </div>
              )}
              <button className="btn btn-primary" onClick={() => { onClearCart?.(); onClose(); onNav(activeAuthSession?.token ? 'mypage' : 'shop'); }} style={{ width: '100%', justifyContent: 'center', padding: '14px 0' }}>
                {activeAuthSession?.token ? '查看訂單' : '繼續購物'}
              </button>
              {!activeAuthSession?.token && (
                <button className="btn btn-ghost" onClick={() => { onClearCart?.(); onClose(); onNav('register'); }} style={{ width: '100%', justifyContent: 'center', padding: '12px 0', marginTop: 10 }}>加入會員追蹤訂單</button>
              )}
            </div>
          ) : step === 'entry' ? (
            <div>
              <div style={{ marginBottom: 18 }}>
                <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 4, color: 'var(--jf-red)', marginBottom: 8 }}>FAST CHECKOUT</div>
                <h3 style={{ fontFamily: 'var(--serif)', fontSize: 24, margin: 0, letterSpacing: 2, fontWeight: 500 }}>選擇結帳方式</h3>
              </div>
              {authNotice && (
                <div className={`line-checkout-auth-notice ${authNotice.status === 'success' ? 'is-success' : 'is-error'}`}>
                  <strong>{authNotice.status === 'success' ? 'LINE 登入成功' : 'LINE 登入未完成'}</strong>
                  <span>
                    {authNotice.message || (authNotice.status === 'success'
                      ? '已回到剛剛的結帳畫面，可以繼續填寫收件資料。'
                      : '目前無法完成 LINE 登入，請重新操作或改用訪客結帳。')}
                  </span>
                </div>
              )}

              <div style={{ border: '1px solid var(--jf-line)', background: 'var(--jf-paper-2)', padding: 22, marginBottom: 14 }}>
                <div style={{ fontFamily: 'var(--serif)', fontSize: 18, marginBottom: 8 }}>{activeAuthSession?.token ? '已登入會員，接收更多到貨消息' : '用會員結帳，接收更多到貨消息'}</div>
                <p style={{ fontFamily: 'var(--serif)', fontSize: 13, color: 'var(--jf-mute-2)', lineHeight: 1.8, margin: '0 0 16px' }}>
                  {activeAuthSession?.token ? '可用會員身分繼續結帳，保留訂單紀錄與到貨通知。' : '使用 LINE 註冊登入可取得會員專屬優惠、訂單通知、到貨提醒與後續客服追蹤。'}
                </p>
                <button className="btn" disabled={lineLoading} onClick={activeAuthSession?.token ? () => setStep('recipient') : lineLogin} style={{ width: '100%', justifyContent: 'center', padding: '13px 0', background: '#06c755', color: '#fff', marginBottom: 10, opacity: lineLoading ? .65 : 1 }}>
                  {lineLoading ? '連線中...' : (activeAuthSession?.token ? '繼續填寫收件資料' : '使用 LINE 註冊登入領優惠')}
                </button>
                <button className="btn btn-ghost" onClick={() => { onClose(); onNav('register'); }} style={{ width: '100%', justifyContent: 'center', padding: '12px 0' }}>Email 註冊 / 登入</button>
              </div>

              <div style={{ border: '1px solid var(--jf-line)', padding: 22 }}>
                <div style={{ fontFamily: 'var(--serif)', fontSize: 18, marginBottom: 8 }}>不用註冊也可以直接購物</div>
                <p style={{ fontFamily: 'var(--serif)', fontSize: 13, color: 'var(--jf-mute-2)', lineHeight: 1.8, margin: '0 0 16px' }}>
                  只填台灣收件資料與 Email，即可完成本次購物。會員優惠與訂單中心追蹤會保留給登入會員。
                </p>
                <button className="btn btn-primary" onClick={() => { setCheckoutMode('guest'); setStep('recipient'); }} style={{ width: '100%', justifyContent: 'center', padding: '14px 0' }}>以訪客身分直接下購</button>
                <button onClick={() => { onClose(); onNav('shop'); }} style={{ marginTop: 14, width: '100%', color: 'var(--jf-mute)', fontFamily: 'var(--sans)', fontSize: 12, letterSpacing: 2 }}>繼續購物</button>
              </div>

              {orderError && (
                <div style={{ marginTop: 16, padding: 12, background: 'var(--jf-paper-2)', color: 'var(--jf-red)', fontFamily: 'var(--serif)', fontSize: 13, lineHeight: 1.8 }}>
                  {orderError}
                </div>
              )}
            </div>
          ) : (
            <form onSubmit={submit} noValidate>
              <div style={{ marginBottom: 22 }}>
                <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 4, color: 'var(--jf-red)', marginBottom: 8 }}>TAIWAN DELIVERY</div>
                <h3 style={{ fontFamily: 'var(--serif)', fontSize: 22, margin: '0 0 8px', letterSpacing: 2, fontWeight: 500 }}>台灣收件資料</h3>
                <p style={{ fontFamily: 'var(--serif)', fontSize: 13, color: 'var(--jf-mute-2)', lineHeight: 1.7, margin: 0 }}>
                  目前配送以台灣本島與離島為主，請填寫可收簡訊與宅配聯繫的資料。
                </p>
              </div>

              <Field label="Email">
                <input type="email" value={email} onChange={e => { setEmail(e.target.value); setFieldErrors(prev => ({ ...prev, email: '' })); }} placeholder="you@email.com" required style={inputStyle} />
                <FieldErrorText message={fieldErrors.email} />
              </Field>

              {activeAuthSession?.token && savedAddresses.length > 0 && (
                <Field label="常用收件地址">
                  <select
                    value={selectedAddressId}
                    onChange={e => {
                      const address = savedAddresses.find(item => item.id === e.target.value);
                      if (address) {
                        applySavedAddress(address);
                        setAddressAutoFilled(true);
                      } else {
                        setSelectedAddressId('');
                      }
                    }}
                    style={inputStyle}
                  >
                    <option value="">不套用常用地址</option>
                    {savedAddresses.map(address => (
                      <option key={address.id} value={address.id}>
                        {address.isDefault ? '預設｜' : ''}{address.recipientName}・{address.city}{address.district || ''}{address.addressLine}
                      </option>
                    ))}
                  </select>
                </Field>
              )}

              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
                <Field label="收件人姓名">
                  <input value={recipientName} onChange={e => { setRecipientName(e.target.value); setFieldErrors(prev => ({ ...prev, recipientName: '' })); }} placeholder="王小明" required style={inputStyle} />
                  <FieldErrorText message={fieldErrors.recipientName} />
                </Field>
                <Field label="手機">
                  <input value={phone} onChange={e => { setPhone(e.target.value); setFieldErrors(prev => ({ ...prev, phone: '' })); }} placeholder="09xx xxx xxx" required style={inputStyle} />
                  <FieldErrorText message={fieldErrors.phone} />
                </Field>
              </div>

              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 110px', gap: 14 }}>
                <Field label="縣市">
                  <select value={city} onChange={e => setCity(e.target.value)} style={inputStyle}>
                    {TAIWAN_CITIES.map(item => <option key={item} value={item}>{item}</option>)}
                  </select>
                </Field>
                <Field label="區域">
                  <input value={district} onChange={e => { setDistrict(e.target.value); setFieldErrors(prev => ({ ...prev, district: '' })); }} placeholder="大安區" required style={inputStyle} />
                  <FieldErrorText message={fieldErrors.district} />
                </Field>
                <Field label="郵遞區號">
                  <input value={postalCode} onChange={e => setPostalCode(e.target.value.replace(/\D/g, '').slice(0, 5))} placeholder="106" style={inputStyle} />
                </Field>
              </div>

              <Field label="詳細地址">
                <input value={addressLine} onChange={e => { setAddressLine(e.target.value); setFieldErrors(prev => ({ ...prev, addressLine: '' })); }} placeholder="請填寫路名、巷弄、門牌、樓層" required style={inputStyle} />
                <FieldErrorText message={fieldErrors.addressLine} />
              </Field>

              <Field label="配送備註（選填）">
                <textarea value={deliveryNote} onChange={e => setDeliveryNote(e.target.value)} placeholder="例如：平日白天可收件、管理室代收" style={{ ...inputStyle, minHeight: 72, resize: 'vertical' }} />
              </Field>

              <div style={{ height: 1, background: 'var(--jf-line)', margin: '22px 0' }} />
              <h3 style={{ fontFamily: 'var(--serif)', fontSize: 22, margin: '0 0 12px', letterSpacing: 2, fontWeight: 500 }}>信用卡結帳</h3>
              <div style={{ border: '1px solid var(--jf-line)', background: 'var(--jf-paper-2)', padding: 18, marginBottom: 18 }}>
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 14, marginBottom: 12 }}>
                  <div style={{ fontFamily: 'var(--serif)', fontSize: 16, color: 'var(--jf-ink)' }}>前往安全信用卡付款頁</div>
                  <span style={{ display: 'inline-flex', gap: 5, flexShrink: 0 }}>
                    <span style={{ padding: '4px 7px', background: '#1a1f36', color: '#fff', fontSize: 9, fontWeight: 700, letterSpacing: 0.5 }}>VISA</span>
                    <span style={{ padding: '4px 7px', background: '#eb001b', color: '#fff', fontSize: 9, fontWeight: 700, letterSpacing: 0.5 }}>MC</span>
                    <span style={{ padding: '4px 7px', background: '#006fcf', color: '#fff', fontSize: 9, fontWeight: 700, letterSpacing: 0.5 }}>AMEX</span>
                    <span style={{ padding: '4px 7px', background: '#fb8e1c', color: '#fff', fontSize: 9, fontWeight: 700, letterSpacing: 0.5 }}>JCB</span>
                  </span>
                </div>
                <p style={{ margin: 0, fontFamily: 'var(--serif)', fontSize: 13, color: 'var(--jf-mute-2)', lineHeight: 1.8 }}>
                  按下付款後會建立一筆待付款訂單，並跳轉到安全信用卡付款頁。JaFun 不會接觸或儲存卡號。
                </p>
              </div>

              {orderError && (
                <div style={{ marginBottom: 16, padding: 12, background: 'var(--jf-paper-2)', color: 'var(--jf-red)', fontFamily: 'var(--serif)', fontSize: 13, lineHeight: 1.8 }}>
                  {orderError}
                </div>
              )}

              <button type="submit" disabled={paying} className="btn btn-primary" style={{ width: '100%', justifyContent: 'center', padding: '16px 0', opacity: paying ? .7 : 1 }}>
                {paying ? (
                  <span style={{ display: 'inline-flex', alignItems: 'center', gap: 10 }}>
                    <span style={{ width: 14, height: 14, border: '2px solid rgba(255,255,255,.4)', borderTopColor: '#fff', borderRadius: '50%', animation: 'jfspin 0.7s linear infinite', display: 'inline-block' }} />
                    建立付款頁面中…
                  </span>
                ) : `前往付款 NT$ ${total.toLocaleString()}`}
              </button>

              <div style={{ textAlign: 'center', marginTop: 16, fontFamily: 'var(--sans)', fontSize: 10, letterSpacing: 2, color: 'var(--jf-mute)' }}>
                信用卡資訊會在加密付款頁處理，JaFun 不會接觸或儲存卡號
              </div>
            </form>
          )}
        </div>
      </div>
      <style>{`@keyframes jfspin { to { transform: rotate(360deg); } }`}</style>
    </div>
  );
}

const inputStyle = { width: '100%', padding: '12px 14px', border: '1px solid var(--jf-line)', fontFamily: 'var(--serif)', fontSize: 14, outline: 'none', background: '#fff', boxSizing: 'border-box' };

function Field({ label, children }) {
  return (
    <div style={{ marginBottom: 14 }}>
      <label style={{ display: 'block', fontFamily: 'var(--sans)', fontSize: 10, letterSpacing: 2, color: 'var(--jf-mute)', marginBottom: 6 }}>{label}</label>
      {children}
    </div>
  );
}

Object.assign(window, {
  AuthPage,
  MyPage,
  CartPage,
  CheckoutModal,
  authRequest,
  storeAuthSession,
  readAuthSession,
  clearAuthSession,
});
