/* Aevitan — "Essential Categories": pinned, scroll-scrubbed 4-panel sequence.
   Odometer number (1→4), persistent heading/button, and a particle field that
   coalesces into each category's product image as it reaches screen-center. */
const DScat = window.AevitanDesignSystem_6a042d;

const CATS = [
  { eyebrow: 'ASSESS', title: 'Biomarkers & baseline', desc: 'Longevity begins with measurement: biological age and biomarkers set the starting point.', img: '../../assets/cat-assess.png' },
  { eyebrow: 'METABOLIC', title: 'Peptides', desc: 'Verified metabolic and recovery peptides, the engine of the protocol.', img: '../../assets/cat-metabolic.png' },
  { eyebrow: 'FOUNDATION', title: 'Supplements', desc: 'Daily NAD+, NMN and actives: the consistent base that compounds over time.', img: '../../assets/cat-foundation.png' },
  { eyebrow: 'SURFACE', title: 'Cosmetics & topicals', desc: 'Skin and surface care, so visible vitality matches internal health.', img: '../../assets/cat-surface.png' },
];
const PCOLORS = ['#BFA15A', '#E7D4A1', '#FFFFFF', '#9FB2D6'];
const NPART = 7000;

function Categories({ go }) {
  const wrapRef = React.useRef(null);
  const stageRef = React.useRef(null);
  const canvasRef = React.useRef(null);
  const numColRef = React.useRef(null);
  const panelRefs = React.useRef([]);
  const ptitleRefs = React.useRef([]);
  const ptitleBoxRef = React.useRef(null);
  const tslotRefs = React.useRef([]);
  const headRef = React.useRef(null);
  const colLeftRef = React.useRef(null);
  const colRightRef = React.useRef(null);
  const progRef = React.useRef(0);
  const reduce = typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  // scroll → TARGET progress; the render loop eases toward it so every explosion/reform
  // plays out over time (a fast flick can't skip it), then snaps to the nearest category.
  React.useEffect(() => {
    const el = wrapRef.current; if (!el) return;
    let snapTimer = 0;
    const read = () => {
      const r = el.getBoundingClientRect();
      const total = el.offsetHeight - window.innerHeight;
      return Math.max(0, Math.min(1, -r.top / Math.max(1, total)));
    };
    const onScroll = () => {
      progRef.current = read();
      clearTimeout(snapTimer);
      snapTimer = setTimeout(() => {
        // settle to the nearest category dwell-center so it never rests mid-explosion
        const pf = progRef.current * 4;
        const idx = Math.max(0, Math.min(3, Math.round(pf - 0.5)));
        progRef.current = (idx + 0.5) / 4;
      }, 140);
    };
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    return () => { window.removeEventListener('scroll', onScroll); window.removeEventListener('resize', onScroll); clearTimeout(snapTimer); };
  }, []);

  // particles + UI driver
  React.useEffect(() => {
    if (reduce) return;
    const canvas = canvasRef.current, stage = stageRef.current;
    if (!canvas || !stage) return;
    const ctx = canvas.getContext('2d');
    const dpr = Math.min(2, window.devicePixelRatio || 1);
    let W = 0, H = 0, cx = 0, cy = 0, boxW = 300, boxH = 300;
    const targets = CATS.map(() => null);

    // sample the category TITLE into NPART target points (text coalesces cleanly)
    const sampleText = (text) => {
      const off = document.createElement('canvas');
      off.width = Math.max(2, boxW | 0); off.height = Math.max(2, boxH | 0);
      const o = off.getContext('2d');
      o.clearRect(0, 0, off.width, off.height);
      o.fillStyle = '#fff'; o.textAlign = 'center'; o.textBaseline = 'middle';
      const words = text.split(' ');
      let lines = [text];
      if (words.length > 2) { const m = Math.ceil(words.length / 2); lines = [words.slice(0, m).join(' '), words.slice(m).join(' ')]; }
      else if (words.length === 2) lines = words;             // stack two words
      let fs = Math.min(boxH / (lines.length + 0.5), boxW * 0.62);
      o.font = `600 ${fs}px 'Cormorant Garamond', Georgia, serif`;
      const fits = () => lines.every(l => o.measureText(l).width <= boxW * 0.96);
      while (fs > 13 && !fits()) { fs -= 2; o.font = `600 ${fs}px 'Cormorant Garamond', Georgia, serif`; }
      const lh = fs * 1.04, sy = off.height / 2 - (lines.length - 1) * lh / 2;
      lines.forEach((l, i) => o.fillText(l, off.width / 2, sy + i * lh));
      let data; try { data = o.getImageData(0, 0, off.width, off.height).data; } catch (e) { return null; }
      const pts = [];
      for (let y = 0; y < off.height; y += 2) for (let x = 0; x < off.width; x += 2) {
        if (data[(y * off.width + x) * 4 + 3] > 80) pts.push([x - off.width / 2, y - off.height / 2, Math.random() < 0.22 ? '#E7D4A1' : '#FFFFFF']);
      }
      if (!pts.length) return null;
      const res = new Array(NPART);
      for (let i = 0; i < NPART; i++) { const p = pts[(Math.random() * pts.length) | 0]; res[i] = { x: p[0], y: p[1], c: p[2] }; }
      return { points: res, fs, lines };
    };
    const resample = () => {
      for (let i = 0; i < CATS.length; i++) {
        const r = sampleText(CATS[i].title);
        targets[i] = r ? r.points : null;
        const el = ptitleRefs.current[i];
        if (el && r) { el.style.fontSize = r.fs + 'px'; el.innerHTML = r.lines.map(l => l.replace(/&/g, '&amp;').replace(/</g, '&lt;')).join('<br>'); }
      }
    };

    const resize = () => {
      const s = stage.getBoundingClientRect();
      W = s.width; H = s.height;
      canvas.width = W * dpr; canvas.height = H * dpr;
      canvas.style.width = W + 'px'; canvas.style.height = H + 'px';
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      // anchor the particle field + crisp title to the title slot in the right column
      const slot = tslotRefs.current[0] && tslotRefs.current[0].getBoundingClientRect();
      if (slot) {
        cx = slot.left - s.left + slot.width / 2;
        cy = slot.top - s.top + slot.height / 2;
        boxW = Math.max(140, slot.width);
        boxH = Math.max(96, slot.height);
      } else {
        const rr = colRightRef.current && colRightRef.current.getBoundingClientRect();
        if (rr) { cx = rr.left - s.left + rr.width / 2; cy = rr.top - s.top + rr.height / 2; boxW = rr.width; }
        else { cx = W / 2; boxW = Math.min(280, W * 0.24); }
        boxH = Math.min(boxW * 0.9, H * 0.4);
      }
      if (ptitleBoxRef.current) {
        const pb = ptitleBoxRef.current;
        pb.style.left = cx + 'px'; pb.style.top = cy + 'px'; pb.style.width = boxW + 'px';
      }
      resample();
    };
    resize(); window.addEventListener('resize', resize);
    if (document.fonts && document.fonts.ready) document.fonts.ready.then(resample);

    // particle state
    const P = new Array(NPART);
    for (let i = 0; i < NPART; i++) P[i] = { x: Math.random() * Math.max(1, W), y: Math.random() * Math.max(1, H), c: PCOLORS[i % PCOLORS.length] };

    const noise = (x, y, t) => Math.sin(x * 0.0032 + t) * Math.cos(y * 0.0041 - t * 0.7);
    let smoothIdx = 0, raf = 0, running = true, dispProg = progRef.current;
    const setOpacityVars = (prog) => {
      const pf = prog * 4;
      // panels: stable during the dwell, quick crossfade in the transition band (matches the particles)
      panelRefs.current.forEach((el, i) => {
        if (!el) return;
        const d = Math.abs(pf - (i + 0.5));
        const op = d <= 0.18 ? 1 : Math.max(0, 1 - (d - 0.18) / 0.32);
        el.style.opacity = op.toFixed(3);
        el.style.transform = `translateY(${((pf - (i + 0.5)) * 16).toFixed(1)}px)`;
      });
      // odometer: lerp toward the active index so the digit rolls and snaps into place
      const active = Math.max(0, Math.min(3, Math.round(pf - 0.5)));
      smoothIdx += (active - smoothIdx) * 0.16;
      if (Math.abs(active - smoothIdx) < 0.002) smoothIdx = active;
      if (numColRef.current) numColRef.current.style.transform = `translateY(${(-smoothIdx * 25).toFixed(3)}%)`;
    };

    const render = () => {
      if (!running) { raf = 0; return; }
      dispProg += (progRef.current - dispProg) * 0.045;   // ease toward target so the morph always plays out
      const prog = dispProg, pf = prog * 4;
      const active = Math.max(0, Math.min(3, Math.round(pf - 0.5)));
      const dist = Math.abs(pf - (active + 0.5));        // 0 at dwell-center … 0.5 at the boundary
      const PLATEAU = 0.18, EDGE = 0.5 - PLATEAU;        // narrower dwell → longer, more visible explosion/reform
      const c = dist <= PLATEAU ? 1 : Math.max(0, 1 - (dist - PLATEAU) / EDGE);
      const txtMix = Math.max(0, Math.min(1, (c - 0.58) / 0.30));  // crisp title fully in (and particles gone) by c≈0.88
      const tgt = targets[active];
      const t = performance.now() * 0.0006;
      ctx.clearRect(0, 0, W, H);
      const flow = 1 - c;
      for (let i = 0; i < NPART; i++) {
        const p = P[i];
        if (tgt && c > 0.02) {                            // pull into the title shape
          const g = tgt[i]; p.c = g.c;
          const tx = cx + g.x, ty = cy + g.y;
          const k = (0.06 + 0.16 * c) * (0.3 + 0.7 * c);
          p.x += (tx - p.x) * k;
          p.y += (ty - p.y) * k;
        }
        if (flow > 0.02) {                               // explode radially outward + scatter
          const dx = p.x - cx, dy = p.y - cy, dl = Math.hypot(dx, dy) || 1;
          p.x += (dx / dl) * flow * 2.4 + Math.cos(noise(p.x, p.y, t)) * 1.1 * flow;
          p.y += (dy / dl) * flow * 2.4 + Math.sin(noise(p.y, p.x, t)) * 1.1 * flow;
        }
        if (p.x > W + 24) p.x = -24; else if (p.x < -24) p.x = W + 24;
        if (p.y > H + 24) p.y = -24; else if (p.y < -24) p.y = H + 24;
        ctx.globalAlpha = (0.16 + c * 0.7) * (1 - txtMix); // fade particles as crisp text appears
        ctx.fillStyle = p.c;
        const s = 1.3 + c * 0.9;
        ctx.fillRect(p.x, p.y, s, s);
      }
      ctx.globalAlpha = 1;
      // crisp written title: fades in only at full dwell (maximum precision), out during transition
      ptitleRefs.current.forEach((el, i) => { if (el) el.style.opacity = (i === active ? txtMix : 0).toFixed(3); });
      setOpacityVars(prog);
      raf = requestAnimationFrame(render);
    };
    raf = requestAnimationFrame(render);

    // pause when offscreen, and reliably restart when it returns
    const io = new IntersectionObserver((es) => es.forEach(e => {
      running = e.isIntersecting;
      if (running && !raf) raf = requestAnimationFrame(render);
    }), { threshold: 0 });
    io.observe(wrapRef.current || stage);

    return () => { running = false; cancelAnimationFrame(raf); window.removeEventListener('resize', resize); io.disconnect(); };
  }, []);

  if (reduce) {
    // static fallback — the four categories as plain cards
    return (
      <section className="aevcat aevcat--static">
        <div className="aevcat__static-in">
          <span className="aevcat__big">4</span>
          <h2 className="aevcat__h">essential categories</h2>
          <p className="aevcat__sub">that work together to support strength, recovery, and long-term healthspan.</p>
          <div className="aevcat__grid">
            {CATS.map((cat, i) => (
              <div key={cat.title} className="aevcat__card">
                <div className="aevcat__card-img"><img src={cat.img} alt={cat.title} /></div>
                <div className="aevcat__eyebrow">{i + 1} · {cat.eyebrow}</div>
                <div className="aevcat__title">{cat.title}</div>
                <p className="aevcat__desc">{cat.desc}</p>
              </div>
            ))}
          </div>
          <button className="aevcat__cta" onClick={() => go('shop')}>Explore products</button>
        </div>
      </section>
    );
  }

  return (
    <section className="aevcat" ref={wrapRef}>
      <div className="aevcat__stage" ref={stageRef}>
        <div className="aevcat__bg" aria-hidden="true"><img src="../../assets/bw-running-couple.png" alt="" /></div>
        <canvas className="aevcat__canvas" ref={canvasRef} aria-hidden="true"></canvas>
        <div className="aevcat__ptitle" ref={ptitleBoxRef} aria-hidden="true">
          {CATS.map((cat, i) => (
            <span key={cat.title} ref={el => (ptitleRefs.current[i] = el)}></span>
          ))}
        </div>
        <div className="aevcat__ui">
          <div className="aevcat__col-left" ref={colLeftRef}>
            <div className="aevcat__odometer" aria-hidden="true">
              <div className="aevcat__num-col" ref={numColRef}>
                <span>1</span><span>2</span><span>3</span><span>4</span>
              </div>
            </div>
            <h2 className="aevcat__h" ref={headRef}>essential categories</h2>
            <p className="aevcat__sub">that work together to support strength, recovery, and long-term healthspan.</p>
            <button className="aevcat__cta" onClick={() => go('shop')}>Explore products</button>
          </div>
          <div className="aevcat__col-right" ref={colRightRef}>
            <div className="aevcat__panels">
              {CATS.map((cat, i) => (
                <div key={cat.title} className="aevcat__panel" ref={el => (panelRefs.current[i] = el)}>
                  <div className="aevcat__eyebrow">{cat.eyebrow}</div>
                  <div className="aevcat__tslot" ref={el => (tslotRefs.current[i] = el)}></div>
                  <p className="aevcat__desc">{cat.desc}</p>
                </div>
              ))}
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

window.Categories = Categories;
