// Product detail page

const { useState: useStateP, useEffect: useEffectP, useRef: useRefP, useMemo: useMemoP } = React;

const SIZING_TIPS = {
  tee:      'EU sizes run smaller than US sizes — US customers should order a size up.',
  pullover: 'This style runs small — we recommend ordering one size larger than you usually would.',
};

function Stars({ n = 5 }) {
  return (
    <span className="stars">
      {Array.from({ length: 5 }).map((_, i) =>
        <Icon.star key={i} style={{ opacity: i < n ? 1 : 0.2 }}/>
      )}
    </span>
  );
}

function PdpRow({ k, children, open, onToggle }) {
  return (
    <div style={{ borderBottom: '1px solid var(--line)' }}>
      <button onClick={onToggle}
              onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}
              style={{ width:'100%', padding:'18px 0', display:'flex', justifyContent:'space-between', alignItems:'center', textAlign:'left' }}>
        <span className="label-mono" style={{ color:'var(--fg)', fontSize:12 }}>{k}</span>
        <span className="num" style={{ color:'var(--fg-mute)', transform: open ? 'rotate(45deg)':'none', transition:'transform 300ms' }}>+</span>
      </button>
      <div style={{ display:'grid', gridTemplateRows: open ? '1fr':'0fr', transition:'grid-template-rows 400ms cubic-bezier(.2,.6,.2,1)' }}>
        <div style={{ overflow:'hidden', minHeight:0 }}>
          <div style={{ paddingBottom:18, color:'var(--fg-mute)', fontSize:14, lineHeight:1.6, maxWidth:540 }}>
            {children}
          </div>
        </div>
      </div>
    </div>
  );
}


function PDP({ product, products, onClose, onAdd, wish, onWish, cart, setCart, recentlyViewed, openProduct, navigate, initialVariantType, initialCwIdx, initialShirtColorIdx }) {
  // Variant type: printed or embroidered
  const initType = initialVariantType || (product.printed?.length > 0 ? 'printed' : 'embroidered');
  const [variantType, setVariantType] = useStateP(initType);
  const [cwIdx,       setCwIdx]       = useStateP(initialCwIdx ?? 0);
  const [imgIdx,      setImgIdx]      = useStateP(0);
  const [openSection,  setOpenSection]  = useStateP('description');
  const [lightboxIdx,  setLightboxIdx]  = useStateP(null);
  const [lbZoomed,     setLbZoomed]     = useStateP(false);
  const [lbGs,         setLbGs]         = useStateP({ bg: null, full: false, light: false });
  const [thumbsLoaded, setThumbsLoaded] = useStateP(0);
  const [gs,           setGs]           = useStateP({ bg: null, full: false, light: false });
  const [thumbBgs,     setThumbBgs]     = useStateP({});

  const colorways    = product[variantType] || [];
  const cw           = colorways[cwIdx] || colorways[0];
  const shirtColors  = cw?.shirtColors || [];
  const [shirtColor, setShirtColor] = useStateP(shirtColors[initialShirtColorIdx ?? 0] || shirtColors[0]);

  // When colorway or variant type changes: preserve thread if available, clamp image index.
  // Never touch gs.bg here — sampleImgColor owns it; clearing it early causes the cream flash.
  useEffectP(() => {
    const newColors = cw?.shirtColors || [];
    const preserved = newColors.find(sc => sc.name === shirtColor?.name);
    const nextColor = preserved || newColors[0] || null;
    setShirtColor(nextColor);
    const newImages = (nextColor?.images?.length ? nextColor.images : cw?.images) || [];
    setImgIdx(i => (i < newImages.length ? i : Math.max(0, newImages.length - 1)));
  }, [cwIdx, variantType]); // intentional stale reads of shirtColor + imgIdx

  // Thread colour: clamp index if new thread has fewer images; never reset to 0
  useEffectP(() => {
    if (!shirtColor?.images?.length) return;
    setImgIdx(i => (i < shirtColor.images.length ? i : Math.max(0, shirtColor.images.length - 1)));
  }, [shirtColor?.name]);

  const switchVariantType = (type) => {
    const currentName = cw?.name;
    const newColorways = product[type] || [];
    const newIdx = newColorways.findIndex(c => c.name === currentName);
    setVariantType(type);
    setCwIdx(newIdx >= 0 ? newIdx : 0);
    // imgIdx clamped by the cwIdx/variantType effect — don't reset here
  };

  // Gallery — declared here so lightbox useEffect deps can reference images.length
  const images  = (shirtColor?.images?.length ? shirtColor.images : cw?.images) || [];
  const mainImg = images[imgIdx] || null;

  // Buffer: only update displayed image after it's fully loaded so the old
  // image stays visible during load → instant swap with no flash to blank.
  const pendingRef = React.useRef(mainImg);
  const [shownImg, setShownImg] = useStateP(mainImg);
  useEffectP(() => {
    if (!mainImg) { setShownImg(null); return; }
    pendingRef.current = mainImg;
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.onload  = () => { if (pendingRef.current === mainImg) setShownImg(mainImg); };
    img.onerror = () => { if (pendingRef.current === mainImg) setShownImg(mainImg); };
    img.src = mainImg;
  }, [mainImg]);

  // Thumbnails: hold the previous set until ALL new images are preloaded, then swap atomically.
  // This prevents partial-update flicker and preserves correct background colours throughout.
  const [displayedImages, setDisplayedImages] = useStateP(images);
  const thumbPendingRef = useRefP(null);

  // On first render hide thumbs until loaded; on config changes keep them visible.
  const thumbsFirstLoad = useRefP(true);
  const thumbsKey = images.join('|');
  useEffectP(() => {
    if (thumbsFirstLoad.current) {
      thumbsFirstLoad.current = false;
      setThumbsLoaded(0);
    }
    // Cached images won't fire onLoad — force visible after short delay
    const t = setTimeout(() => setThumbsLoaded(images.length), 200);
    return () => clearTimeout(t);
  }, [thumbsKey]);

  useEffectP(() => {
    const key = thumbsKey;
    thumbPendingRef.current = key;
    if (!images.length) { setDisplayedImages(images); return; }
    let done = 0;
    images.forEach(url => {
      const img = new Image();
      img.crossOrigin = 'anonymous';
      img.onload = () => {
        // Pre-populate thumbBgs so background is ready the moment displayedImages commits — no cream flash.
        const r = readCorners(img);
        if (r && thumbPendingRef.current === key) setThumbBgs(prev => ({ ...prev, [url]: r }));
        done++;
        if (done >= images.length && thumbPendingRef.current === key) setDisplayedImages([...images]);
      };
      img.onerror = () => { done++; if (done >= images.length && thumbPendingRef.current === key) setDisplayedImages([...images]); };
      img.src = url;
    });
  }, [thumbsKey]);

  // Keep bg during transition to avoid flash; reset full+light to safe defaults only.
  // bg for images 0+1 is cleared once the flat-lay actually loads (in sampleImgColor).
  useEffectP(() => { setGs(s => ({ ...s, full: false, light: false })); }, [mainImg]);

  // Sample all 4 corners. Uniform corners = plain bg (contain). Varied = complex photo (cover).
  // Complex photos always get white text. Plain bg photos use bg brightness for text colour.
  const readCorners = (imgEl) => {
    try {
      const w = imgEl.naturalWidth, h = imgEl.naturalHeight;
      const c = document.createElement('canvas');
      c.width = c.height = 1;
      const ctx = c.getContext('2d');
      const px = (x, y) => { ctx.drawImage(imgEl, x, y, 1, 1, 0, 0, 1, 1); return [...ctx.getImageData(0,0,1,1).data]; };
      const tl = px(0,0), tr = px(w-1,0), bl = px(0,h-1), br = px(w-1,h-1);
      const dist = (a,b) => Math.abs(a[0]-b[0]) + Math.abs(a[1]-b[1]) + Math.abs(a[2]-b[2]);
      const uniform = dist(tl,tr) < 40 && dist(tl,bl) < 40 && dist(tl,br) < 40;
      if (tl[3] < 128) return null;
      const bgBrightness = (0.299*tl[0] + 0.587*tl[1] + 0.114*tl[2]) / 255;
      return { bg: `rgb(${tl[0]},${tl[1]},${tl[2]})`, full: !uniform, light: !uniform || bgBrightness < 0.5 };
    } catch(_) { return null; }
  };

  // Bags don't have cream flat-lay shots at index 0–1 — always sample them.
  const isBag = product.silhouette === 'tote' || product.silhouette === 'carrier';

  const sampleImgColor = (e) => {
    // Images 0+1 are flat-lay garment shots — keep cream. Bags always sample.
    if (imgIdx < 2 && !isBag) { setGs({ bg: null, full: false, light: false }); return; }
    const r = readCorners(e.target);
    if (r) setGs(r);
  };

  const sampleThumbColor = (url, e) => {
    const r = readCorners(e.target);
    if (r) setThumbBgs(prev => ({ ...prev, [url]: r }));
  };

  const sampleLightboxColor = (e) => {
    if (lightboxIdx < 2 && !isBag) { setLbGs({ bg: null, full: false, light: false }); return; }
    const r = readCorners(e.target);
    if (r) setLbGs(r);
  };

  // Reset zoom when navigating to a different lightbox image
  useEffectP(() => { setLbZoomed(false); }, [lightboxIdx]);

  // Lightbox keyboard navigation
  useEffectP(() => {
    if (lightboxIdx === null) return;
    const handler = e => {
      if (e.key === 'Escape')     { setLightboxIdx(null); onCursor('default'); }
      if (e.key === 'ArrowLeft')  setLightboxIdx(i => (i - 1 + images.length) % images.length);
      if (e.key === 'ArrowRight') setLightboxIdx(i => (i + 1) % images.length);
    };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  }, [lightboxIdx, images.length]);

  const DEFAULT_SIZES = new Set(['S', 'M', 'L']);
  const [showAllSizes, setShowAllSizes] = useStateP(false);

  const allSizes = cw?.sizes || product.sizes || [];
  const hasSML = allSizes.some(s => DEFAULT_SIZES.has(s));

  const size0 = hasSML
    ? (allSizes.find(s => DEFAULT_SIZES.has(s)) || allSizes[0] || '')
    : (allSizes[0] || '');
  const [size, setSize] = useStateP(size0);
  // Preserve size selection if new colorway still has it, otherwise prefer S/M/L
  useEffectP(() => {
    const newSizes = cw?.sizes || [];
    if (!newSizes.includes(size)) {
      const preferred = newSizes.find(s => DEFAULT_SIZES.has(s));
      setSize(preferred || newSizes[0] || '');
    }
  }, [cwIdx, variantType]); // intentional stale read of size

  const visibleSizes = (showAllSizes || !hasSML)
    ? allSizes
    : allSizes.filter(s => DEFAULT_SIZES.has(s) || s === size);
  const hasMoreSizes = !showAllSizes && hasSML && allSizes.some(s => !DEFAULT_SIZES.has(s));

  // Size table for size guide accordion
  const sizeTableKey =
    product.silhouette === 'tee'      ? 'tee'      :
    product.silhouette === 'tee-crop' ? 'cropTop'  :
    product.silhouette === 'tank'     ? 'tank'     :
    product.silhouette === 'hoodie'   ? 'hoodie'   :
    product.silhouette === 'pullover' ? 'pullover' : null;
  const sizeTable    = sizeTableKey ? (SIZES?.[sizeTableKey] || null) : null;
  const showSleeve   = product.silhouette === 'tee' || product.silhouette === 'pullover';
  const pdpFmt       = (cm) => pdpSizeUnit === 'cm' ? `${cm} cm` : `${(cm / 2.54).toFixed(1)}"`;

  const isWished = wish.has(product.id);
  const [ctaVisible, setCtaVisible] = useStateP(true);
  const ctaRef = useRefP(null);
  const [shared, setShared] = useStateP(false);
  const [pdpSizeUnit, setPdpSizeUnit] = useStateP('cm');
  const [tipHovered, setTipHovered] = useStateP(false);
  const sizingTip = SIZING_TIPS[product.silhouette] || null;

  // Derive cart qty for the currently selected variant
  const cartKey  = cw && size ? `${product.id}:${cw.name}:${shirtColor?.name || ''}:${size}` : null;
  const cartLine = cartKey ? (cart || []).find(l => l.key === cartKey) : null;
  const addedQty = cartLine?.qty || 0;

  const handleAddToCart = () => {
    if (!cw) return;
    onAdd(product, cw, shirtColor, size, variantType);
  };
  const handleIncrease = () => onAdd(product, cw, shirtColor, size, variantType, false);
  const handleDecrease = () => {
    if (!cartLine) return;
    if (cartLine.qty <= 1) {
      setCart(prev => prev.filter(l => l.key !== cartLine.key));
    } else {
      setCart(prev => prev.map(l => l.key === cartLine.key ? { ...l, qty: l.qty - 1 } : l));
    }
  };
  const handleCheckout = () => {
    navigate({ name: 'cart' }, '/merch/cart');
  };

  const handleShare = async () => {
    const url = `https://peripheralvinyl.com/merch/product/${product.id}`;
    try {
      if (navigator.share) {
        await navigator.share({ title: product.name, url });
      } else {
        await navigator.clipboard.writeText(url);
        setShared(true);
        setTimeout(() => setShared(false), 2000);
      }
    } catch (e) { /* user cancelled */ }
  };

  // Reset sticky bar visibility when navigating between products
  useEffectP(() => { setCtaVisible(true); }, [product.id]);

  // JSON-LD structured data for Google rich results
  useEffectP(() => {
    const price = cw?.price || product.price;
    const currency = window.shopCurrency || 'USD';
    const data = {
      '@context': 'https://schema.org/',
      '@type': 'Product',
      name: product.name,
      description: product.description || '',
      brand: { '@type': 'Brand', name: 'Peripheral' },
      ...(images[0] ? { image: images[0] } : {}),
      offers: {
        '@type': 'Offer',
        url: `https://peripheralvinyl.com/merch/product/${product.id}`,
        priceCurrency: currency,
        price: price != null ? Number(price).toFixed(2) : '0.00',
        availability: 'https://schema.org/InStock',
      },
    };
    let el = document.getElementById('pdp-jsonld');
    if (!el) {
      el = document.createElement('script');
      el.type = 'application/ld+json';
      el.id = 'pdp-jsonld';
      document.head.appendChild(el);
    }
    el.textContent = JSON.stringify(data);
    return () => document.getElementById('pdp-jsonld')?.remove();
  }, [product.id, cw?.name]);

  // Update page title and meta description per product
  useEffectP(() => {
    const prevTitle = document.title;
    const metaDesc = document.querySelector('meta[name="description"]');
    const prevDesc = metaDesc?.getAttribute('content');
    document.title = `${product.name} — Peripheral`;
    if (metaDesc) {
      const desc = [product.description, product.variantDescription?.[variantType]].filter(Boolean).join(' ');
      metaDesc.setAttribute('content', desc);
    }
    return () => {
      document.title = prevTitle;
      if (metaDesc && prevDesc) metaDesc.setAttribute('content', prevDesc);
    };
  }, [product.id, variantType]);

  // Show sticky CTA bar when the main CTA scrolls out of view
  useEffectP(() => {
    const el = ctaRef.current;
    if (!el) return;
    const obs = new IntersectionObserver(
      ([entry]) => setCtaVisible(entry.isIntersecting),
      { threshold: 0 }
    );
    obs.observe(el);
    return () => obs.disconnect();
  }, [product.id]);

  // Preload all images for this product so colorway/thread switching is instant.
  // Keep refs to Image objects — without them the browser GC's the request before it completes.
  const preloadedImgsRef = useRefP([]);
  useEffectP(() => {
    const urls = new Set();
    ['printed', 'embroidered'].forEach(vt => {
      (product[vt] || []).forEach(cw => {
        (cw.shirtColors || []).forEach(sc => (sc.images || []).forEach(u => urls.add(u)));
        (cw.images || []).forEach(u => urls.add(u));
      });
    });
    const imgs = [];
    urls.forEach(u => { const img = new Image(); img.crossOrigin = 'anonymous'; img.src = u; imgs.push(img); });
    preloadedImgsRef.current = imgs;
    return () => { preloadedImgsRef.current = []; };
  }, [product.id]);

  const reviews  = REVIEWS[product.id] || REVIEWS._default;
  const avgScore = (reviews.reduce((a, r) => a + r.rating, 0) / reviews.length).toFixed(1);

  const related = useMemoP(() => {
    const allOthers = (products || []).filter(p => p.id !== product.id);
    const shuffled = [...allOthers].sort(() => Math.random() - 0.5);
    return shuffled.slice(0, 3).map(p => ({
      product: p,
      cwIdx: Math.floor(Math.random() * Math.max(1, p.printed?.length || p.embroidered?.length || 1)),
    }));
  }, [product.id]); // eslint-disable-line
  const recent  = recentlyViewed
    .filter(id => id !== product.id)
    .map(id => (products || []).find(p => p.id === id))
    .filter(Boolean).slice(0, 4);

  // Gallery
  const total   = images.length;
  const prevImg = () => setImgIdx(i => (i - 1 + total) % total);
  const nextImg = () => setImgIdx(i => (i + 1) % total);


  return (
    <main className="fade-in" style={{ paddingTop: 80 }}>
      <div className="pdp">

        {/* ── gallery ── */}
        <div className="pdp-gallery">
          <div className="pdp-main" onClick={() => { if (images.length > 0) { setLightboxIdx(imgIdx); onCursor('default'); } }}
               style={{ cursor: images.length > 0 ? 'none' : 'default' }}
               onMouseEnter={() => images.length > 0 && onCursor('hover')} onMouseLeave={() => onCursor('default')}>
            {mainImg ? (
              <div style={{ position:'absolute', top:0, right:0, bottom:0, left:0, borderRadius:16, overflow:'hidden', background: gs.bg || 'var(--bg-elev)' }}>
                <img src={shownImg} alt={product.name} fetchPriority="high" crossOrigin="anonymous"
                     onLoad={sampleImgColor}
                     style={{ position:'absolute', top:0, right:0, bottom:0, left:0, width:'100%', height:'100%', objectFit: isBag || gs.full ? 'cover' : 'contain' }}/>
              </div>
            ) : (
              <div style={{ position:'absolute', inset:0, padding:'14% 22%' }}>
                <Garment kind={product.silhouette} color={cw?.hex || '#888'}/>
              </div>
            )}
            <div style={{ position:'absolute', top:20, left:20, color: gs.light ? 'rgba(255,255,255,0.8)' : undefined }} className="eyebrow">
              {product.category}
            </div>
            {total > 1 && (
              <div style={{ position:'absolute', top:20, right:20, color: gs.light ? 'rgba(255,255,255,0.8)' : undefined }} className="eyebrow">
                {String(imgIdx + 1).padStart(2,'0')} / {String(total).padStart(2,'0')}
              </div>
            )}
            {total > 1 && (
              <>
                <button className="gallery-arrow gallery-prev" onClick={e => { e.stopPropagation(); prevImg(); }}
                        onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
                  <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
                </button>
                <button className="gallery-arrow gallery-next" onClick={e => { e.stopPropagation(); nextImg(); }}
                        onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
                  <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
                </button>
              </>
            )}
          </div>
          {images.length > 1 && (
            <div className="pdp-thumbs"
                 style={{ opacity: thumbsLoaded >= displayedImages.length ? 1 : 0, transition: 'opacity 250ms ease' }}>
              {displayedImages.map((img, i) => (
                <button key={i}
                        className={`pdp-thumb ${i === imgIdx ? 'on' : ''}`}
                        style={{ background: thumbBgs[img]?.bg || 'var(--bg-elev)' }}
                        onClick={() => setImgIdx(i)}
                        onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
                  <img src={img} alt={`View ${i + 1}`} decoding="async" crossOrigin="anonymous"
                       style={{ position:'absolute', inset:0, width:'100%', height:'100%', objectFit: thumbBgs[img]?.full ? 'cover' : 'contain', objectPosition: isBag ? '50% 30%' : 'center' }}
                       onLoad={(e) => { sampleThumbColor(img, e); setThumbsLoaded(n => n + 1); }}/>
                </button>
              ))}
            </div>
          )}
        </div>

        {/* ── info panel ── */}
        <div className="pdp-info">
          <div className="pdp-crumb">
            <a onClick={onClose} onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>Shop</a>
            <span className="sep">/</span>
            <a onClick={() => navigate({ name:'collection', id: product.category }, `/merch/collection/${product.category}`)}
               onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
              {CATEGORIES.find(c => c.id === product.category)?.name}
            </a>
            <span className="sep">/</span>
            <span>{product.name}</span>
          </div>

          <div className="pdp-title">
            <h1 className="h-1">{product.name}</h1>
            <button className="pdp-share-btn" onClick={handleShare}
                    onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}
                    title={shared ? 'Copied!' : 'Share'}>
              {shared
                ? <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
                : <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg>
              }
            </button>
          </div>

          <div className="pdp-price">
            <span className="num">{window.fmt ? window.fmt(cw?.price || product.price) : `€${cw?.price || product.price}`}</span>
            <span className="ship">Tax incl. · Ships in 3–5 days</span>
          </div>

          <div>
            <p className="pdp-desc">{product.description}</p>
            {product.variantDescription?.[variantType] && (
              <p className="pdp-desc" style={{ marginTop: 4 }}>
                {product.variantDescription[variantType]}
              </p>
            )}
          </div>

          {/* ── Mobile-only inline gallery ── */}
          <div className="pdp-mobile-gallery">
            <div className="pdp-main" onClick={() => { if (images.length > 0) { setLightboxIdx(imgIdx); onCursor('default'); } }}>
              {mainImg ? (
                <div style={{ position:'absolute', top:0, right:0, bottom:0, left:0, borderRadius:16, overflow:'hidden', background: gs.bg || 'var(--bg-elev)' }}>
                  <img src={shownImg} alt={product.name} crossOrigin="anonymous"
                       onLoad={sampleImgColor}
                       style={{ position:'absolute', top:0, right:0, bottom:0, left:0, width:'100%', height:'100%', objectFit: isBag || gs.full ? 'cover' : 'contain' }}/>
                </div>
              ) : (
                <div style={{ position:'absolute', inset:0, padding:'14% 22%' }}>
                  <Garment kind={product.silhouette} color={cw?.hex || '#888'}/>
                </div>
              )}
              {total > 1 && (
                <>
                  <button className="gallery-arrow gallery-prev" onClick={e => { e.stopPropagation(); prevImg(); }}>
                    <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
                  </button>
                  <button className="gallery-arrow gallery-next" onClick={e => { e.stopPropagation(); nextImg(); }}>
                    <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
                  </button>
                </>
              )}
            </div>
          </div>

          {/* ── Printed / Embroidered tiles ── */}
          {product.hasEmbroidered && product.printed?.length > 0 && (
            <div className="pdp-section">
              <div className="h"><h5>Style</h5></div>
              <div style={{ display:'flex', gap:8 }}>
                {[['printed','Print'],['embroidered','Embroidered']].map(([type, label]) => (
                  <button key={type}
                          className={`pdp-type-btn ${variantType === type ? 'on' : ''}`}
                          onClick={() => switchVariantType(type)}
                          onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
                    {label}
                  </button>
                ))}
              </div>
            </div>
          )}

          {/* ── Colorway swatches ── */}
          <div className="pdp-section">
            <div className="h">
              <h5>Colour — {cw?.name}</h5>
            </div>
            <div className="opts">
              {colorways.map((c, i) => (
                <button key={c.name}
                        className={`opt-color ${cwIdx === i ? 'on' : ''}`}
                        style={{ background: c.hex }}
                        onClick={() => setCwIdx(i)}
                        aria-label={c.name}
                        onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}/>
              ))}
            </div>
          </div>

          {/* ── Shirt colour (Black / White thread) ── */}
          {shirtColors.length > 0 && (
            <div className="pdp-section">
              <div className="h">
                <h5>Thread — {shirtColor?.name}</h5>
              </div>
              <div className="opts">
                {shirtColors.map(sc => (
                  <button key={sc.name}
                          className={`opt ${shirtColor?.name === sc.name ? 'on' : ''}`}
                          onClick={() => setShirtColor(sc)}
                          onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}
                          style={{ fontFamily:'var(--mono)', fontSize:11 }}>
                    {sc.name}
                  </button>
                ))}
              </div>
            </div>
          )}

          {/* ── Size ── */}
          <div className="pdp-section">
            <div className="h">
              <div style={{ display:'flex', alignItems:'center', gap:7 }}>
                <h5>Size — {size}</h5>
                {sizingTip && (
                  <div style={{ position:'relative' }}>
                    <button
                      onMouseEnter={() => { setTipHovered(true); onCursor('hover'); }}
                      onMouseLeave={() => { setTipHovered(false); onCursor('default'); }}
                      style={{ display:'inline-flex', alignItems:'center', justifyContent:'center', color:'var(--fg-mute)', cursor:'none', flexShrink:0, padding:0 }}>
                      <svg width="14" height="14" viewBox="0 0 640 640" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
                        <path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 240C302.3 240 288 254.3 288 272C288 285.3 277.3 296 264 296C250.7 296 240 285.3 240 272C240 227.8 275.8 192 320 192C364.2 192 400 227.8 400 272C400 319.2 364 339.2 344 346.5L344 350.3C344 363.6 333.3 374.3 320 374.3C306.7 374.3 296 363.6 296 350.3L296 342.2C296 321.7 310.8 307 326.1 302C332.5 299.9 339.3 296.5 344.3 291.7C348.6 287.5 352 281.7 352 272.1C352 254.4 337.7 240.1 320 240.1zM288 432C288 414.3 302.3 400 320 400C337.7 400 352 414.3 352 432C352 449.7 337.7 464 320 464C302.3 464 288 449.7 288 432z"/>
                      </svg>
                    </button>
                    {tipHovered && (
                      <div style={{ position:'absolute', bottom:'calc(100% + 10px)', left:'50%', transform:'translateX(-50%)', background:'var(--fg)', color:'var(--bg)', padding:'9px 13px', borderRadius:6, fontSize:12, lineHeight:1.55, width:240, fontFamily:'var(--sans)', zIndex:100, pointerEvents:'none' }}>
                        {sizingTip}
                        <div style={{ position:'absolute', top:'100%', left:'50%', transform:'translateX(-50%)', borderLeft:'5px solid transparent', borderRight:'5px solid transparent', borderTop:'5px solid var(--fg)' }}/>
                      </div>
                    )}
                  </div>
                )}
              </div>
              {sizeTable && (
                <a onClick={() => { setOpenSection('sizing'); setTimeout(() => scrollToId('pdp-sizing'), 50); }}
                   onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>Size guide ↓</a>
              )}
            </div>
            <div className="opts">
              {visibleSizes.map(s => (
                <button key={s} className={`opt ${size === s ? 'on' : ''}`}
                        onClick={() => setSize(s)}
                        onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
                  {s}
                </button>
              ))}
              {hasMoreSizes && (
                <button className="opt" onClick={() => setShowAllSizes(true)}
                        onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}
                        style={{color:'var(--fg-mute)', borderStyle:'dashed', minWidth:48}}>
                  More
                </button>
              )}
            </div>
          </div>

          {/* ── Buy CTA ── */}
          <div className="pdp-actions" ref={ctaRef}>
            {addedQty === 0 ? (
              <div className="pdp-pair">
                <button className="btn-primary" onClick={handleAddToCart}
                        onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
                  <span>Add to crate</span>
                  <span className="num">— {window.fmt ? window.fmt(cw?.price || product.price) : `€${cw?.price || product.price}`}</span>
                </button>
                <button className={`pdp-wish ${isWished ? 'on' : ''}`} onClick={() => onWish(product.id)}
                        onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
                  {isWished ? <Icon.heartFill/> : <Icon.heart/>}
                </button>
              </div>
            ) : (
              <div className="pdp-added-row">
                <button className="btn-primary" onClick={handleCheckout}
                        onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
                  <span>View crate</span>
                  <Icon.arrow/>
                </button>
                <div className="pdp-qty-ctrl">
                  <button onClick={handleDecrease}
                          onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
                    <Icon.minus/>
                  </button>
                  <span className="v">{addedQty}</span>
                  <button onClick={handleIncrease}
                          onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
                    <Icon.plus/>
                  </button>
                </div>
                <button className={`pdp-wish ${isWished ? 'on' : ''}`} onClick={() => onWish(product.id)}
                        onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
                  {isWished ? <Icon.heartFill/> : <Icon.heart/>}
                </button>
              </div>
            )}
          </div>

          {/* ── Shipping strip ── */}
          <div className="pdp-ship-strip">
            <div className="pdp-ship-item">
              <span className="pdp-ship-icon">
                <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round">
                  <path d="M5 12h14M13 6l6 6-6 6"/>
                </svg>
              </span>
              <span>Ships in 3–5 days</span>
            </div>
            <div className="pdp-ship-item">
              <span className="pdp-ship-icon">
                <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round">
                  <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
                  <path d="M3 3v5h5"/>
                </svg>
              </span>
              <span>30-day returns</span>
            </div>
            <div className="pdp-ship-item">
              <span className="pdp-ship-icon">
                <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round">
                  <circle cx="12" cy="12" r="9"/>
                  <circle cx="12" cy="12" r="3"/>
                </svg>
              </span>
              <span>Made in Portugal</span>
            </div>
          </div>

          {/* ── Accordion ── */}
          <div className="pdp-meta-list" style={{ marginTop:8 }}>
            <PdpRow open={openSection === 'materials'} onToggle={() => setOpenSection(openSection === 'materials' ? null : 'materials')} k="Materials & Construction">
              <p>{product.materials}</p>
              <p style={{ marginTop:8 }}>{product.fit}</p>
            </PdpRow>
            <PdpRow open={openSection === 'care'} onToggle={() => setOpenSection(openSection === 'care' ? null : 'care')} k="Care">
              <p>{product.care}</p>
            </PdpRow>
            <PdpRow open={openSection === 'origin'} onToggle={() => setOpenSection(openSection === 'origin' ? null : 'origin')} k="Origin">
              <p>{product.origin}</p>
            </PdpRow>
            <PdpRow open={openSection === 'ship'} onToggle={() => setOpenSection(openSection === 'ship' ? null : 'ship')} k="Shipping & Returns">
              <p>Ships within 3–5 business days. Delivery 3–7 days in EU / UK / US, 7–14 days elsewhere. Free returns within 30 days on unworn items.</p>
            </PdpRow>
            {sizeTable && (
              <div id="pdp-sizing">
                <PdpRow open={openSection === 'sizing'} onToggle={() => setOpenSection(openSection === 'sizing' ? null : 'sizing')} k="Size Guide">
                  <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:14 }}>
                    <div style={{ display:'flex', gap:4 }}>
                      {['cm', 'in'].map(u => (
                        <button key={u} className={`btn-unit${pdpSizeUnit === u ? ' active' : ''}`}
                                onClick={() => setPdpSizeUnit(u)}
                                onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
                          {u}
                        </button>
                      ))}
                    </div>
                    <a className="lnk-guide"
                       onClick={() => navigate({ name:'info', id:'size-guide' }, `/merch/size-guide#${sizeTableKey}`)}
                       onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
                      Full guide →
                    </a>
                  </div>
                  <table style={{ width:'100%', borderCollapse:'collapse' }}>
                    <thead>
                      <tr style={{ fontFamily:'var(--mono)', fontSize:10, letterSpacing:'0.12em', textTransform:'uppercase', color:'var(--fg-mute)' }}>
                        <th style={{ textAlign:'left',  paddingBottom:10, fontWeight:400 }}>Size</th>
                        <th style={{ textAlign:'right', paddingBottom:10, fontWeight:400 }}>Chest</th>
                        <th style={{ textAlign:'right', paddingBottom:10, fontWeight:400 }}>Length</th>
                        {showSleeve && <th style={{ textAlign:'right', paddingBottom:10, fontWeight:400 }}>Sleeve</th>}
                      </tr>
                    </thead>
                    <tbody>
                      {sizeTable.map(row => (
                        <tr key={row.size} style={{ borderTop:'1px solid var(--line)' }}>
                          <td style={{ padding:'7px 0', fontFamily:'var(--mono)', fontSize:12 }}>{row.size}</td>
                          <td style={{ padding:'7px 0', fontFamily:'var(--mono)', fontSize:12, textAlign:'right' }}>{pdpFmt(row.chest)}</td>
                          <td style={{ padding:'7px 0', fontFamily:'var(--mono)', fontSize:12, textAlign:'right' }}>{pdpFmt(row.length)}</td>
                          {showSleeve && <td style={{ padding:'7px 0', fontFamily:'var(--mono)', fontSize:12, textAlign:'right' }}>{pdpFmt(row.sleeve)}</td>}
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </PdpRow>
              </div>
            )}
          </div>
        </div>
      </div>

      {/* ── Related ── */}
      {related.length > 0 && (
        <section className="section">
          <div className="section-head">
            <div className="left">
              <h2 className="h-2"><em>You</em> might also like</h2>
            </div>
          </div>
          <div className="strip">
            {related.map(({ product: rp, cwIdx }) => (
              <ProductCard key={rp.id} p={rp} onOpen={openProduct} wish={wish} onWish={onWish} initialCwIdx={cwIdx} compact/>
            ))}
          </div>
        </section>
      )}

      {/* ── Recently viewed ── */}
      {recent.length > 0 && (
        <section className="section">
          <div className="section-head">
            <div className="left">
              <h2 className="h-2"><em>Pick up</em> where you left off</h2>
            </div>
          </div>
          <div className="strip">
            {recent.map(p => (
              <ProductCard key={p.id} p={p} onOpen={openProduct} wish={wish} onWish={onWish} compact/>
            ))}
          </div>
        </section>
      )}

      {/* ── Sticky mobile CTA ── */}
      {ReactDOM.createPortal(
        <div className={`pdp-sticky-cta${!ctaVisible ? ' show' : ''}`}>
          <div className="pdp-sticky-info">
            <div className="pdp-sticky-name">{product.name}</div>
            <div className="pdp-sticky-detail">
              {[size, cw?.name, window.fmt ? window.fmt(cw?.price || product.price) : null].filter(Boolean).join(' · ')}
            </div>
          </div>
          {addedQty === 0 ? (
            <button className="btn-primary pdp-sticky-btn" onClick={handleAddToCart}
                    onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
              Add to crate
            </button>
          ) : (
            <button className="btn-primary pdp-sticky-btn" onClick={handleCheckout}
                    onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
              View crate <Icon.arrow/>
            </button>
          )}
        </div>,
        document.body
      )}

      {/* ── Lightbox ── */}
      {lightboxIdx !== null && images.length > 0 && ReactDOM.createPortal(
        <div style={{
          position:'fixed', inset:0, zIndex:9000,
          background:'rgba(0,0,0,0.82)',
          display:'flex', alignItems:'center', justifyContent:'center',
        }} onClick={() => { setLightboxIdx(null); setLbZoomed(false); onCursor('default'); }}>
          <div style={{
            position:'relative',
            width:'min(88vw, 960px)', height:'min(88vh, 960px)',
            background: lbGs.bg || 'var(--bg-elev)',
            borderRadius:16, overflow: lbZoomed ? 'auto' : 'hidden',
            flexShrink:0,
          }} onClick={e => e.stopPropagation()}>

            <img src={images[lightboxIdx]} alt={product.name}
                 crossOrigin="anonymous"
                 onClick={() => setLbZoomed(z => !z)}
                 onLoad={sampleLightboxColor}
                 onMouseLeave={(e) => { onCursor('default'); e.currentTarget.style.removeProperty('cursor'); }}
                 onMouseMove={(e) => {
                   if (lbZoomed) {
                     onCursor('hidden');
                     e.currentTarget.style.setProperty('cursor', 'zoom-out', 'important');
                     return;
                   }
                   const rect = e.currentTarget.getBoundingClientRect();
                   const lx = e.clientX - rect.left;
                   const ly = e.clientY - rect.top;
                   const { naturalWidth: nw, naturalHeight: nh } = e.currentTarget;
                   if (!nw || !nh) { onCursor('default'); return; }
                   const scale = Math.min(rect.width / nw, rect.height / nh);
                   const iw = nw * scale, ih = nh * scale;
                   const il = (rect.width - iw) / 2, it = (rect.height - ih) / 2;
                   const onImg = lx >= il && lx <= il + iw && ly >= it && ly <= it + ih;
                   onCursor(onImg ? 'hidden' : 'default');
                   e.currentTarget.style.setProperty('cursor', onImg ? 'zoom-in' : 'none', 'important');
                 }}
                 style={{
                   display:'block',
                   width: lbZoomed ? 'auto' : '100%',
                   height: lbZoomed ? 'auto' : '100%',
                   maxWidth: lbZoomed ? 'none' : '100%',
                   maxHeight: lbZoomed ? 'none' : '100%',
                   objectFit: lbZoomed ? 'none' : 'contain',
                 }}/>

            <div style={{ position:'absolute', top:20, left:20, color: lbGs.light ? 'rgba(255,255,255,0.8)' : undefined }} className="eyebrow">
              {product.category}
            </div>

            <button onClick={e => { e.stopPropagation(); setLightboxIdx(null); setLbZoomed(false); onCursor('default'); }}
                    onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}
                    style={{ position:'absolute', top:14, right:14, zIndex:1, width:32, height:32, display:'grid', placeItems:'center', background:'rgba(0,0,0,0.35)', borderRadius:'50%', color:'rgba(255,255,255,0.85)' }}>
              <Icon.close width={14} height={14}/>
            </button>

            {images.length > 1 && !lbZoomed && (
              <>
                <button className="gallery-arrow gallery-prev"
                        onClick={e => { e.stopPropagation(); setLightboxIdx(i => (i - 1 + images.length) % images.length); }}
                        onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
                  <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
                </button>
                <button className="gallery-arrow gallery-next"
                        onClick={e => { e.stopPropagation(); setLightboxIdx(i => (i + 1) % images.length); }}
                        onMouseEnter={() => onCursor('hover')} onMouseLeave={() => onCursor('default')}>
                  <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
                </button>
                <div style={{ position:'absolute', bottom:16, right:20, fontFamily:'var(--mono)', fontSize:11, letterSpacing:'0.14em', color: lbGs.light ? 'rgba(255,255,255,0.7)' : 'var(--fg-mute)' }}>
                  {String(lightboxIdx + 1).padStart(2,'0')} / {String(images.length).padStart(2,'0')}
                </div>
              </>
            )}
          </div>
        </div>,
        document.body
      )}
    </main>
  );
}

Object.assign(window, { PDP });
