// GearGallery.jsx — homepage "gear coverflow" (replaces the diagonal-split
// CompareTeaser as the section under the hero).
//
// A 3D ring of five product cards (concept: "Scrolling Gallery" design 1b,
// Coverflow). On scroll-into-view the ring does one full revolution, then
// rests with one card front-and-centre. Cards are picked at random from the
// compare catalog (only products with real cutout shots — the "Coming soon"
// placeholders are excluded). Every card is an <a> into the Compare tool,
// pre-filled with that product plus a same-category rival:
//   • the FRONT card navigates (the router intercepts the click), and
//   • a SIDE card's click is intercepted here and rotates it to the front
//     instead — so the link only fires for the card the user can fully see.
// Prev/next buttons cover keyboard/mobile, and a compare CTA under them
// tracks the front card. Styles: .mmgg in page-styles.css.
//
// Internal helpers are _GG_/_gg-prefixed — one shared global scope, see
// CLAUDE.md. Exposes window.GearGallery.

const _GG_RING = 5; // cards in the ring — the transform curve is tuned for 5

// The spacing constants in _ggT were tuned on a ~1250px-wide stage; narrower
// stages squeeze the ring by k = stageWidth / _GG_SPREAD (clamped below).
const _GG_SPREAD = 1250;

// Horizontal fan-out of the ring (card size lives in the CSS --mmgg-cw/-ch —
// keep the two in step). Tuned so the outer cards' edges land on the ~1200px
// content column the sections below use. Depth (tz) stays unscaled so the
// side cards don't recede — smaller side cards would undo the width gain.
const _GG_SIZE_X = 1.63;

const _ggHasPhoto = (p) => p.image && !/coming-soon/i.test(p.image);

// Random five with real photos, mixed across upper/lower — a fresh set each
// page load keeps the shelf from going stale.
const _ggPickProducts = () => {
  const pool = [...window.COMPARE_DATA.upper(), ...window.COMPARE_DATA.lower()]
    .filter(_ggHasPhoto);
  for (let i = pool.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [pool[i], pool[j]] = [pool[j], pool[i]];
  }
  return pool.slice(0, _GG_RING);
};

// Same-category rival for the compare pre-fill, so the user lands on a
// meaningful like-for-like matchup (never a jacket vs leggings).
const _ggRival = (p) => {
  const mates = window.COMPARE_DATA[p.category]().filter((m) => m.id !== p.id);
  const pick =
    mates.find((m) => m.popular && _ggHasPhoto(m)) ||
    mates.find(_ggHasPhoto) ||
    mates[0];
  return pick ? pick.id : null;
};

// Shortest signed distance around a ring of n slots -> range [-n/2, n/2).
const _ggWrap = (x, n) => {
  let r = ((x % n) + n) % n;
  if (r >= n / 2) r -= n;
  return r;
};

// Coverflow transform for a card whose signed distance from the front is
// `rel` (fractional during the intro spin). `k` scales the x/z spacing for
// narrow stages; rotation/scale/opacity stay as tuned.
const _ggT = (rel, k) => {
  const s = Math.sign(rel) || 0;
  const ar = Math.abs(rel);
  const c1 = Math.min(ar, 1);
  const c12 = Math.max(0, Math.min(ar - 1, 1));
  const c22 = Math.max(0, Math.min(ar - 2, 0.5));
  const tx = s * (210 * c1 + 150 * c12 + 130 * c22) * k * _GG_SIZE_X;
  const tz = -(120 * c1 + 90 * c12 + 260 * c22) * k;
  const ry = -s * (30 * c1 + 14 * c12);
  let scale, op;
  if (ar <= 1) { scale = 1 - 0.16 * ar; op = 1; }
  else if (ar <= 2) { scale = 0.84 - 0.22 * (ar - 1); op = 1 - 0.55 * (ar - 1); }
  else { scale = 0.62 - 0.2 * Math.min(ar - 2, 0.5); op = Math.max(0, 0.45 - 0.9 * (ar - 2)); }
  return {
    // The leading -50% translate centres the card on the stage; the card's
    // width/height live in CSS (var(--mmgg-cw/ch)) so they can shrink on
    // mobile without touching this math.
    transform: `translate(-50%, -50%) translate3d(${tx.toFixed(1)}px, 0, ${tz.toFixed(1)}px) rotateY(${ry.toFixed(1)}deg) scale(${scale.toFixed(3)})`,
    opacity: Number(op.toFixed(3)),
  };
};

const GearGallery = () => {
  const products = React.useMemo(_ggPickProducts, []);
  const n = products.length;
  const REST = Math.floor(n / 2); // slot index that sits front-and-centre

  // `pos` is which card index is at the front. It's an unbounded integer
  // (clicks just add the signed step); _ggWrap folds it back onto the ring.
  const [pos, setPos] = React.useState(REST);
  const [k, setK] = React.useState(1);
  const stageRef = React.useRef(null);
  const cardRefs = React.useRef([]);
  const spunRef = React.useRef(false);
  const kRef = React.useRef(1);
  kRef.current = k;

  // Live manufacturer prices (src/live-prices.jsx) — the hardcoded catalog
  // prices are reference-only fallbacks.
  const liveIds = React.useMemo(() => products.map((p) => p.id), [products]);
  const livePrices = window.useLivePrices(liveIds);

  // Squeeze the ring to fit narrow stages.
  React.useEffect(() => {
    const fit = () => {
      const el = stageRef.current;
      if (el) setK(Math.max(0.36, Math.min(1, el.clientWidth / _GG_SPREAD)));
    };
    fit();
    window.addEventListener("resize", fit);
    return () => window.removeEventListener("resize", fit);
  }, []);

  // Intro spin — one full revolution, fired once on scroll-into-view. The
  // revolution starts and ends at the rest layout React already rendered, so
  // there's no jump on either side; cancelling the finished animation just
  // hands control back to the inline styles. Reduced-motion (or no IO)
  // simply skips it — the ring is already at rest.
  React.useEffect(() => {
    const reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (reduce || typeof IntersectionObserver === "undefined") return;
    const el = stageRef.current;
    if (!el) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (!entry.isIntersecting || spunRef.current) return;
        spunRef.current = true;
        io.disconnect();
        const FRAMES = 60;
        cardRefs.current.forEach((card, i) => {
          if (!card || !card.animate) return;
          const kf = [];
          for (let f = 0; f < FRAMES; f++) {
            const t = f / (FRAMES - 1);
            const p = _ggT(_ggWrap(i - (REST - n + n * t), n), kRef.current);
            kf.push({ offset: t, transform: p.transform, opacity: p.opacity });
          }
          // Web Animations API (as in the concept): timeline-driven, so it
          // plays even when rAF is throttled in a background tab.
          const anim = card.animate(kf, { duration: 2300, easing: "cubic-bezier(.16,1,.3,1)", fill: "both" });
          anim.onfinish = () => anim.cancel();
        });
      });
    }, { threshold: 0.3 });
    io.observe(el);
    return () => io.disconnect();
  }, []);

  const step = (d) => setPos((p) => p + d);

  const compareHref = (p) => {
    const rival = _ggRival(p);
    return `/compare?ids=${p.id}${rival ? `,${rival}` : ""}`;
  };

  // `pos` is unbounded — fold it back onto the ring for the front product.
  const frontProduct = products[((pos % n) + n) % n];

  const price = (p) => {
    const lp = livePrices[p.id];
    return (lp && lp.status === "ok" && window.mmFormatPrice(lp.resolved.GBP)) ||
      (p.price && p.price.GBP) || "";
  };

  return (
    <section className="mm-section mmgg" id="product-compare"
      aria-label="Featured gear — pick a product to compare">
      <div className="mmgg__glow" aria-hidden="true" />

      <div className="mmgg__stage" ref={stageRef}>
        <div className="mmgg__ring">
          {products.map((p, i) => {
            const rel = _ggWrap(i - pos, n);
            const front = rel === 0;
            const t = _ggT(rel, k);
            return (
              <a key={p.id}
                ref={(el) => { cardRefs.current[i] = el; }}
                className={`mmgg__card${front ? " is-front" : ""}`}
                style={{ transform: t.transform, opacity: t.opacity }}
                href={compareHref(p)}
                tabIndex={front ? 0 : -1}
                aria-label={front
                  ? `Compare the ${p.brand} ${p.name}`
                  : `Show the ${p.brand} ${p.name}`}
                onClick={front ? undefined : (e) => { e.preventDefault(); step(rel); }}>
                <span className="mm-mono mmgg__brand">{p.brand}</span>
                <span className="mmgg__name">{p.name}</span>
                <span className="mmgg__imgwrap">
                  {/* multiply-blend hides the white boxes around non-cutout
                      shots on the white card (same trick as the compare page) */}
                  <img src={p.image} alt="" loading="lazy" />
                </span>
                <span className="mmgg__specrow">
                  <span className={`mmgg__ce${p.ce && p.ce.rating === "AAA" ? " mmgg__ce--aaa" : ""}`}>
                    CE {(p.ce && p.ce.rating) || "—"}
                  </span>
                  <span className="mmgg__price">{price(p)}</span>
                </span>
                <span className="mm-mono mmgg__sub">
                  {(p.size && p.size.range) || ""} · {p.type}
                </span>
              </a>
            );
          })}
        </div>
      </div>

      <div className="mmgg__nav">
        <button type="button" className="mmgg__arrow" aria-label="Previous product"
          onClick={() => step(-1)}>←</button>
        <button type="button" className="mmgg__arrow" aria-label="Next product"
          onClick={() => step(1)}>→</button>
      </div>

      {/* The compare CTA tracks whichever card is at the front. */}
      <div className="mmgg__ctarow">
        <a className="mmgg__compare" href={compareHref(frontProduct)}>
          Compare the {frontProduct.brand} {frontProduct.name} <span aria-hidden="true">→</span>
        </a>
      </div>
    </section>
  );
};

window.GearGallery = GearGallery;
