/**
 * Home / Category / PDP / Culture screens — data bound to API.
 */

// ----------------------------------------------------------------------------
// PickCarousel — horizontal strip of hero "picks" with one big slide + a
// thumbnail strip. Each pick can override its cover image, title, subtitle,
// and CTA text. Clicking a slide opens the referenced PDP (or a custom target).
// ----------------------------------------------------------------------------
window.PickCarousel = function PickCarousel({ picks, lang, onNav, titleFallback }) {
  const [idx, setIdx] = React.useState(0);
  if (!Array.isArray(picks) || picks.length === 0) return null;
  const safeIdx = Math.min(Math.max(0, idx), picks.length - 1);
  const cur = picks[safeIdx];
  const goto = (i) => setIdx(((i % picks.length) + picks.length) % picks.length);
  const onCta = () => {
    const target = cur.cta_target || '';
    if (target.startsWith('#/section/')) onNav('section', null, target.slice('#/section/'.length));
    else if (target) onNav(target);
    else if (cur.product) onNav('pdp', cur.product.id);
  };
  return (
    <section className="grid-section" style={{ padding: '24px 48px 36px' }}>
      <div className="grid-head">
        <div>
          <h2 className="grid-title">{titleFallback || (lang === 'zh' ? '编辑推荐' : "Editors' picks")}</h2>
          <div className="grid-sub">{picks.length} {lang === 'zh' ? '件精选' : 'curated picks'}</div>
        </div>
        {picks.length > 1 && (
          <div style={{ display: 'inline-flex', gap: 6 }}>
            <button className="btn-ghost" onClick={() => goto(safeIdx - 1)} style={{ padding: '6px 12px' }}>‹</button>
            <button className="btn-ghost" onClick={() => goto(safeIdx + 1)} style={{ padding: '6px 12px' }}>›</button>
          </div>
        )}
      </div>
      <div style={{
        display: 'grid',
        gridTemplateColumns: 'minmax(0, 1.2fr) minmax(0, 1fr)',
        gap: 28,
        alignItems: 'stretch',
        background: 'var(--bg-alt)',
        border: '1px solid var(--border)',
        borderRadius: 'var(--radius-lg)',
        overflow: 'hidden',
      }}>
        <div
          style={{
            cursor: 'pointer', aspectRatio: '4 / 3',
            background: cur.image_url ? `center/cover no-repeat url(${cur.image_url})` : '#eee',
            minHeight: 340,
          }}
          onClick={() => cur.product && onNav('pdp', cur.product.id)}
        >
          {!cur.image_url && cur.product ? (
            <div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
              <div style={{ width: '70%', height: '80%' }}><ProductVisual product={cur.product}/></div>
            </div>
          ) : null}
        </div>
        <div style={{ padding: '36px 36px 36px 0', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
          <div style={{ fontSize: 11, letterSpacing: '0.26em', textTransform: 'uppercase', color: 'var(--fg-muted)', marginBottom: 14 }}>
            {lang === 'zh' ? '编辑精选' : "Editor's pick"} · {String(safeIdx + 1).padStart(2, '0')} / {String(picks.length).padStart(2, '0')}
          </div>
          <h2 className="hero-title" style={{ fontSize: 38, lineHeight: 1.15, marginBottom: 14 }}>{cur.title}</h2>
          {cur.subtitle ? <p className="hero-sub" style={{ marginBottom: 22, maxWidth: 520 }}>{cur.subtitle}</p> : null}
          <div style={{ display: 'inline-flex', gap: 10, alignItems: 'center' }}>
            <button className="btn-primary" onClick={onCta}>{cur.cta || (lang === 'zh' ? '立即查看' : 'Shop now')}</button>
            {cur.product && (
              <span style={{ fontSize: 13, color: 'var(--fg-muted)' }}>
                {cur.product.name && (cur.product.name[lang] || cur.product.name.en)} · ${cur.product.price}
              </span>
            )}
          </div>
        </div>
      </div>
      {picks.length > 1 && (
        <div style={{ display: 'flex', gap: 10, marginTop: 16, flexWrap: 'wrap' }}>
          {picks.map((p, i) => (
            <div key={i}
              onClick={() => goto(i)}
              style={{
                width: 96, aspectRatio: '1 / 1',
                cursor: 'pointer', borderRadius: 4, overflow: 'hidden',
                border: i === safeIdx ? '2px solid var(--accent)' : '1px solid var(--border)',
                background: p.image_url ? `center/cover no-repeat url(${p.image_url})` : '#f2ede5',
                opacity: i === safeIdx ? 1 : 0.85,
              }}
              title={p.title}
            >
              {!p.image_url && p.product ? (
                <div style={{ width: '100%', height: '100%' }}><ProductVisual product={p.product}/></div>
              ) : null}
            </div>
          ))}
        </div>
      )}
    </section>
  );
};

window.HomeScreen = function HomeScreen({ lang, onNav, onAdd, channel }) {
  // "Featured this week" (精选上新) area is driven by either the legacy
  // `featured` boolean column *or* the new "featured" (精选) tag, so admins
  // can toggle it either from the product editor checkbox or the tag picker.
  // Products are ranked by: (1) has `featured` tag, (2) `featured=true`,
  // (3) newer created_at first — so the area rewards both promotion level
  // and recency ("上架程度和打标签程度").
  const allProds = (window.YY_PRODUCTS || []);
  const isFeat = p => p.featured === true || (p.tag_codes || []).includes('featured');
  const featuredRanked = allProds
    .filter(isFeat)
    .slice()
    .sort((a, b) => {
      const ta = (a.tag_codes || []).includes('featured') ? 1 : 0;
      const tb = (b.tag_codes || []).includes('featured') ? 1 : 0;
      if (ta !== tb) return tb - ta;
      const ba = a.featured ? 1 : 0;
      const bb = b.featured ? 1 : 0;
      if (ba !== bb) return bb - ba;
      return String(b.created_at || '').localeCompare(String(a.created_at || ''));
    });
  const featured = featuredRanked.slice(0, 8);
  const cats = (window.YY_CATEGORIES || []).slice(0, 8).map(c => {
    const marks = { hanfu:'衣', tea:'茶', porcelain:'瓷', jade:'玉', stationery:'墨', craft:'艺', beauty:'妆', gift:'礼', fashion:'服' };
    return { id: c.code, name_i18n: c.name_i18n, mark: marks[c.code] || c.code[0].toUpperCase() };
  });

  // Aggregate picks across active site sections (layout='home' only) so the
  // home page carousel reflects whatever curators set up in the admin. Falls
  // back silently to nothing (and the page just omits the carousel).
  const pickL = (obj) => (obj && (obj[lang] || obj.en)) || '';
  const byProd = new Map((window.YY_PRODUCTS || []).map(p => [p.id, p]));
  const homePicks = ((window.YY_SITE_SECTIONS || []).filter(s => s.active && (s.layout || 'home') === 'home'))
    .flatMap(s => {
      const hero = s.hero || {};
      const raw = Array.isArray(hero.picks) && hero.picks.length
        ? hero.picks
        : (Array.isArray(hero.product_ids) ? hero.product_ids.map(pid => ({ product_id: pid })) : []);
      return raw.map(pk => {
        const prod = pk && pk.product_id ? byProd.get(pk.product_id) : null;
        if (!prod) return null;
        return {
          product: prod,
          image_url: (pk && pk.image_url) || prod.image_url || null,
          title:    (pk && pickL(pk.title_i18n))    || pickL(prod.name) || prod.id,
          subtitle: (pk && pickL(pk.subtitle_i18n)) || '',
          cta:      (pk && pickL(pk.cta_label_i18n)) || '',
          cta_target: (pk && pk.cta_target) || ('#/section/' + s.id),
        };
      }).filter(Boolean);
    });
  // De-dupe by product id so the same featured item doesn't repeat.
  const seen = new Set();
  const homePicksDedup = homePicks.filter(p => {
    const key = p.product && p.product.id;
    if (!key || seen.has(key)) return false;
    seen.add(key);
    return true;
  });

  // If no configured picks, fall back to the first 4 featured products so the
  // page always has a top hero carousel.
  const fallbackPicks = featured.slice(0, 4).map(prod => ({
    product: prod,
    image_url: prod.image_url || null,
    title:    pickL(prod.name) || prod.id,
    subtitle: '',
    cta:      '',
    cta_target: '',
  }));
  const topPicks = homePicksDedup.length > 0 ? homePicksDedup : fallbackPicks;

  return (
    <div className="screen-body">
      {topPicks.length > 0 && (
        <PickCarousel picks={topPicks} lang={lang} onNav={onNav}
                      titleFallback={lang === 'zh' ? '首页精选 · 推荐位' : "Home spotlight"}/>
      )}

      <section className="cat-strip">
        {cats.map(c => (
          <div key={c.id} className="cat-item" onClick={() => c.id === 'hanfu' ? onNav('culture') : onNav('category', null, c.id)}>
            <div className="cat-dot">{c.mark}</div>
            <div>{(c.name_i18n && (c.name_i18n[lang] || c.name_i18n.en)) || c.id}</div>
          </div>
        ))}
      </section>

      <section className="grid-section">
        <div className="grid-head">
          <div>
            <h2 className="grid-title">{lang === 'zh' ? '精选上新' : lang === 'ar' ? 'مختارات جديدة' : 'Featured this week'}</h2>
            <div className="grid-sub">
              {lang === 'zh' ? `编辑精选 · ${featuredRanked.length} 件` : `Editors' picks · ${featuredRanked.length} items`}
            </div>
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <span className="chip">All</span>
            <span className="chip" style={{ opacity: 0.5 }}>Fashion</span>
            <span className="chip" style={{ opacity: 0.5 }}>Culture</span>
            <span className="chip" style={{ opacity: 0.5 }}>Beauty</span>
          </div>
        </div>
        <div className="grid">
          {featured.map(p => (
            <ProductCard key={p.id} product={p} lang={lang} onClick={() => onNav('pdp', p.id)} />
          ))}
        </div>
      </section>

      {/* Below the 精选上新 hero area: render each top-level category as its own
          region, the same pattern used by every other channel/section. */}
      {(() => {
        // Synthesize include rules so the category order matches the cat-strip
        // ordering above (which itself comes from YY_CATEGORIES ordering).
        const topInclude = cats.map(c => ({ type: 'category', code: c.id, include_descendants: true }));
        const syntheticSection = { include: topInclude, hero: { product_sort: 'default' } };
        const groups = window.__groupByTopCat(allProds, syntheticSection, lang);
        return groups.map(g => (
          <section className="grid-section" key={'home-cat-' + g.code}>
            <div className="grid-head">
              <div>
                <h2 className="grid-title">{g.name}</h2>
                <div className="grid-sub">{g.products.length} {lang === 'zh' ? '件商品' : 'items'}</div>
              </div>
              <button
                className="btn-ghost"
                onClick={() => onNav('category', null, g.code)}
                style={{ padding: '6px 14px' }}
              >
                {lang === 'zh' ? '查看全部' : 'View all'} →
              </button>
            </div>
            <div className="grid">
              {g.products.slice(0, 8).map(p => (
                <ProductCard key={p.id} product={p} lang={lang} onClick={() => onNav('pdp', p.id)} />
              ))}
            </div>
          </section>
        ));
      })()}

      <Footer lang={lang} />
    </div>
  );
};

window.CategoryScreen = function CategoryScreen({ lang, onNav, categoryFilter }) {
  const all = (window.YY_PRODUCTS || []);

  // Virtual filters that don't map to a real category code:
  //   __sale — products with was_price (discounted)
  //   __new  — recently featured / newest
  //   __home — home & lifestyle (porcelain / craft / tea / gift)
  let products;
  let title;
  const virtual = {
    __sale: { match: p => p.was != null, title: { en: 'Sale', zh: '促销' } },
    __new:  { match: p => p.featured,    title: { en: 'New Arrivals', zh: '新品上市' } },
    __home: { match: p => ['porcelain','craft','tea','gift'].includes(p.cat), title: { en: 'Home & Living', zh: '家居生活' } },
  };
  if (virtual[categoryFilter]) {
    products = all.filter(virtual[categoryFilter].match);
    title = virtual[categoryFilter].title[lang] || virtual[categoryFilter].title.en;
  } else if (categoryFilter) {
    products = all.filter(p => p.cat === categoryFilter);
    const cat = (window.YY_CATEGORIES || []).find(c => c.code === categoryFilter);
    title = (cat && cat.name_i18n && (cat.name_i18n[lang] || cat.name_i18n.en)) || categoryFilter;
  } else {
    products = all.filter(p => ['fashion','beauty','hanfu'].includes(p.cat));
    title = t('nav.fashion', lang);
  }
  const colors = Array.from(new Set(all.map(p => p.color))).slice(0, 8);

  return (
    <div className="screen-body">
      <div className="listing">
        <aside className="filters">
          <div className="filter-group">
            <h5>Category</h5>
            {(window.YY_CATEGORIES || []).slice(0, 8).map(c => (
              <label key={c.code} className="filter-option" onClick={() => onNav('category', null, c.code)} style={{ cursor: 'pointer' }}>
                <span className={'cb ' + (c.code === categoryFilter ? 'on' : '')}/>
                {(c.name_i18n && (c.name_i18n[lang] || c.name_i18n.en)) || c.code}
              </label>
            ))}
          </div>
          <div className="filter-group">
            <h5>Price</h5>
            <label className="filter-option"><span className="cb"/>Under $50</label>
            <label className="filter-option"><span className="cb on"/>$50 – $150</label>
            <label className="filter-option"><span className="cb"/>$150 – $300</label>
            <label className="filter-option"><span className="cb"/>$300+</label>
          </div>
          <div className="filter-group">
            <h5>Color</h5>
            <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
              {colors.map(c => (
                <div key={c} style={{ width: 26, height: 26, borderRadius: '50%', background: c, border: '1px solid var(--border)', cursor: 'pointer' }}/>
              ))}
            </div>
          </div>
          <div className="filter-group">
            <h5>Material</h5>
            <label className="filter-option"><span className="cb"/>Silk</label>
            <label className="filter-option"><span className="cb"/>Linen</label>
            <label className="filter-option"><span className="cb"/>Cotton</label>
            <label className="filter-option"><span className="cb"/>Wool</label>
          </div>
          <div className="filter-group">
            <h5>Origin</h5>
            <label className="filter-option"><span className="cb on"/>Made in China</label>
            <label className="filter-option"><span className="cb"/>Made in Indonesia</label>
            <label className="filter-option"><span className="cb"/>Made in Vietnam</label>
          </div>
        </aside>

        <main className="listing-main">
          <div className="listing-crumbs">Home / Women / {title}</div>
          <div className="listing-head">
            <div>
              <h1 className="listing-title">{title}</h1>
              <div className="listing-count">{products.length} {lang === 'zh' ? '件商品' : 'items'} · sorted by popularity</div>
            </div>
            <div style={{ display: 'flex', gap: 8 }}>
              <span className="chip">Popular ▾</span>
              <span className="chip" style={{ opacity: 0.6 }}>▦ Grid</span>
            </div>
          </div>
          <div className="grid">
            {products.map(p => (
              <ProductCard key={p.id} product={p} lang={lang} onClick={() => onNav('pdp', p.id)} />
            ))}
          </div>
        </main>
      </div>
    </div>
  );
};

window.PDPScreen = function PDPScreen({ lang, productId, onNav, onAdd }) {
  const product = (window.YY_PRODUCTS || []).find(p => p.id === productId) || (window.YY_PRODUCTS || [])[0];
  const [size, setSize]       = React.useState('M');
  const [colorIdx, setColor]  = React.useState(0);
  const [active, setActive]   = React.useState(0);
  const [tab, setTab]         = React.useState('desc');

  if (!product) return <div className="screen-body"><div style={{ padding: 60 }}>Loading…</div></div>;

  // Normalise colors to objects — legacy rows may contain plain hex strings.
  const rawColors = (product.colors && product.colors.length)
    ? product.colors
    : [{ hex: product.color, name_i18n: { en: 'Default', zh: '默认' } }];
  const colors = rawColors.map(c => (typeof c === 'string' ? { hex: c } : (c || {})));
  const currentColor = colors[colorIdx] || colors[0] || {};
  const currentColorName = (currentColor.name_i18n && (currentColor.name_i18n[lang] || currentColor.name_i18n.en)) || '—';

  // Color → image linkage: if the selected color variant defines its own
  // image_url/gallery_urls, those replace the product-level media. Otherwise
  // fall back to the product's default image set.
  const colorMain    = currentColor.image_url || null;
  const colorGalleryArr = Array.isArray(currentColor.gallery_urls) && currentColor.gallery_urls.length
    ? currentColor.gallery_urls.filter(Boolean)
    : [];
  const baseMain    = product.image_url || null;
  const baseGallery = (product.gallery && product.gallery.length) ? product.gallery : null;

  // Build the visible gallery. The color's primary image MUST be the first
  // thumbnail so it's selected by default; gallery_urls follow (deduped against
  // the primary so we never show the same image twice). If the color has no
  // media at all, fall back to the product-level gallery / image.
  const gallery = (() => {
    const out = [];
    if (colorMain) out.push(colorMain);
    for (const u of colorGalleryArr) {
      if (u && u !== colorMain && !out.includes(u)) out.push(u);
    }
    if (out.length > 0) return out;
    if (baseGallery && baseGallery.length) {
      if (baseMain && !baseGallery.includes(baseMain)) return [baseMain, ...baseGallery];
      return baseGallery;
    }
    if (baseMain) return [baseMain, baseMain, baseMain, baseMain];
    return [];
  })();

  // Keep `active` inside the current gallery bounds when colour swap shrinks it.
  React.useEffect(() => {
    if (active >= gallery.length && gallery.length > 0) setActive(0);
  }, [colorIdx, gallery.length]); // eslint-disable-line

  // Sub-product shape for main image: when changing colour, tint SVG fallback.
  const tintedProduct = { ...product, color: currentColor.hex };

  const get = (obj) => (obj && (obj[lang] || obj.en)) || '';
  const desc     = get(product.desc);
  const material = get(product.material);
  const fit      = get(product.fit);
  const care     = get(product.care);

  // Tab contents
  const tabs = [
    { id: 'desc',     label: { en: 'Description', zh: '商品介绍' } },
    { id: 'material', label: { en: 'Material',    zh: '材质成分' } },
    { id: 'fit',      label: { en: 'Fit & Size',  zh: '尺码版型' } },
    { id: 'care',     label: { en: 'Care',        zh: '保养建议' } },
  ].filter(tb => {
    if (tb.id === 'material') return !!material;
    if (tb.id === 'fit')      return !!fit;
    if (tb.id === 'care')     return !!care;
    return true;
  });
  const tabLabel = (tb) => tb.label[lang] || tb.label.en;
  const tabBody = {
    desc: desc || (lang === 'zh' ? '采用传统工艺结合现代剪裁，精选天然面料。' : 'Crafted with heritage techniques and contemporary tailoring.'),
    material, fit, care,
  }[tab];

  return (
    <div className="screen-body">
      <div className="pdp">
        {/* ---------- GALLERY ---------- */}
        <div className="pdp-gallery">
          <div className="pdp-thumbs">
            {gallery.length
              ? gallery.map((src, i) => (
                  <div key={i}
                    className={'pdp-thumb ' + (i === active ? 'on' : '')}
                    style={{ cursor: 'pointer', background: '#f2ede5', overflow: 'hidden' }}
                    onClick={() => setActive(i)}>
                    <img src={src} alt="" loading="lazy"
                      style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
                      onError={(e) => { e.currentTarget.style.display = 'none'; }}/>
                  </div>
                ))
              : [0,1,2,3].map(i => (
                  <div key={i} className={'pdp-thumb ' + (i === active ? 'on' : '')} onClick={() => setActive(i)}>
                    <div style={{ width: '80%', height: '80%' }}><ProductVisual product={tintedProduct}/></div>
                  </div>
                ))
            }
          </div>
          <div className="pdp-main-img">
            {gallery.length
              ? <img
                  src={gallery[active]} alt=""
                  style={{ maxWidth: '90%', maxHeight: '92%', objectFit: 'contain', borderRadius: 'var(--radius-lg)' }}
                  onError={(e) => { e.currentTarget.style.display = 'none'; }}/>
              : <div style={{ width: '75%', height: '90%' }}><ProductVisual product={tintedProduct}/></div>}
          </div>
        </div>

        {/* ---------- INFO ---------- */}
        <div className="pdp-info">
          <div className="pdp-crumb">
            <a onClick={() => onNav('home')} style={{cursor:'pointer'}}>Home</a>
            {' / '}
            <a onClick={() => onNav('category', null, product.cat)} style={{cursor:'pointer'}}>{product.cat}</a>
            {product.subcat ? ' / ' + product.subcat : ''}
          </div>
          <h1 className="pdp-name">{product.name[lang] || product.name.en}</h1>
          <div className="pdp-meta">
            <span>★★★★★ 4.8 (214)</span>
            <span>SKU: YY-{product.id.toUpperCase()}</span>
            <span>{product.stock > 0 ? '✓ In stock' : '✗ Out of stock'}</span>
          </div>
          <div className="pdp-price">
            ${product.price}
            {product.was && <span className="was">${product.was}</span>}
            {product.was && <span className="pcard-save" style={{ marginLeft: 10, fontSize: 12, color: 'var(--danger)', letterSpacing: '0.06em' }}>
              − ${(product.was - product.price).toFixed(0)} OFF
            </span>}
          </div>

          {colors.length > 0 && (
            <div className="pdp-section">
              <h4>{lang === 'zh' ? '颜色 · ' : 'Color · '}{currentColorName}</h4>
              <div className="swatches">
                {colors.map((c, i) => (
                  <div key={i}
                    title={(c.name_i18n && (c.name_i18n[lang] || c.name_i18n.en)) || ''}
                    className={'swatch ' + (i === colorIdx ? 'on' : '')}
                    style={{ background: c.hex }}
                    onClick={() => setColor(i)}/>
                ))}
              </div>
            </div>
          )}

          <div className="pdp-section">
            <h4>{lang === 'zh' ? '尺码 · ' : 'Size · '}{size}</h4>
            <div className="sizes">
              {['XS','S','M','L','XL'].map(s => (
                <div key={s} className={'size ' + (s === size ? 'on' : '')} onClick={() => setSize(s)}>{s}</div>
              ))}
            </div>
          </div>

          <div className="pdp-cta-row">
            <button className="btn-primary" onClick={() => onAdd({ ...product, color: currentColor.hex, selectedColor: currentColorName, selectedSize: size })}>
              {t('common.addtocart', lang)}
            </button>
            <button className="btn-ghost">♡</button>
          </div>

          <div style={{ display: 'flex', gap: 20, marginTop: 20, fontSize: 12, color: 'var(--fg-muted)' }}>
            <span>◆ {t('common.free_ship', lang)} $80+</span>
            <span>◆ Free returns 30d</span>
            <span>◆ Authentic</span>
          </div>

          {/* ---- tabs: Description / Material / Fit / Care ---- */}
          <div className="pdp-section" style={{ marginTop: 28 }}>
            <div style={{ display: 'flex', gap: 22, borderBottom: '1px solid var(--border)', marginBottom: 14 }}>
              {tabs.map(tb => (
                <div key={tb.id}
                  onClick={() => setTab(tb.id)}
                  style={{
                    cursor: 'pointer', paddingBottom: 10, fontSize: 13,
                    color: tab === tb.id ? 'var(--fg)' : 'var(--fg-muted)',
                    borderBottom: tab === tb.id ? '2px solid var(--accent)' : '2px solid transparent',
                    marginBottom: -1,
                    letterSpacing: '0.04em',
                  }}>
                  {tabLabel(tb)}
                </div>
              ))}
            </div>
            <p className="pdp-desc" style={{ whiteSpace: 'pre-line' }}>{tabBody}</p>
          </div>
        </div>
      </div>
    </div>
  );
};

// ----------------------------------------------------------------------------
// __groupByTopCat — Group a list of products by their top-level category and
// return an ordered array of { code, name, products } groups.
//
// Ordering rules:
//   1. If section.hero.category_order is set, use that explicit order.
//   2. Otherwise, use the order of include rules of type 'category'.
//   3. Any remaining categories encountered in the product set are appended.
// Products inside each group are sorted per section.hero.product_sort:
//   'default' | 'price_asc' | 'price_desc' | 'newest' | 'name_asc'.
// ----------------------------------------------------------------------------
window.__groupByTopCat = function groupByTopCat(products, section, lang) {
  const hero = (section && section.hero) || {};
  const cats = (window.YY_CATEGORIES || []);
  const byCode = new Map(cats.map(c => [c.code, c]));
  const topOf = (code) => {
    let cur = byCode.get(code);
    if (!cur) return code;
    while (cur && cur.parent_code) {
      const next = byCode.get(cur.parent_code);
      if (!next) break;
      cur = next;
    }
    return cur ? cur.code : code;
  };

  let orderedCodes = [];
  if (Array.isArray(hero.category_order) && hero.category_order.length) {
    orderedCodes = hero.category_order.slice();
  } else if (section && Array.isArray(section.include)) {
    for (const r of section.include) {
      if (r.type === 'category' && r.code) {
        const top = topOf(r.code);
        if (!orderedCodes.includes(top)) orderedCodes.push(top);
      }
    }
  }

  const buckets = new Map();
  for (const p of products) {
    const top = topOf(p.cat);
    if (!buckets.has(top)) buckets.set(top, []);
    buckets.get(top).push(p);
  }
  for (const code of buckets.keys()) {
    if (!orderedCodes.includes(code)) orderedCodes.push(code);
  }

  const sortMode = hero.product_sort || 'default';
  const cmp = {
    'default':    (a, b) => (a.sort_order || 0) - (b.sort_order || 0),
    'price_asc':  (a, b) => Number(a.price || 0) - Number(b.price || 0),
    'price_desc': (a, b) => Number(b.price || 0) - Number(a.price || 0),
    'newest':     (a, b) => String(b.created_at || '').localeCompare(String(a.created_at || '')),
    'name_asc':   (a, b) => String((a.name && (a.name[lang] || a.name.en)) || '')
                              .localeCompare(String((b.name && (b.name[lang] || b.name.en)) || '')),
  }[sortMode] || ((a, b) => 0);

  const result = [];
  for (const code of orderedCodes) {
    const items = buckets.get(code);
    if (!items || items.length === 0) continue;
    items.sort(cmp);
    const cat = byCode.get(code);
    const name = (cat && cat.name_i18n && (cat.name_i18n[lang] || cat.name_i18n.en)) || code;
    result.push({ code, name, products: items });
  }
  return result;
};

// ============================================================================
// SectionScreen — renders a configurable top-nav "site section".
// The server resolves the section's include rules into a product list.
// Layout: 'home' (hero + per-top-category grids) | 'culture' (culture cards).
// ============================================================================
window.SectionScreen = function SectionScreen({ lang, onNav, sectionId, channel, onAdd }) {
  const [state, setState] = React.useState({ loading: true, section: null, products: [] });

  React.useEffect(() => {
    let live = true;
    setState({ loading: true, section: null, products: [] });
    (async () => {
      try {
        // Server returns { section, products } envelope. If the endpoint fails
        // (e.g. 404 on a stale section id) fall back to the locally cached
        // YY_SITE_SECTIONS so we still render *something* meaningful.
        const resp = await window.YY_API
          .get('/site-sections/' + encodeURIComponent(sectionId) + '/products' + (channel ? '?channel=' + encodeURIComponent(channel) : ''))
          .catch(() => null);
        let section = resp && resp.section;
        const products = (resp && Array.isArray(resp.products)) ? resp.products : [];
        if (!section) {
          section = (window.YY_SITE_SECTIONS || []).find(s => s.id === sectionId) || null;
        }
        if (!live) return;
        setState({ loading: false, section, products: products.map(window.__productFromDb || (x => x)) });
      } catch (e) {
        if (live) setState({ loading: false, section: null, products: [] });
      }
    })();
    return () => { live = false; };
  }, [sectionId, channel]);

  const { loading, section, products } = state;
  if (loading) return <div className="screen-body"><div style={{ padding: 60, color: 'var(--fg-muted)' }}>Loading…</div></div>;
  if (!section) return <div className="screen-body"><div style={{ padding: 60 }}>Section not found.</div></div>;

  const pick = (obj) => (obj && (obj[lang] || obj.en)) || '';
  const hero = section.hero || {};
  const heroTitle = pick(hero.title_i18n) || pick(section.name_i18n) || section.id;
  const heroSub   = pick(hero.subtitle_i18n);
  const cta       = pick(hero.cta_label_i18n);
  const ctaTarget = hero.cta_target || '';

  // hero.picks = [{product_id, image_url, title_i18n, subtitle_i18n, cta_label_i18n, cta_target}]
  // Legacy: hero.product_ids = [pid, pid, ...]
  const rawPicks = Array.isArray(hero.picks) && hero.picks.length
    ? hero.picks
    : (Array.isArray(hero.product_ids) ? hero.product_ids.map(pid => ({ product_id: pid })) : []);
  const byId = new Map(products.map(p => [p.id, p]));
  const picks = rawPicks
    .map(pk => {
      const prod = pk && pk.product_id ? byId.get(pk.product_id) : null;
      if (!prod) return null;
      return {
        product: prod,
        image_url: (pk && pk.image_url) || prod.image_url || null,
        title:    (pk && pick(pk.title_i18n))    || pick(prod.name) || prod.id,
        subtitle: (pk && pick(pk.subtitle_i18n)) || '',
        cta:      (pk && pick(pk.cta_label_i18n)) || '',
        cta_target: (pk && pk.cta_target) || '',
      };
    })
    .filter(Boolean);

  const featured = picks.length ? picks.map(p => p.product) : products.slice(0, 4);

  const layout = section.layout || 'home';

  if (layout === 'culture') {
    return (
      <div className="screen-body">
        <section className="culture-hero">
          <div className="culture-mark" style={{ left: '-40px', top: '-60px' }}>華</div>
          <div className="culture-mark" style={{ right: '-40px', bottom: '-80px', fontSize: 220 }}>美</div>
          <div className="culture-hero-inner">
            <div style={{ fontSize: 12, letterSpacing: '0.3em', textTransform: 'uppercase', color: 'var(--fg-muted)', marginBottom: 20 }}>
              {pick(section.name_i18n) || section.id}
            </div>
            <h1 className="culture-hero-title">{heroTitle}</h1>
            {heroSub ? <p className="culture-hero-sub">{heroSub}</p> : null}
            {cta ? (
              <button className="btn-primary" style={{ marginTop: 24 }} onClick={() => {
                if (ctaTarget.startsWith('#/section/')) onNav('section', null, ctaTarget.slice('#/section/'.length));
                else if (ctaTarget) onNav(ctaTarget);
              }}>{cta}</button>
            ) : null}
          </div>
        </section>
        <section className="grid-section">
          <div className="grid-head">
            <div>
              <h2 className="grid-title" style={{ fontFamily: 'var(--font-accent)' }}>{pick(section.name_i18n) || section.id}</h2>
              <div className="grid-sub">{products.length} {lang === 'zh' ? '件商品' : 'items'}</div>
            </div>
            <div className="seal">选</div>
          </div>
          <div className="grid">
            {products.map(p => (
              <ProductCard key={p.id} product={p} lang={lang} onClick={() => onNav('pdp', p.id)} />
            ))}
          </div>
        </section>
      </div>
    );
  }

  // Default: 'home' layout — single PickCarousel at top + product grid.
  // When no curated picks exist, fall back to the first 4 products so the page
  // still has a top hero block.
  const fallbackPicks = products.slice(0, 4).map(prod => ({
    product: prod,
    image_url: prod.image_url || null,
    title:    pick(prod.name) || prod.id,
    subtitle: '',
    cta:      '',
    cta_target: '',
  }));
  const topPicks = picks.length > 0 ? picks : fallbackPicks;

  // Group products by top-level category so each category gets its own
  // region on the channel listing page (configurable sort via section.hero).
  const groups = window.__groupByTopCat(products, section, lang);

  return (
    <div className="screen-body">
      {topPicks.length > 0 && (
        <PickCarousel picks={topPicks} lang={lang} onNav={onNav}
                      titleFallback={pick(section.name_i18n) || heroTitle}/>
      )}

      {groups.length === 0 ? (
        <section className="grid-section">
          <div className="grid-head">
            <div>
              <h2 className="grid-title">{pick(section.name_i18n) || section.id}</h2>
              <div className="grid-sub">0 {lang === 'zh' ? '件商品' : 'items'}</div>
            </div>
          </div>
          <div style={{ padding: '48px 0', textAlign: 'center', color: 'var(--fg-muted)' }}>
            {lang === 'zh' ? '该频道暂无商品' : 'No products in this section yet.'}
          </div>
        </section>
      ) : groups.length === 1 ? (
        // One-category section: render a flat grid without the extra header.
        <section className="grid-section">
          <div className="grid-head">
            <div>
              <h2 className="grid-title">{groups[0].name}</h2>
              <div className="grid-sub">{groups[0].products.length} {lang === 'zh' ? '件商品' : 'items'}</div>
            </div>
          </div>
          <div className="grid">
            {groups[0].products.map(p => (
              <ProductCard key={p.id} product={p} lang={lang} onClick={() => onNav('pdp', p.id)} />
            ))}
          </div>
        </section>
      ) : (
        groups.map(g => (
          <section className="grid-section" key={g.code}>
            <div className="grid-head">
              <div>
                <h2 className="grid-title">{g.name}</h2>
                <div className="grid-sub">{g.products.length} {lang === 'zh' ? '件商品' : 'items'}</div>
              </div>
              <button
                className="btn-ghost"
                onClick={() => onNav('category', null, g.code)}
                style={{ padding: '6px 14px' }}
              >
                {lang === 'zh' ? '查看全部' : 'View all'} →
              </button>
            </div>
            <div className="grid">
              {g.products.map(p => (
                <ProductCard key={p.id} product={p} lang={lang} onClick={() => onNav('pdp', p.id)} />
              ))}
            </div>
          </section>
        ))
      )}

      <Footer lang={lang} />
    </div>
  );
};

window.CultureScreen = function CultureScreen({ lang, onNav }) {
  const cultureProds = (window.YY_PRODUCTS || []).filter(p => ['hanfu','tea','porcelain','jade','stationery','craft'].includes(p.cat));
  const collections = window.YY_CULTURE_COLLECTIONS || [];

  return (
    <div className="screen-body">
      <section className="culture-hero">
        <div className="culture-mark" style={{ left: '-40px', top: '-60px' }}>華</div>
        <div className="culture-mark" style={{ right: '-40px', bottom: '-80px', fontSize: 220 }}>美</div>
        <div className="culture-hero-inner">
          <div style={{ fontSize: 12, letterSpacing: '0.3em', textTransform: 'uppercase', color: 'var(--fg-muted)', marginBottom: 20 }}>
            The China Collection · 中 华 精 选
          </div>
          <h1 className="culture-hero-title">{lang === 'zh' ? '东方意象' : 'Timeless Orient'}</h1>
          <p className="culture-hero-sub">{t('culture.sub', lang)}</p>
          <div style={{ display: 'inline-flex', gap: 10, marginTop: 24, alignItems: 'center' }}>
            <div className="seal">悦</div>
            <div style={{ fontSize: 12, color: 'var(--fg-muted)', fontFamily: 'var(--font-accent)', letterSpacing: '0.1em' }}>
              源头直发 · Sourced Direct
            </div>
          </div>
        </div>
      </section>

      <section style={{ padding: '48px 48px 24px' }}>
        <div style={{ display: 'grid', gridTemplateColumns: `repeat(${Math.max(1, collections.length)}, 1fr)`, gap: 20 }}>
          {collections.map((c, i) => (
            <div key={c.id} style={{
              background: c.color, color: '#f5e4b8',
              padding: '36px 28px', borderRadius: 'var(--radius-lg)',
              position: 'relative', overflow: 'hidden', minHeight: 180,
              cursor: 'pointer',
            }}>
              <div style={{ position: 'absolute', right: -10, bottom: -30, fontSize: 180, fontFamily: 'var(--font-accent)', opacity: 0.14, lineHeight: 0.8 }}>{c.mark}</div>
              <div style={{ fontSize: 11, letterSpacing: '0.2em', textTransform: 'uppercase', opacity: 0.7, marginBottom: 12 }}>Collection · 0{i+1}</div>
              <h3 style={{ fontFamily: 'var(--font-accent)', fontSize: 32, letterSpacing: '0.04em', marginBottom: 8 }}>{c.title[lang] || c.title.en}</h3>
              <div style={{ fontSize: 13, opacity: 0.8 }}>{c.desc[lang] || c.desc.en}</div>
            </div>
          ))}
        </div>
      </section>

      <section className="grid-section">
        <div className="grid-head">
          <div>
            <h2 className="grid-title" style={{ fontFamily: 'var(--font-accent)' }}>
              {lang === 'zh' ? '非遗好物' : 'Heritage goods · 精选'}
            </h2>
            <div className="grid-sub">{lang === 'zh' ? '匠人手作 · 限量发售' : 'Artisan-made · Limited editions'}</div>
          </div>
          <div className="seal">选</div>
        </div>
        <div className="grid">
          {cultureProds.map(p => (
            <ProductCard key={p.id} product={p} lang={lang} onClick={() => onNav('pdp', p.id)} />
          ))}
        </div>
      </section>
    </div>
  );
};

// ---- SearchScreen -----------------------------------------------------------
// Simple full-catalogue text search: matches name/description across all
// languages plus category code. Case-insensitive substring match. Shows
// a helpful empty state when nothing matches.
window.SearchScreen = function SearchScreen({ lang, onNav, query }) {
  const q = (query || '').trim().toLowerCase();
  const all = (window.YY_PRODUCTS || []);
  const matches = q ? all.filter(p => {
    const hay = [
      ...Object.values(p.name || {}),
      ...Object.values(p.desc || {}),
      p.cat, p.subcat || '',
      ...(p.tag_codes || []),
    ].filter(Boolean).join(' ').toLowerCase();
    return hay.includes(q);
  }) : [];
  return (
    <div className="screen-body">
      <section className="grid-section">
        <div className="grid-head">
          <div>
            <h2 className="grid-title">
              {lang === 'zh' ? '搜索结果' : 'Search results'}
              {q ? <span style={{ color: 'var(--fg-muted)', fontWeight: 400, marginLeft: 10, fontSize: 16 }}>&ldquo;{query}&rdquo;</span> : null}
            </h2>
            <div className="grid-sub">
              {q
                ? (lang === 'zh' ? `${matches.length} 件结果` : `${matches.length} ${matches.length === 1 ? 'result' : 'results'}`)
                : (lang === 'zh' ? '请在顶部搜索框输入关键词' : 'Type a keyword in the search box above')}
            </div>
          </div>
        </div>
        {matches.length > 0 ? (
          <div className="grid">
            {matches.map(p => (
              <ProductCard key={p.id} product={p} lang={lang} onClick={() => onNav('pdp', p.id)} />
            ))}
          </div>
        ) : q ? (
          <div style={{ padding: '48px 0', textAlign: 'center', color: 'var(--fg-muted)' }}>
            {lang === 'zh' ? '没有找到相关商品' : 'No matching products.'}
          </div>
        ) : null}
      </section>
    </div>
  );
};
