/* SOURCING SCROLL - shared core.
   Pin-progress hook, ScrollStage (sticky canvas + progress overlay),
   darker-gold palette, pixel-field builder, shared data. window.SS2. */
const { useRef: useR, useEffect: useE, useState: useS } = React;

const clamp2 = (v, lo = 0, hi = 1) => Math.min(hi, Math.max(lo, v));
const lerp2 = (a, b, t) => a + (b - a) * t;
const span2 = (x, a, b) => clamp2((x - a) / (b - a));
const eio2 = (t) => (t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2);
const eout2 = (t) => 1 - Math.pow(1 - t, 3);
const TAU2 = Math.PI * 2;
const mul2 = (a) => () => { a |= 0; a = (a + 0x6D2B79F5) | 0; let t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; };

const GOLD = {
  ink: "#0C0A07", hi: "#E4C079", g: "#C0922E", g2: "#A2741F", g3: "#7E561A", g4: "#4E380F",
  white: "#F4F0EA", mono: '"Kumbh Sans","Helvetica Neue",Arial,sans-serif',
  // gold ramp by brightness 0..1
  ramp(v, a = 1) { v = clamp2(v); const r = (235 * v + 14) | 0, g = (188 * v + 10) | 0, b = (96 * v + 6) | 0; return `rgba(${r},${g},${b},${a})`; },
};

/* shared story data */
const STORY = {
  raw: "3,402,118",
  rawShort: "≈ 3.4M",
  layers: [
    { id: "match", name: "MATCH", sub: "Resolve entities", count: "41,604", dens: 0.70 },
    { id: "dedupe", name: "DEDUPE", sub: "Collapse duplicates", count: "33,902", dens: 0.52 },
    { id: "suppress", name: "SUPPRESS", sub: "vs Salesforce", count: "29,140", dens: 0.38 },
    { id: "filter", name: "FILTER", sub: "to ICP", count: "12,486", dens: 0.18 },
  ],
  tam: "12,486",
  sources: [
    { k: "Custom scrapers", n: 3 },
    { k: "Industry-specific", n: 3 },
    { k: "LinkedIn & Maps", n: 4 },
    { k: "Open-source intel", n: 2 },
  ],
};

/* ---- pin progress: 0..1 across a tall track with a sticky child ---- */
function usePin2(ref) {
  const [p, setP] = useS(0);
  const raf = useR(0);
  useE(() => {
    if (window.__forceP != null) { setP(window.__forceP); return; }
    const measure = () => {
      const el = ref.current; if (!el) return;
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight || 800;
      const total = el.offsetHeight - vh;
      const scrolled = clamp2(-rect.top, 0, Math.max(total, 1));
      setP(total > 0 ? scrolled / total : 0);
    };
    const onScroll = () => { cancelAnimationFrame(raf.current); raf.current = requestAnimationFrame(measure); };
    measure();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    const poll = setInterval(measure, 250);
    return () => { cancelAnimationFrame(raf.current); window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", onScroll); clearInterval(poll); };
  }, []);
  return p;
}

/* ---- ScrollStage: sticky stage with a full-viewport canvas + a progress-aware overlay ----
   draw(ctx, p, t, W, H) runs every frame. children can be a node or (p)=>node. */
function ScrollStage({ vh = 440, draw, children, paper = false, canvasStyleFn }) {
  const trackRef = useR(null), cvRef = useR(null), pRef = useR(0), drawRef = useR(draw), stageDraw = useR(null);
  drawRef.current = draw;
  const p = usePin2(trackRef);
  pRef.current = p;
  useE(() => {
    const cv = cvRef.current; if (!cv) return;
    const ctx = cv.getContext("2d");
    let W = 0, H = 0, dpr = Math.min(2, window.devicePixelRatio || 1);
    const size = () => {
      W = window.innerWidth; H = window.innerHeight;
      cv.width = Math.round(W * dpr); cv.height = Math.round(H * dpr);
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    size();
    let raf, start = performance.now(), alive = true;
    const loop = (now) => { if (!alive) return; if (window.innerWidth !== W || window.innerHeight !== H) size(); if (drawRef.current) drawRef.current(ctx, pRef.current, (now - start) / 1000, W, H); raf = requestAnimationFrame(loop); };
    raf = requestAnimationFrame(loop);
    stageDraw.current = (pv) => { if (drawRef.current) drawRef.current(ctx, pv, (performance.now() - start) / 1000, W, H); };
    const ro = new ResizeObserver(() => { size(); stageDraw.current(pRef.current); });
    ro.observe(cv);
    const onR = () => { size(); stageDraw.current(pRef.current); };
    window.addEventListener("resize", onR);
    return () => { alive = false; cancelAnimationFrame(raf); window.removeEventListener("resize", onR); ro.disconnect(); };
  }, []);
  // redraw immediately on every scroll-progress change (robust when rAF is throttled)
  useE(() => { if (stageDraw.current) stageDraw.current(p); }, [p]);
  return (
    <div className={"ss2-track" + (paper ? " ss2-paper" : "")} style={{ height: vh + "vh" }} ref={trackRef}>
      <div className="ss2-stage">
        <canvas className="ss2-canvas" ref={cvRef} style={canvasStyleFn ? canvasStyleFn(p) : undefined} />
        <div className="ss2-overlay">{typeof children === "function" ? children(p) : children}</div>
      </div>
    </div>
  );
}

/* ---- pixel field: N particles with fractional home + circle/lane params ---- */
function buildPixels(N, seed) {
  const rng = mul2(seed);
  const arr = [];
  for (let i = 0; i < N; i++) {
    arr.push({
      i, rank: i / N,
      hx: rng(), hy: rng(),                 // full-screen home (fractions)
      lane: Math.floor(rng() * 4),          // which source category
      ang: rng() * TAU2,                    // angle in big circle
      rr: Math.sqrt(rng()),                 // radius fraction (area-uniform)
      jx: (rng() - 0.5), jy: (rng() - 0.5), // stream jitter
      off: rng(),                            // stream phase offset
      tw: rng() * TAU2,                     // twinkle phase
      gold: rng(),                           // brightness seed
    });
  }
  return arr;
}

/* count-up that loops a darker-gold number toward a target (used in a few captions) */
function useCountTo(target, p, from = 0) {
  const n = Math.round(lerp2(from, target, eio2(p)));
  return n.toLocaleString();
}

Object.assign(window, {
  SS2: { clamp2, lerp2, span2, eio2, eout2, TAU2, mul2, GOLD, STORY, usePin2, ScrollStage, buildPixels, useCountTo },
});
