// Shared UI components: Nav, Footer, Hero collage
const { useState, useEffect, useRef, useCallback } = React;

const JAFUN_LINE_OFFICIAL_ID = '@659mpzle';
const JAFUN_LINE_OFFICIAL_URL = 'https://line.me/R/ti/p/@659mpzle';
const JAFUN_FAVORITES_KEY = 'jafun-favorite-products';
const JAFUN_PENDING_FAVORITE_KEY = 'jafun-pending-favorite-product';
const JAFUN_PENDING_FAVORITE_TTL = 24 * 60 * 60 * 1000;

function optimizeImageUrl(src, width = 720, quality = 72) {
  if (!src || typeof src !== 'string') return src || '';
  try {
    const url = new URL(src, window.location.origin);
    if (url.hostname === 'images.unsplash.com') {
      url.searchParams.set('w', String(width));
      url.searchParams.set('q', String(quality));
      url.searchParams.set('auto', 'format');
      url.searchParams.set('fit', 'crop');
      return url.toString();
    }
  } catch (error) {
    return src;
  }
  return src;
}

function imageVariantUrl(imageSizes, variant, fallback = '') {
  if (!imageSizes) return optimizeImageUrl(fallback);
  return imageSizes[variant] || imageSizes.card || imageSizes.og || imageSizes.original || optimizeImageUrl(fallback);
}

function imageVariantSrcSet(imageSizes) {
  if (!imageSizes) return undefined;
  const entries = [
    imageSizes.card ? `${imageSizes.card} 720w` : '',
    imageSizes.og ? `${imageSizes.og} 1200w` : '',
  ].filter(Boolean);
  return entries.length ? Array.from(new Set(entries)).join(', ') : undefined;
}

function openLineOfficial() {
  // 開官方帳號加好友頁;若新分頁被瀏覽器彈窗攔截(window.open 回傳 null),退回原分頁直接導向,確保按了一定有反應。
  const win = window.open(JAFUN_LINE_OFFICIAL_URL, '_blank');
  if (win) {
    win.opener = null;
  } else {
    window.location.href = JAFUN_LINE_OFFICIAL_URL;
  }
}

function validateTaiwanRecipient({ email, recipientName, phone, district, addressLine }) {
  const errors = {};
  const trimmedEmail = String(email || '').trim();
  if (!trimmedEmail) errors.email = '請填寫 Email';
  else if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(trimmedEmail)) errors.email = '請輸入有效的 Email';
  if (!String(recipientName || '').trim()) errors.recipientName = '請填寫收件人姓名';
  const rawPhone = String(phone || '').trim();
  const phoneDigits = rawPhone.replace(/\D/g, '').replace(/^886(?=9)/, '0');
  if (!rawPhone) errors.phone = '請填寫電話';
  else if (!/^09\d{8}$/.test(phoneDigits)) errors.phone = '電話格式不正確，請輸入 09 開頭共 10 碼的手機號碼';
  if (!String(district || '').trim()) errors.district = '請填寫區域';
  if (!String(addressLine || '').trim()) errors.addressLine = '請填寫完整地址';
  return errors;
}

function FieldErrorText({ message }) {
  if (!message) return null;
  return (
    <div role="alert" style={{ marginTop: 6, fontFamily: 'var(--sans)', fontSize: 12, color: '#b3261e' }}>
      {message}
    </div>
  );
}

function favoriteProductId(product) {
  return String(product?.id || product?.sku || product?.slug || '').trim();
}

function normalizeFavoriteProduct(product) {
  if (!product) return null;
  const id = favoriteProductId(product);
  if (!id) return null;
  return {
    id,
    productId: id,
    sku: product.sku || '',
    slug: product.slug || '',
    name: product.name || '',
    jname: product.jname || product.japaneseName || '',
    region: product.region || '',
    price: Number(product.price || 0),
    jpy: Number(product.jpy || 0),
    img: product.img || '',
    imageSizes: product.imageSizes || undefined,
    tags: Array.isArray(product.tags) ? product.tags : [],
    savedAt: new Date().toISOString(),
  };
}

function readFavoriteProducts() {
  try {
    const parsed = JSON.parse(window.localStorage.getItem(JAFUN_FAVORITES_KEY) || '[]');
    return Array.isArray(parsed) ? parsed.filter(item => favoriteProductId(item)) : [];
  } catch (error) {
    return [];
  }
}

function writeFavoriteProducts(favorites) {
  window.localStorage.setItem(JAFUN_FAVORITES_KEY, JSON.stringify(favorites));
}

function readPendingFavoriteProduct() {
  try {
    const parsed = JSON.parse(window.localStorage.getItem(JAFUN_PENDING_FAVORITE_KEY) || 'null');
    const savedAt = Date.parse(parsed?.savedAt || '');
    if (!favoriteProductId(parsed) || !savedAt || Date.now() - savedAt > JAFUN_PENDING_FAVORITE_TTL) {
      clearPendingFavoriteProduct();
      return null;
    }
    return parsed;
  } catch (error) {
    return null;
  }
}

function writePendingFavoriteProduct(product) {
  const favorite = normalizeFavoriteProduct(product);
  if (!favorite) return '';
  window.localStorage.setItem(JAFUN_PENDING_FAVORITE_KEY, JSON.stringify(favorite));
  return favorite.productId;
}

function clearPendingFavoriteProduct() {
  window.localStorage.removeItem(JAFUN_PENDING_FAVORITE_KEY);
}

function completePendingFavoriteProduct() {
  if (!isMemberLoggedIn()) return null;
  const pending = readPendingFavoriteProduct();
  if (!pending) return null;
  const result = toggleFavoriteProduct(pending, true);
  clearPendingFavoriteProduct();
  return result;
}

// Guest checkout creates an order before the visitor has an account. We stash a
// lightweight reference + the server-issued claimToken in localStorage so that,
// once the visitor registers/logs in, the order can be pulled into their member
// account automatically. Mirrors the pending-favorite flow above.
const JAFUN_PENDING_GUEST_ORDER_KEY = 'jafun-pending-guest-order';
const JAFUN_PENDING_GUEST_ORDER_TTL = 7 * 24 * 60 * 60 * 1000; // 7 days

function readPendingGuestOrder() {
  try {
    const parsed = JSON.parse(window.localStorage.getItem(JAFUN_PENDING_GUEST_ORDER_KEY) || 'null');
    const savedAt = Date.parse(parsed?.savedAt || '');
    const ref = String(parsed?.orderId || parsed?.orderNumber || '').trim();
    if (!ref || !parsed?.claimToken || !savedAt || Date.now() - savedAt > JAFUN_PENDING_GUEST_ORDER_TTL) {
      clearPendingGuestOrder();
      return null;
    }
    return parsed;
  } catch (error) {
    return null;
  }
}

function writePendingGuestOrder(order) {
  if (!order) return '';
  const orderId = String(order.id || order.orderId || '').trim();
  const orderNumber = String(order.orderNumber || '').trim();
  const claimToken = String(order.claimToken || '').trim();
  // Without a claimToken the order can never be claimed, so there is nothing worth storing.
  if ((!orderId && !orderNumber) || !claimToken) return '';
  window.localStorage.setItem(JAFUN_PENDING_GUEST_ORDER_KEY, JSON.stringify({
    orderId,
    orderNumber,
    claimToken,
    contactEmail: String(order.contactEmail || order.recipient?.email || '').trim(),
    savedAt: new Date().toISOString(),
  }));
  return orderNumber || orderId;
}

function clearPendingGuestOrder() {
  window.localStorage.removeItem(JAFUN_PENDING_GUEST_ORDER_KEY);
}

async function completePendingGuestOrder() {
  if (!isMemberLoggedIn()) return null;
  const pending = readPendingGuestOrder();
  if (!pending) return null;
  const session = window.readAuthSession?.();
  if (!session?.token || !window.authRequest) return null;
  try {
    const result = await window.authRequest('/auth/orders/claim', {
      method: 'POST',
      token: session.token,
      body: {
        orderId: pending.orderId || undefined,
        orderNumber: pending.orderNumber || undefined,
        claimToken: pending.claimToken,
      },
    });
    // Resolved response (claimed, or already-owned/not-found) — we are done either way.
    clearPendingGuestOrder();
    window.dispatchEvent(new CustomEvent('jafun:guest-order-claimed', { detail: result }));
    return result;
  } catch (error) {
    // Keep the pending reference so a transient failure can retry on the next login.
    console.warn(`Guest order claim skipped: ${error instanceof Error ? error.message : String(error)}`);
    return null;
  }
}

function syncFavoriteProduct(productId, isFavorite) {
  const session = window.readAuthSession?.();
  if (!session?.token || !window.authRequest) return;
  window.authRequest(isFavorite ? '/auth/favorites' : '/auth/favorites/remove', {
    method: 'POST',
    token: session.token,
    body: { productId },
  }).catch(error => {
    console.warn(`Favorite sync skipped: ${error instanceof Error ? error.message : String(error)}`);
  });
}

function toggleFavoriteProduct(product, force) {
  const favorite = normalizeFavoriteProduct(product);
  if (!favorite) return { isFavorite: false, favorites: readFavoriteProducts(), productId: '' };
  const favorites = readFavoriteProducts();
  const productId = favorite.productId;
  const exists = favorites.some(item => favoriteProductId(item) === productId);
  const shouldFavorite = typeof force === 'boolean' ? force : !exists;
  const nextFavorites = shouldFavorite
    ? [favorite, ...favorites.filter(item => favoriteProductId(item) !== productId)]
    : favorites.filter(item => favoriteProductId(item) !== productId);
  writeFavoriteProducts(nextFavorites);
  syncFavoriteProduct(productId, shouldFavorite);
  const detail = { productId, isFavorite: shouldFavorite, favorites: nextFavorites };
  window.dispatchEvent(new CustomEvent('jafun:favorites-changed', { detail }));
  return detail;
}

function isFavoriteProduct(product) {
  const id = favoriteProductId(product);
  return Boolean(id && readFavoriteProducts().some(item => favoriteProductId(item) === id));
}

function isMemberLoggedIn() {
  return Boolean(window.readAuthSession?.()?.token);
}

function favoriteAuthReturnUrl() {
  const url = new URL(window.location.href);
  ['authToken', 'authStatus', 'authMessage'].forEach(key => url.searchParams.delete(key));
  return url.toString();
}

function sharedLooksLikeProductUrl(value) {
  const input = String(value || '').trim();
  if (/^https?:\/\//i.test(input)) return true;
  if (/^(www\.)?(amazon\.co\.jp|item\.rakuten\.co\.jp|search\.rakuten\.co\.jp|uniqlo\.com|gu-global\.com)\//i.test(input)) return true;
  return /\.(co\.jp|com|jp)(\/|$)/i.test(input);
}

function readableJafunSearchError(message) {
  const fallback = '商品資料無法解析，請改用 LINE 人工確認或重新輸入商品需求。';
  const text = String(message || fallback)
    .replace(/Amazon JP\s*/gi, '')
    .replace(/Amazon\.co\.jp\s*/gi, '')
    .replace(/Rakuten\s*Ichiba\s*/gi, '')
    .replace(/\s*[\(（]\s*[\)）]/g, ' ')
    .replace(/\s*[\(（][^()（）]*[A-Za-z][^()（）]*[\)）]\s*$/g, '')
    .replace(/請確認商品網址是否仍有效。?/g, '')
    .replace(/\s+/g, ' ')
    .trim();
  if (/商品資料無法解析/.test(text)) return '商品資料無法解析';
  return text || fallback;
}

async function jafunReadApiError(response, fallback) {
  const fallbackMessage = fallback || '商品資料無法解析';
  try {
    const payload = await response.json();
    const message = Array.isArray(payload.message) ? payload.message.join('、') : (payload.message || payload.error || fallbackMessage);
    return readableJafunSearchError(message);
  } catch (error) {
    return readableJafunSearchError(fallbackMessage);
  }
}

function productSearchFallbackQuery(value) {
  const input = String(value || '').trim();
  if (!input) return '';
  try {
    const normalized = /^https?:\/\//i.test(input) ? input : `https://${input}`;
    const url = new URL(normalized);
    const searchKeyword = url.searchParams.get('k') || url.searchParams.get('keyword') || url.searchParams.get('q');
    if (searchKeyword) return decodeURIComponent(searchKeyword).replace(/[-_+]+/g, ' ').trim();
    const parts = url.pathname
      .split('/')
      .map(part => decodeURIComponent(part || '').trim())
      .filter(Boolean)
      .filter(part => !/^(dp|gp|product|ref|item|shop)$/i.test(part))
      .filter(part => !/^[A-Z0-9]{8,14}$/i.test(part))
      .filter(part => !/^(pc|sp|mall|search)$/i.test(part));
    const titleLike = parts.find(part => /[\u3040-\u30ff\u3400-\u9fffA-Za-z]/.test(part));
    return (titleLike || parts[0] || input).replace(/[-_+]+/g, ' ').slice(0, 160).trim();
  } catch (error) {
    return input;
  }
}

async function requestJafunQuote(url, quantity = 1, selections = {}, options = {}) {
  const apiBase = window.JAFUN_QUOTE_API_BASE || 'http://localhost:3001';
  const response = await fetch(`${apiBase}/quote/parse`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      url,
      quantity,
      selections,
      ...(window.jafunAnalyticsPayload?.(options.entryPoint || 'url_quote_search') || {}),
    }),
  });
  if (!response.ok) {
    throw new Error(await jafunReadApiError(response, '商品資料無法解析'));
  }
  return { ...(await response.json()), fromFallback: false };
}

async function requestJafunKeywordRecommendation(query, options = {}) {
  const apiBase = window.JAFUN_QUOTE_API_BASE || 'http://localhost:3001';
  const response = await fetch(`${apiBase}/quote/recommend`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      query,
      maxCandidates: options.maxCandidates || 3,
      ...(window.jafunAnalyticsPayload?.(options.entryPoint || 'home_keyword_search') || {}),
    }),
  });
  if (!response.ok) {
    throw new Error(await jafunReadApiError(response, '目前無法取得 AI 推薦，請稍後再試。'));
  }
  return response.json();
}

async function resolveJafunProductSearch(rawValue, options = {}) {
  const query = String(rawValue || '').trim();
  if (!query) {
    throw new Error('請先輸入商品網址或想找的商品。');
  }
  const mode = sharedLooksLikeProductUrl(query) && options.preferredMode !== 'keyword' ? 'url' : 'keyword';
  if (mode === 'url') {
    try {
      const quote = await requestJafunQuote(query, options.quantity || 1, options.selections || {}, {
        entryPoint: options.urlEntryPoint || options.entryPoint || 'url_quote_search',
      });
      return { mode: 'url', query, quote, recommendation: null, parseWarning: '' };
    } catch (parseError) {
      if (options.fallbackToRecommend !== true) throw parseError;
      const fallbackQuery = productSearchFallbackQuery(query) || query;
      const recommendation = await requestJafunKeywordRecommendation(fallbackQuery, {
        maxCandidates: options.maxCandidates || 3,
        entryPoint: options.fallbackEntryPoint || 'url_quote_ai_fallback',
      });
      return {
        mode: 'keyword',
        sourceMode: 'url_fallback',
        query,
        fallbackQuery,
        quote: recommendation.quote || null,
        recommendation,
        parseWarning: readableJafunSearchError(parseError?.message || ''),
      };
    }
  }
  const recommendation = await requestJafunKeywordRecommendation(query, {
    maxCandidates: options.maxCandidates || 3,
    entryPoint: options.keywordEntryPoint || options.entryPoint || 'home_keyword_search',
  });
  return { mode: 'keyword', query, quote: recommendation.quote || null, recommendation, parseWarning: '' };
}

window.jafunProductSearch = {
  looksLikeProductUrl: sharedLooksLikeProductUrl,
  quote: requestJafunQuote,
  recommend: requestJafunKeywordRecommendation,
  resolve: resolveJafunProductSearch,
  readableError: readableJafunSearchError,
};

function submitMobileProductSearch(query, onNav) {
  const keyword = String(query || '').trim();
  if (!keyword) {
    onNav?.('line');
    return;
  }
  if (sharedLooksLikeProductUrl(keyword)) {
    onNav?.('line', { url: keyword });
    return;
  }
  const payload = { query: keyword, mode: 'keyword', at: Date.now() };
  try {
    window.sessionStorage.setItem('jafun-pending-home-search', JSON.stringify(payload));
  } catch (error) {}
  const currentRoute = (window.location.pathname.replace(/^\/|\/$/g, '') || 'home').split('?')[0] || 'home';
  onNav?.('home');
  if (currentRoute === 'home') {
    window.setTimeout(() => window.dispatchEvent(new CustomEvent('jafun:home-search-request', { detail: payload })), 0);
  }
}

function Logo({ size = 40, light = false }) {
  const source = light ? 'assets/jafun-logo-white.png' : 'assets/jafun-logo-red.png';
  return (
    <div className="nav-logo">
      <img src={source} alt="JaFun" style={{ height: size, width: 'auto', display: 'block' }} />
    </div>
  );
}

function Nav({ active, onNav, t, cartCount = 0, currentUser = null, onLogout = null }) {
  const [solid, setSolid] = useState(false);
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
  const [accountMenuOpen, setAccountMenuOpen] = useState(false);
  const accountMenuRef = useRef(null);
  const isSolid = solid || active !== 'home';
  useEffect(() => {
    const onScroll = () => setSolid(window.scrollY > 60);
    window.addEventListener('scroll', onScroll);
    onScroll();
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
  useEffect(() => {
    setMobileMenuOpen(false);
    setAccountMenuOpen(false);
  }, [active]);
  useEffect(() => {
    const onResize = () => {
      if (window.innerWidth > 760) setMobileMenuOpen(false);
      if (window.innerWidth <= 760) setAccountMenuOpen(false);
    };
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);
  useEffect(() => {
    const onPointerDown = (event) => {
      if (!accountMenuRef.current || accountMenuRef.current.contains(event.target)) return;
      setAccountMenuOpen(false);
    };
    document.addEventListener('mousedown', onPointerDown);
    document.addEventListener('touchstart', onPointerDown);
    return () => {
      document.removeEventListener('mousedown', onPointerDown);
      document.removeEventListener('touchstart', onPointerDown);
    };
  }, []);
  const items = [
    { k: 'home', label: t.nav.home },
    { k: 'line', label: t.nav.line },
    { k: 'shop', label: t.nav.shop },
    { k: 'subscribe', label: t.nav.subscribe },
    { k: 'ship', label: t.nav.ship },
    { k: 'learn', label: t.nav.learn },
    { k: 'about', label: t.nav.about },
  ];
  const memberMenuItems = [
    ['orders', '我的訂單'],
    ['subscriptions', '訂閱管理'],
    ['purchases', '代購紀錄'],
    ['fav', '收藏商品'],
    ['address', '收件資料'],
    ['settings', '帳戶設定'],
  ];
  const goNav = (page, props = {}) => {
    setMobileMenuOpen(false);
    setAccountMenuOpen(false);
    onNav(page, props);
  };
  const goMemberTab = (tab) => {
    goNav('mypage', { tab });
  };
  const toggleAccountMenu = () => {
    if (!currentUser) {
      goNav('login');
      return;
    }
    setAccountMenuOpen(open => !open);
  };
  const handleLogout = () => {
    setMobileMenuOpen(false);
    setAccountMenuOpen(false);
    onLogout?.();
  };
  const accountTextColor = isSolid ? 'var(--jf-mute)' : '#fff';
  const accountDividerColor = isSolid ? 'var(--jf-line)' : 'rgba(255,255,255,.55)';
  return (
    <nav className={`nav ${isSolid ? 'solid' : ''} ${mobileMenuOpen ? 'menu-open' : ''}`}>
      <div onClick={() => goNav('home')} style={{ cursor: 'pointer' }}>
        <Logo light={!isSolid} />
      </div>
      <div className="nav-center">
        {items.map(it => (
          <a key={it.k} className={active === it.k ? 'active' : ''} onClick={() => goNav(it.k)}>{it.label}</a>
        ))}
      </div>
      <div className="nav-right">
        <span className="icon-btn nav-cart-dot" title={t.nav.cart} onClick={() => goNav('cart')} style={{ cursor: 'pointer' }}>
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
            <path d="M3 3h2l2.4 12.2a2 2 0 002 1.8h9.5a2 2 0 002-1.6L22 7H6" />
            <circle cx="9" cy="20" r="1.5" /><circle cx="18" cy="20" r="1.5" />
          </svg>
          {cartCount > 0 && <span className="nav-cart-count">{Math.min(cartCount, 99)}</span>}
        </span>
        <div
          ref={accountMenuRef}
          className={`nav-account-menu ${currentUser ? 'is-authenticated' : ''} ${accountMenuOpen ? 'is-open' : ''}`}
          onMouseEnter={() => currentUser && setAccountMenuOpen(true)}
          onMouseLeave={() => currentUser && setAccountMenuOpen(false)}
          style={{
            '--account-text-color': accountTextColor,
            '--account-divider-color': accountDividerColor,
          }}
        >
          <button
            type="button"
            className="nav-account-trigger"
            title={currentUser ? '會員選單' : t.nav.member}
            aria-haspopup={currentUser ? 'menu' : undefined}
            aria-expanded={currentUser ? accountMenuOpen : undefined}
            onClick={toggleAccountMenu}
          >
            <span className="icon-btn nav-account-icon" aria-hidden="true">
              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
                <circle cx="12" cy="8" r="4" /><path d="M4 21c0-4.4 3.6-8 8-8s8 3.6 8 8" />
              </svg>
            </span>
            <span className="nav-account-name">{currentUser ? currentUser.name : '登入'}</span>
          </button>
          {currentUser && (
            <div className="nav-account-dropdown" role="menu" aria-label="會員選單">
              {memberMenuItems.map(([key, label], index) => (
                <button
                  key={key}
                  type="button"
                  role="menuitem"
                  className={index === 0 ? 'is-active' : ''}
                  onClick={() => goMemberTab(key)}
                >
                  {label}
                </button>
              ))}
              <button type="button" role="menuitem" className="nav-account-dropdown-logout" onClick={handleLogout}>
                登出
              </button>
            </div>
          )}
        </div>
        <button
          className={`mobile-menu-toggle ${mobileMenuOpen ? 'open' : ''}`}
          type="button"
          aria-label={mobileMenuOpen ? '關閉選單' : '開啟選單'}
          aria-expanded={mobileMenuOpen}
          aria-controls="jafun-mobile-menu"
          onClick={() => setMobileMenuOpen(open => !open)}
        >
          <span></span>
          <span></span>
          <span></span>
        </button>
      </div>
      <div id="jafun-mobile-menu" className={`mobile-menu-panel ${mobileMenuOpen ? 'open' : ''}`}>
        {items.map(it => (
          <button key={it.k} type="button" className={active === it.k ? 'active' : ''} onClick={() => goNav(it.k)}>
            <span>{it.label}</span>
            <span>→</span>
          </button>
        ))}
        {currentUser ? (
          <div className="mobile-member-group">
            {memberMenuItems.map(([key, label], index) => (
              <button
                key={key}
                type="button"
                className={`mobile-member-tab ${index === 0 ? 'active' : ''}`}
                onClick={() => goMemberTab(key)}
              >
                <span>{label}</span>
                <span>→</span>
              </button>
            ))}
            <button type="button" className="mobile-member-tab mobile-member-logout" onClick={handleLogout}>
              <span>登出</span>
              <span>→</span>
            </button>
          </div>
        ) : (
          <button type="button" onClick={() => goNav('login')}>
            <span>會員登入</span>
            <span>→</span>
          </button>
        )}
      </div>
    </nav>
  );
}

function Footer({ t, onNav }) {
  const go = (page, props = {}) => (event) => {
    event.preventDefault();
    onNav(page, props);
  };

  return (
    <footer className="footer">
      <div className="footer-grid">
        <div className="footer-brand">
          <Logo light size={48} />
          <p style={{ marginTop: 16, whiteSpace: 'pre-line' }}>{t.footer.tagline}</p>
        </div>
        <div>
          <h5>{t.footer.service}</h5>
          <ul>
            <li><a href="/line" onClick={go('line')}>{t.nav.line}</a></li>
            <li><a href="/subscribe" onClick={go('subscribe')}>{t.nav.subscribe}</a></li>
            <li><a href="/ship" onClick={go('ship')}>{t.nav.ship}</a></li>
            <li><a href="/contact" onClick={go('contact')}>企業合作</a></li>
          </ul>
        </div>
        <div>
          <h5>{t.footer.shop}</h5>
          <ul>
            <li><a href="/shop" onClick={go('shop', { category: 'souvenirs' })}>伴手禮</a></li>
            <li><a href="/shop" onClick={go('shop', { category: 'lifestyle' })}>生活雜貨</a></li>
            <li><a href="/shop" onClick={go('shop', { category: 'toys-models' })}>玩具模型</a></li>
          </ul>
        </div>
        <div>
          <h5>{t.footer.help}</h5>
          <ul>
            <li><a href="/learn" onClick={go('learn')}>購物攻略</a></li>
            <li><a href="/ship" onClick={go('ship')}>運費試算</a></li>
            <li><a href="/mypage" onClick={go('mypage')}>包裹追蹤</a></li>
            <li><a href="/faq" onClick={go('faq')}>常見問題</a></li>
          </ul>
        </div>
        <div>
          <h5>{t.footer.company}</h5>
          <ul>
            <li><a href="/about" onClick={go('about')}>關於 JaFun</a></li>
            <li><a href="/contact" onClick={go('contact')}>聯絡我們</a></li>
            <li><a href="/privacy" onClick={go('privacy')}>隱私權政策</a></li>
            <li><a href="/terms" onClick={go('terms')}>服務條款</a></li>
          </ul>
        </div>
      </div>
      <div className="footer-bot">
        <span>{t.footer.copyright}</span>
        <span>JAFUN.TW　·　TOKYO ⇄ TAIPEI</span>
      </div>
    </footer>
  );
}

function MobileActionBar({ active, onNav, cartCount = 0, currentUser = null }) {
  const [visible, setVisible] = useState(false);
  const [searchOpen, setSearchOpen] = useState(false);
  const [query, setQuery] = useState('');
  const hiddenRoutes = new Set(['product', 'cart', 'login', 'register']);
  const shouldRender = !hiddenRoutes.has(active);

  useEffect(() => {
    const onScroll = () => setVisible(window.scrollY > 260);
    window.addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener('scroll', onScroll);
  }, []);

  useEffect(() => {
    setSearchOpen(false);
  }, [active]);

  if (!shouldRender) return null;

  const submit = (event) => {
    event?.preventDefault?.();
    submitMobileProductSearch(query, onNav);
    setSearchOpen(false);
    setQuery('');
  };

  return (
    <>
      <div className={`mobile-search-sheet ${searchOpen ? 'is-open' : ''}`} aria-hidden={!searchOpen}>
        <form className="mobile-search-sheet-card" onSubmit={submit}>
          <div className="mobile-search-sheet-head">
            <strong>快速搜尋日本商品</strong>
            <button type="button" onClick={() => setSearchOpen(false)} aria-label="關閉搜尋">×</button>
          </div>
          <label>
            <span>商品網址 / 關鍵字</span>
            <input
              value={query}
              onChange={event => setQuery(event.target.value)}
              placeholder="貼日本商品網址，或輸入想找的日本名產"
              autoComplete="off"
            />
          </label>
          <div className="mobile-search-sheet-actions">
            <button type="submit">開始搜尋</button>
            <button type="button" onClick={() => { setSearchOpen(false); onNav('line'); }}>貼網址報價</button>
          </div>
        </form>
      </div>
      <div className={`mobile-action-footer ${visible || searchOpen ? 'is-visible' : ''}`}>
        <button type="button" onClick={() => setSearchOpen(true)}>
          <svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="11" cy="11" r="7" /><path d="M21 21l-4.3-4.3" /></svg>
          <span>搜尋</span>
        </button>
        <button type="button" onClick={() => onNav('cart')} className="mobile-action-cart">
          <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M3 3h2l2.4 12.2a2 2 0 002 1.8h9.5a2 2 0 002-1.6L22 7H6" /><circle cx="9" cy="20" r="1.5" /><circle cx="18" cy="20" r="1.5" /></svg>
          {cartCount > 0 && <em>{Math.min(cartCount, 99)}</em>}
          <span>購物車</span>
        </button>
        <button type="button" onClick={() => onNav(currentUser ? 'mypage' : 'login')}>
          <svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><circle cx="12" cy="8" r="4" /><path d="M4 21c0-4.4 3.6-8 8-8s8 3.6 8 8" /></svg>
          <span>{currentUser ? '會員' : '登入'}</span>
        </button>
        <button type="button" onClick={() => window.dispatchEvent(new CustomEvent('jafun:chatbot-open'))}>
          <svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" /></svg>
          <span>AI客服</span>
        </button>
      </div>
    </>
  );
}

function HeroCollage({ tiles }) {
  return (
    <div className="hero-grid">
      {tiles.map((src, i) => (
        <div key={i} className="hero-tile">
          <img src={optimizeImageUrl(src, 560, 72)} alt="" loading="lazy" decoding="async" />
        </div>
      ))}
    </div>
  );
}

function HeroSection({ t, onNav, onSearchSubmit, searchBusy = false }) {
  const [tab, setTab] = useState(0);
  const [val, setVal] = useState('');
  const submit = () => {
    const query = val.trim();
    if (!query) {
      onNav(tab === 0 ? 'line' : 'shop');
      return;
    }
    if (onSearchSubmit) {
      onSearchSubmit(query, tab === 0 ? 'url' : 'keyword');
      return;
    }
    onNav('line', { url: query });
  };
  const placeholder = tab === 0
    ? '貼上日本商品網址，立即估算總報價…'
    : '輸入需求，AI 幫你找語意接近的日本商品…';
  return (
    <section className="hero" data-screen-label="01 Home — Hero">
      <HeroCollage tiles={window.HERO_TILES} />
      <div className="hero-card">
        <div className="eyebrow">{t.hero.eyebrow}</div>
        <h1 style={{ whiteSpace: 'pre-line' }}>{t.hero.title}</h1>
        <p style={{ whiteSpace: 'pre-line' }}>{t.hero.sub}</p>
        <div className="hero-search">
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#8a7f70" strokeWidth="1.5"><circle cx="11" cy="11" r="7" /><path d="M21 21l-4.3-4.3" /></svg>
          <input value={val} onChange={e => setVal(e.target.value)} placeholder={placeholder || t.hero.placeholder} onKeyDown={e => e.key === 'Enter' && submit()} />
          <button className="hero-search-btn" onClick={submit} disabled={searchBusy} title={searchBusy ? '搜尋中' : '送出'}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M5 12h14M13 5l7 7-7 7" /></svg>
          </button>
        </div>
        <div className="hero-tabs">
          {t.hero.tabs.map((tab_, i) => (
            <span key={i} className={tab === i ? 'active' : ''} onClick={() => setTab(i)}>{tab_}</span>
          ))}
        </div>
      </div>
      <div className="hero-stamp">
        <div>
          <div className="jp">{t.hero.stamp[0]}</div>
          <div className="small">{t.hero.stamp[1]}</div>
        </div>
      </div>
    </section>
  );
}

const PRODUCT_TAGS = {
  1: ['網紅推薦', '洋菓子'],
  2: ['抹茶', '巧克力'],
  3: ['東京限定', '蛋糕'],
  4: ['沖繩限定', '和菓子'],
  5: ['期間限定', '甜點'],
  6: ['北海道', '巧克力'],
  7: ['名古屋', '和菓子'],
  8: ['福岡名物', '伴手禮'],
};

const REGION_EN_MAP = {
  '北海道': 'HOKKAIDO',
  '東京': 'TOKYO',
  '京都': 'KYOTO',
  '大阪': 'OSAKA',
  '名古屋': 'NAGOYA',
  '福岡': 'FUKUOKA',
  '沖繩': 'OKINAWA',
  '神戶': 'KOBE',
  '奈良': 'NARA',
  '廣島': 'HIROSHIMA',
  '仙台': 'SENDAI',
  '札幌': 'SAPPORO',
  '橫濱': 'YOKOHAMA',
  '日本': 'JAPAN',
};

// 統一地區標籤為「中文 ENGLISH」；後台英文欄位缺漏或誤填中文時自動補上對照表英文
function formatRegionLabel(region = '') {
  const raw = String(region || '').trim();
  if (!raw) return '日本 JAPAN';
  const [zh, ...rest] = raw.split(/\s+/);
  const restLabel = rest.join(' ').trim();
  const hasLatin = /[A-Za-z]/.test(restLabel);
  const en = hasLatin ? restLabel.toUpperCase() : (REGION_EN_MAP[zh] || '');
  return en ? `${zh} ${en}` : zh;
}

function productRegionLabel(region = '') {
  return formatRegionLabel(region);
}

function FavoriteMemberPrompt({ product, onClose, onCompleteFavorite }) {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const primaryActionRef = useRef(null);
  const titleId = 'favorite-member-prompt-title';

  useEffect(() => {
    const previousOverflow = document.body.style.overflow;
    const focusTimer = window.setTimeout(() => primaryActionRef.current?.focus(), 0);
    const handleKeyDown = (event) => {
      if (event.key === 'Escape') onClose();
    };
    document.body.style.overflow = 'hidden';
    window.addEventListener('keydown', handleKeyDown);
    return () => {
      window.clearTimeout(focusTimer);
      window.removeEventListener('keydown', handleKeyDown);
      document.body.style.overflow = previousOverflow;
    };
  }, [onClose]);

  const startLineRegister = async () => {
    setLoading(true);
    setError('');
    try {
      writePendingFavoriteProduct(product);
      const returnUrl = favoriteAuthReturnUrl();
      const config = await window.authRequest?.(`/auth/line/login-url?returnUrl=${encodeURIComponent(returnUrl)}`);
      if (config?.configured && config.url) {
        window.location.href = config.url;
        return;
      }
      if (config?.devLoginEndpoint) {
        const auth = await window.authRequest(config.devLoginEndpoint, {
          method: 'POST',
          body: { displayName: 'LINE 使用者', email: 'line-dev@jafun.local' },
        });
        window.storeAuthSession?.(auth);
        onCompleteFavorite?.();
        onClose();
        return;
      }
      throw new Error('LINE 登入目前無法啟動，請改用 Email 註冊或稍後再試。');
    } catch (authError) {
      setError(authError?.message || 'LINE 登入目前無法啟動，請稍後再試。');
      clearPendingFavoriteProduct();
    } finally {
      setLoading(false);
    }
  };

  const modal = (
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby={titleId}
      onClick={event => {
        event.stopPropagation();
        onClose();
      }}
      style={{
        position: 'fixed',
        inset: 0,
        zIndex: 10020,
        background: 'rgba(20,20,20,.56)',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        padding: 20,
        backdropFilter: 'blur(4px)',
      }}
    >
      <div
        onClick={event => event.stopPropagation()}
        style={{
          width: 'min(520px, 100%)',
          background: '#fff',
          border: '1px solid var(--jf-line)',
          boxShadow: '0 34px 80px -26px rgba(0,0,0,.45)',
        }}
      >
        <div style={{ padding: '34px 38px 28px', borderBottom: '1px solid var(--jf-line)' }}>
          <div style={{ fontFamily: 'var(--sans)', fontSize: 11, letterSpacing: 4, color: 'var(--jf-red)', marginBottom: 14 }}>
            MEMBER FAVORITE
          </div>
          <h2 id={titleId} style={{ fontFamily: 'var(--serif)', fontSize: 28, lineHeight: 1.35, fontWeight: 500, letterSpacing: 2, margin: 0 }}>
            登入會員後收藏商品
          </h2>
          <p style={{ fontFamily: 'var(--serif)', fontSize: 14, lineHeight: 1.9, color: 'var(--jf-mute-2)', margin: '16px 0 0' }}>
            用 LINE 註冊 / 登入後，可以把喜歡的日本商品加入收藏，之後在會員中心快速查看、下單與追蹤。
          </p>
        </div>
        <div style={{ padding: '26px 38px 34px' }}>
          {product && (
            <div style={{ display: 'grid', gridTemplateColumns: '72px 1fr', gap: 14, alignItems: 'center', background: 'var(--jf-paper-2)', padding: 12, marginBottom: 18 }}>
              <img
                src={imageVariantUrl(product.imageSizes, 'card', product.img)}
                alt={product.name}
                style={{ width: 72, height: 72, objectFit: 'cover', background: '#fff' }}
              />
              <div>
                <div style={{ fontFamily: 'var(--serif)', fontSize: 15, lineHeight: 1.5, color: 'var(--jf-ink)' }}>{product.name}</div>
                <div style={{ fontFamily: 'var(--sans)', fontSize: 11, color: 'var(--jf-mute)', marginTop: 4 }}>收藏後會保存在會員中心</div>
              </div>
            </div>
          )}
          <button
            type="button"
            ref={primaryActionRef}
            className="btn"
            disabled={loading}
            onClick={startLineRegister}
            style={{ width: '100%', justifyContent: 'center', padding: '14px 0', background: 'var(--jf-line-green)', color: '#fff', opacity: loading ? .68 : 1 }}
          >
            {loading ? '連線中...' : '使用 LINE 註冊 / 登入'}
          </button>
          <button
            type="button"
            className="btn btn-ghost"
            onClick={() => {
              writePendingFavoriteProduct(product);
              window.location.href = '/register';
            }}
            style={{ width: '100%', justifyContent: 'center', padding: '12px 0', marginTop: 10 }}
          >
            Email 註冊 / 登入
          </button>
          {error && (
            <div style={{ marginTop: 14, padding: 12, background: 'var(--jf-paper-2)', color: 'var(--jf-red)', fontFamily: 'var(--serif)', fontSize: 13, lineHeight: 1.8 }}>
              {error}
            </div>
          )}
          <button
            type="button"
            onClick={onClose}
            style={{ width: '100%', marginTop: 18, fontFamily: 'var(--sans)', fontSize: 12, letterSpacing: 2, color: 'var(--jf-mute)' }}
          >
            先不用，繼續逛商品
          </button>
        </div>
      </div>
    </div>
  );

  if (typeof ReactDOM !== 'undefined' && ReactDOM.createPortal && typeof document !== 'undefined') {
    return ReactDOM.createPortal(modal, document.body);
  }

  return modal;
}

function ProductCard({ product, onClick, favorite, onToggleFavorite }) {
  const tags = product.tags?.length ? product.tags : (PRODUCT_TAGS[product.id] || ['日本直送']);
  const [localFavorite, setLocalFavorite] = useState(() => isFavoriteProduct(product));
  const [showMemberPrompt, setShowMemberPrompt] = useState(false);
  const isFavorite = typeof favorite === 'boolean' ? favorite : localFavorite;
  const productId = favoriteProductId(product);

  const completeFavorite = useCallback(() => {
    const result = onToggleFavorite
      ? onToggleFavorite(product, true)
      : toggleFavoriteProduct(product, true);
    if (result && typeof result.isFavorite === 'boolean') {
      setLocalFavorite(result.isFavorite);
    }
  }, [onToggleFavorite, product]);

  const clearCurrentPendingFavorite = useCallback(() => {
    const pendingId = favoriteProductId(readPendingFavoriteProduct());
    if (pendingId && pendingId === productId) {
      clearPendingFavoriteProduct();
    }
  }, [productId]);

  useEffect(() => {
    setLocalFavorite(isFavoriteProduct(product));
  }, [product?.id, product?.sku, product?.slug]);

  useEffect(() => {
    const onFavoritesChanged = () => setLocalFavorite(isFavoriteProduct(product));
    window.addEventListener('jafun:favorites-changed', onFavoritesChanged);
    window.addEventListener('storage', onFavoritesChanged);
    return () => {
      window.removeEventListener('jafun:favorites-changed', onFavoritesChanged);
      window.removeEventListener('storage', onFavoritesChanged);
    };
  }, [product?.id, product?.sku, product?.slug]);

  useEffect(() => {
    if (!isMemberLoggedIn()) return;
    const pending = readPendingFavoriteProduct();
    const pendingId = favoriteProductId(pending);
    if (!pendingId || pendingId !== productId) return;
    completeFavorite();
    clearPendingFavoriteProduct();
  }, [completeFavorite, productId]);

  const handleFavoriteClick = (event) => {
    event.stopPropagation();
    const nextFavorite = !isFavorite;
    if (nextFavorite && !isMemberLoggedIn()) {
      setShowMemberPrompt(true);
      return;
    }
    const result = onToggleFavorite
      ? onToggleFavorite(product, nextFavorite)
      : toggleFavoriteProduct(product, nextFavorite);
    if (result && typeof result.isFavorite === 'boolean') {
      setLocalFavorite(result.isFavorite);
    }
  };

  const closeMemberPrompt = useCallback(() => {
    clearCurrentPendingFavorite();
    setShowMemberPrompt(false);
  }, [clearCurrentPendingFavorite]);

  return (
    <article className="product-card" onClick={onClick}>
      <div className="pic">
        <button
          className={`product-heart ${isFavorite ? 'active' : ''}`}
          type="button"
          aria-label={isFavorite ? '取消收藏商品' : '收藏商品'}
          aria-pressed={isFavorite}
          title={isFavorite ? '取消收藏' : '收藏商品'}
          onClick={handleFavoriteClick}
        >
          <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
            <path d="M20.8 4.6a5.3 5.3 0 0 0-7.5 0L12 5.9l-1.3-1.3a5.3 5.3 0 1 0-7.5 7.5l1.3 1.3L12 21l7.5-7.6 1.3-1.3a5.3 5.3 0 0 0 0-7.5z" />
          </svg>
        </button>
        <img
          src={imageVariantUrl(product.imageSizes, 'card', product.img)}
          srcSet={imageVariantSrcSet(product.imageSizes)}
          sizes="(max-width: 720px) 50vw, 25vw"
          alt={product.name}
          loading="lazy"
          decoding="async"
        />
      </div>
      <div className="product-body">
        <div className="pname">{product.name}</div>
        <div className="pdesc">{product.jname}</div>
        <div className="price">
          <span className="ntd">NT$ {product.price}</span>
          <span className="jpy">¥{product.jpy.toLocaleString()}</span>
        </div>
        <div className="region">
          <svg width="17" height="17" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
            <path d="M12 2a7 7 0 0 0-7 7c0 5.2 7 13 7 13s7-7.8 7-13a7 7 0 0 0-7-7Zm0 9.5A2.5 2.5 0 1 1 12 6a2.5 2.5 0 0 1 0 5.5Z" />
          </svg>
          {productRegionLabel(product.region)}
        </div>
        <div className="product-tags">
          <svg width="17" height="17" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
            <path d="M20.6 13.1 12 21.7 2.3 12V2.3H12l8.6 8.6a1.6 1.6 0 0 1 0 2.2ZM7 8.2A1.7 1.7 0 1 0 7 4.8a1.7 1.7 0 0 0 0 3.4Z" />
          </svg>
          {tags.map(tag => <span key={tag}>{tag}</span>)}
        </div>
      </div>
      {showMemberPrompt && (
        <FavoriteMemberPrompt
          product={product}
          onClose={closeMemberPrompt}
          onCompleteFavorite={completeFavorite}
        />
      )}
    </article>
  );
}

Object.assign(window, {
  JAFUN_LINE_OFFICIAL_ID,
  JAFUN_LINE_OFFICIAL_URL,
  openLineOfficial,
  validateTaiwanRecipient,
  FieldErrorText,
  formatRegionLabel,
  submitMobileProductSearch,
  favoriteProductId,
  readFavoriteProducts,
  readPendingFavoriteProduct,
  writePendingFavoriteProduct,
  clearPendingFavoriteProduct,
  completePendingFavoriteProduct,
  readPendingGuestOrder,
  writePendingGuestOrder,
  clearPendingGuestOrder,
  completePendingGuestOrder,
  isMemberLoggedIn,
  toggleFavoriteProduct,
  isFavoriteProduct,
  FavoriteMemberPrompt,
  Logo,
  Nav,
  Footer,
  MobileActionBar,
  HeroCollage,
  HeroSection,
  ProductCard,
});
