/* ============================================================
   Shared UI primitives + icons  →  window globals
   ============================================================ */
const { useState, useEffect, useRef, useContext, createContext, useReducer } = React;

/* ---------- Icons (simple line set) ---------- */
const I = {
  bag: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><path d="M6 8h12l-1 12H7L6 8Z" strokeLinejoin="round"/><path d="M9 8V6a3 3 0 0 1 6 0v2"/></svg>,
  search: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><circle cx="11" cy="11" r="7"/><path d="m20 20-3.2-3.2"/></svg>,
  user: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><circle cx="12" cy="8" r="4"/><path d="M4 20c0-4 4-6 8-6s8 2 8 6"/></svg>,
  heart: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><path d="M12 20s-7-4.4-7-9.5A4 4 0 0 1 12 7a4 4 0 0 1 7 3.5C19 15.6 12 20 12 20Z" strokeLinejoin="round"/></svg>,
  menu: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><path d="M4 7h16M4 12h16M4 17h16"/></svg>,
  close: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><path d="M6 6l12 12M18 6 6 18"/></svg>,
  plus: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><path d="M12 5v14M5 12h14"/></svg>,
  minus: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><path d="M5 12h14"/></svg>,
  star: (p) => <svg viewBox="0 0 24 24" fill="currentColor" {...p}><path d="m12 3 2.6 5.6 6.1.7-4.5 4.2 1.2 6L12 16.9 6.6 19.5l1.2-6L3.3 9.3l6.1-.7L12 3Z"/></svg>,
  arrow: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><path d="M5 12h14M13 6l6 6-6 6" strokeLinejoin="round" strokeLinecap="round"/></svg>,
  check: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" {...p}><path d="m5 13 4 4 10-11" strokeLinejoin="round" strokeLinecap="round"/></svg>,
  truck: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><path d="M3 6h11v9H3zM14 9h4l3 3v3h-7z"/><circle cx="7" cy="18" r="1.8"/><circle cx="17" cy="18" r="1.8"/></svg>,
  shield: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><path d="M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6l8-3Z" strokeLinejoin="round"/></svg>,
  gift: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><path d="M4 11h16v9H4zM4 8h16v3H4zM12 8v12M12 8c-1-3-5-3-5-1s4 1 5 1Zm0 0c1-3 5-3 5-1s-4 1-5 1Z" strokeLinejoin="round"/></svg>,
  globe: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c3 3 3 15 0 18M12 3c-3 3-3 15 0 18"/></svg>,
  ig: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><rect x="3" y="3" width="18" height="18" rx="5"/><circle cx="12" cy="12" r="4"/><circle cx="17" cy="7" r="1" fill="currentColor" stroke="none"/></svg>,
  pin: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><circle cx="12" cy="12" r="9"/><path d="M12 7c-1.7 0-3 1.3-3 3 0 1.4 1 2 1 3s-.6 2.5-.6 2.5M12 7c1.7 0 3 1.3 3 3 0 2-2.3 2.3-3 4"/></svg>,
  tiktok: (p) => <svg viewBox="0 0 24 24" fill="currentColor" stroke="none" {...p}><path d="M19.59 6.69a4.83 4.83 0 0 1-3.77-4.25V2h-3.45v13.67a2.89 2.89 0 0 1-2.88 2.5 2.89 2.89 0 0 1-2.89-2.89 2.89 2.89 0 0 1 2.89-2.89c.28 0 .54.04.79.1V9.01a6.33 6.33 0 0 0-.79-.05 6.34 6.34 0 0 0-6.34 6.34 6.34 6.34 0 0 0 6.34 6.34 6.34 6.34 0 0 0 6.33-6.34V8.69a8.18 8.18 0 0 0 4.78 1.52V6.77a4.85 4.85 0 0 1-1.01-.08Z"/></svg>,
  box: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><path d="M4 8l8-4 8 4v8l-8 4-8-4V8Z" strokeLinejoin="round"/><path d="M4 8l8 4 8-4M12 12v8" strokeLinejoin="round"/></svg>,
  car: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><path d="M4 16v-3.4L6.2 7h11.6L20 12.6V16" strokeLinejoin="round"/><path d="M4 12.6h16"/><circle cx="8" cy="16.6" r="1.7"/><circle cx="16" cy="16.6" r="1.7"/></svg>,
  cpu: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><rect x="7" y="7" width="10" height="10" rx="1.5"/><rect x="10.5" y="10.5" width="3" height="3"/><path d="M9 4v3M15 4v3M9 17v3M15 17v3M4 9h3M4 15h3M17 9h3M17 15h3"/></svg>,
  wa: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><path d="M21 11.7a8.6 8.6 0 0 1-12.6 7.6L4 20.5l1.3-4.2A8.6 8.6 0 1 1 21 11.7Z" strokeLinejoin="round"/><path d="M9.2 9.3c.4 3 2.5 5.1 5.5 5.5l1-1.4 2 .9c-.3 1.5-1.5 2.2-3 1.9-3.4-.7-6.2-3.5-6.9-6.9-.3-1.5.4-2.7 1.9-3l.9 2-1.4 1Z" strokeLinejoin="round"/></svg>,
  doc: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><path d="M6 3h9l4 4v14H6V3Z" strokeLinejoin="round"/><path d="M15 3v4h4M9 12h6M9 16h6"/></svg>,
};

/* ---------- Hero panel — video with a full-colour still as poster/fallback.
   The poster <img> always renders underneath; the <video> layers on top and
   unmounts itself on error (missing/unset clip), so the poster is the
   guaranteed fallback — never an empty panel. ---------- */
/* ---------- Media slot — still image, with a brand clip layered over it when
   one is supplied. The clip's src is attached only once the slot scrolls into
   view AND a global concurrency slot is free, so off-screen tiles never open a
   connection. The still is always the guaranteed fallback. A single shared
   counter caps concurrent decodes across hero panels AND product tiles —
   these clips are several MB apiece, so only a couple ever run at once. ---------- */
const VID_MAX_LIVE = 8;
let vidLive = 0;
let vidPreload = null;
// Warm the browser cache for the small, fixed set of brand clips once, up
// front — so raising the live-slot cap doesn't reopen the cold-fetch storm
// that caused the earlier freeze (that was N simultaneous first-fetches of
// multi-MB files, not N simultaneous *plays* of already-cached ones).
function warmClips(urls) {
  if (vidPreload) return;
  const list = (Array.isArray(urls) ? urls : Object.values(urls || {})).filter(Boolean);
  vidPreload = list;
  list.forEach(u => { const v = document.createElement('video'); v.preload = 'auto'; v.muted = true; v.src = u; });
}

function HeroPanel({ poster, video, label, style, preloadAll }) {
  useEffect(() => { if (preloadAll) warmClips(preloadAll); }, []);
  return (
    <Ph src={poster} video={!!video} videoSrc={video} label={label} style={{ position: 'relative', ...style }} />
  );
}

function Ph({ label, variant = '', style, className = '', src, video, videoSrc, children, ...rest }) {
  const [err, setErr] = useState(false);
  const [vErr, setVErr] = useState(false);
  const [live, setLive] = useState(false);
  const box = useRef(null);
  const vref = useRef(null);
  const held = useRef(false);
  const clip = videoSrc && !vErr ? videoSrc : null;

  useEffect(() => {
    if (!clip) return;
    const el = box.current;
    if (!el) return;
    const release = () => { if (held.current) { held.current = false; vidLive--; } };
    const io = new IntersectionObserver((es) => {
      es.forEach(e => {
        if (e.isIntersecting) {
          if (!held.current && vidLive < VID_MAX_LIVE) { held.current = true; vidLive++; setLive(true); }
          const v = vref.current;
          if (v) { const pr = v.play(); if (pr && pr.catch) pr.catch(() => {}); }
        } else {
          const v = vref.current;
          if (v) v.pause();
          release();
          setLive(false);
        }
      });
    }, { rootMargin: '0px', threshold: 0.25 });
    io.observe(el);
    return () => { io.disconnect(); release(); };
  }, [clip]);

  // preload="metadata" still needs an explicit kick to actually start
  // buffering — nothing else calls .load() on mount.
  useEffect(() => {
    if (!live) return;
    const v = vref.current;
    if (!v) return;
    v.load();
  }, [live]);

  const fill = { position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', display: 'block' };
  const showImg = src && !err;
  // A clip with no still behind it IS the product's only visual (real stock,
  // filmed in store), so it stays mounted at preload="metadata" and shows its
  // first frame even when it isn't one of the slots cleared to play. Clips that
  // sit over a photo only mount once they win a live slot.
  const mountClip = clip && (live || !showImg);
  return (
    <div ref={box} className={`ph ${variant} ${className}`} data-label={(showImg || clip) ? '' : label} style={style} {...rest}>
      {showImg && (
        <img src={src} alt={label || ''} style={fill} loading="lazy" decoding="async" onError={() => setErr(true)} />
      )}
      {mountClip && (
        <video ref={vref} src={clip} poster={src || undefined} muted loop playsInline preload="metadata"
          style={fill} onError={() => setVErr(true)}
          onCanPlay={(e) => { if (live) { const pr = e.currentTarget.play(); if (pr && pr.catch) pr.catch(() => {}); } }} />
      )}
      {children}
    </div>
  );
}

/* ---------- Star rating ---------- */
function Stars({ value = 5, size = 13 }) {
  return (
    <span style={{ display: 'inline-flex', gap: 2, color: 'var(--gold)' }} aria-label={`${value} stars`}>
      {[0,1,2,3,4].map(i => <I.star key={i} width={size} height={size} style={{ opacity: i < Math.round(value) ? 1 : .25 }} />)}
    </span>
  );
}

/* ---------- Scroll reveal ---------- */
function Reveal({ children, as = 'div', delay = 0, className = '', ...rest }) {
  const ref = useRef(null);
  const [shown, setShown] = useState(false);
  useEffect(() => {
    const el = ref.current; if (!el || shown) return;
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setShown(true); io.disconnect(); }
    }, { threshold: 0.1 });
    io.observe(el);
    // Safety: ensure visible even if observer never fires
    const t = setTimeout(() => setShown(true), 1200);
    return () => { io.disconnect(); clearTimeout(t); };
  }, []);
  const Tag = as;
  return <Tag ref={ref} className={`reveal ${shown ? 'in' : ''} ${className}`} data-d={delay || undefined} {...rest}>{children}</Tag>;
}

Object.assign(window, { I, Ph, HeroPanel, Stars, Reveal, useState, useEffect, useRef, useContext, createContext, useReducer });
