/* KitRate — scroll-triggered motion for the share page.
   One moment of motion per number: count up once when scrolled into
   view, market-rail dots slide into place. Respects reduced motion;
   the editor preview and print stay static. */

const KR_REDUCED_MOTION = !!(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches);

/* Observe once. Pass disabled=true to skip (returns seen=true immediately). */
function useRevealOnce(disabled) {
  const ref = useRef(null);
  const [seen, setSeen] = useState(false);
  useEffect(() => {
    if (disabled || seen || !ref.current) return;
    if (!("IntersectionObserver" in window)) { setSeen(true); return; }
    const ob = new IntersectionObserver((es) => {
      if (es.some(e => e.isIntersecting)) { setSeen(true); ob.disconnect(); }
    }, { rootMargin: "-30px 0px" });
    ob.observe(ref.current);
    return () => ob.disconnect();
  }, [disabled, seen]);
  return [ref, disabled || KR_REDUCED_MOTION ? true : seen];
}

/* Animate every numeric token in `text` from 0 → final. Layout is
   reserved with a hidden copy of the final text, so nothing shifts. */
function CountUp({ text, duration = 950, delay = 0, style }) {
  const str = String(text);
  const [ref, seen] = useRevealOnce(false);
  const [display, setDisplay] = useState(KR_REDUCED_MOTION ? str : str.replace(/\d[\d,]*(?:\.\d+)?/g, "0"));
  useEffect(() => {
    if (!seen) return;
    if (KR_REDUCED_MOTION) { setDisplay(str); return; }
    let raf, start;
    const tick = (now) => {
      if (start == null) start = now + delay;
      const p = Math.min(1, Math.max(0, (now - start) / duration));
      const e = 1 - Math.pow(1 - p, 3);
      setDisplay(str.replace(/\d[\d,]*(?:\.\d+)?/g, (tok) => {
        const hasCommas = tok.includes(",");
        const dec = (tok.split(".")[1] || "").length;
        const v = parseFloat(tok.replace(/,/g, "")) * e;
        if (dec > 0) return v.toFixed(dec);
        return hasCommas ? Math.round(v).toLocaleString("en-US") : String(Math.round(v));
      }));
      if (p < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [seen, str, duration, delay]);
  return (
    <span ref={ref} style={{ position: "relative", display: "inline-block", ...style }}>
      <span style={{ visibility: "hidden" }} aria-hidden="true">{str}</span>
      <span style={{ position: "absolute", inset: 0 }}>{display}</span>
    </span>
  );
}

/* Numeral that counts when motion is on, renders plain when off. */
function KrNum({ motion, value, delay }) {
  return motion ? <CountUp text={String(value)} delay={delay} /> : <span>{value}</span>;
}

Object.assign(window, { useRevealOnce, CountUp, KrNum, KR_REDUCED_MOTION });
