/* KayaCore — animation helpers. Calm, balanced motion.
   All effects respect prefers-reduced-motion and degrade to the end-state. */

function kcReducedMotion() {
  return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
}

/* Observe an element; returns [ref, inView]. Fires once by default. */
function useInView(opts) {
  const { threshold = 0.2, once = true } = opts || {};
  const ref = React.useRef(null);
  const [inView, setInView] = React.useState(false);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (typeof IntersectionObserver === 'undefined') { setInView(true); return; }
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting) { setInView(true); if (once) io.unobserve(e.target); }
        else if (!once) setInView(false);
      });
    }, { threshold, rootMargin: '0px 0px -8% 0px' });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return [ref, inView];
}

/* Count a number up when it scrolls into view. Preserves any prefix/suffix
   (e.g. "$", "+", "%", "×", "/7"). Non-numeric values (e.g. "Zero") render as-is. */
function CountUp({ value, duration = 1200, className, style }) {
  const [ref, inView] = useInView({ threshold: 0.5, once: false });
  const str = String(value);
  const m = str.match(/^(\D*)([\d,]+(?:\.\d+)?)(.*)$/);
  const reduce = kcReducedMotion();
  // Initialize to the FINAL value so it's always correct, even if nothing
  // animates (reduced motion, no IntersectionObserver, frozen rAF, etc.).
  const [disp, setDisp] = React.useState(str);

  // When the value changes (e.g. navigating vertical -> vertical and the same
  // CountUp instance is reused), snap to the new final value first.
  React.useEffect(() => { setDisp(str); }, [str]);

  React.useEffect(() => {
    if (!m || reduce || !inView) return;
    const target = parseFloat(m[2].replace(/,/g, ''));
    const decimals = (m[2].split('.')[1] || '').length;
    const hasComma = m[2].includes(',');
    const render = (val) => {
      let s = decimals ? val.toFixed(decimals) : Math.round(val).toString();
      if (hasComma) s = Number(s).toLocaleString('en-US');
      setDisp(m[1] + s + m[3]);
    };
    const steps = 36;
    let i = 0;
    render(0);
    const id = setInterval(() => {
      i++;
      const p = i / steps;
      render(target * (1 - Math.pow(1 - p, 3)));
      if (i >= steps) { clearInterval(id); render(target); }
    }, Math.max(16, duration / steps));
    // guarantee the final value lands even if the interval is throttled/stopped
    const settle = setTimeout(() => { clearInterval(id); render(target); }, duration + 700);
    return () => { clearInterval(id); clearTimeout(settle); };
  }, [inView, str]);

  return <span ref={ref} className={className} style={style}>{disp}</span>;
}

Object.assign(window, { useInView, CountUp, kcReducedMotion });
