// utils.jsx — formatting + animation hooks
const fmt = {
  usd: (n) => "$" + Math.round(n).toLocaleString("en-US"),
  usdShort: (n) => {
    if (n >= 1_000_000) return "$" + (n / 1_000_000).toFixed(2) + "M";
    if (n >= 1_000)     return "$" + (n / 1_000).toFixed(0) + "K";
    return "$" + Math.round(n);
  },
  usdAbbr: (n) => {
    if (Math.abs(n) >= 1_000_000) return "$" + (n / 1_000_000).toFixed(1) + "M";
    if (Math.abs(n) >= 1_000)     return "$" + (n / 1_000).toFixed(0) + "K";
    return "$" + Math.round(n);
  },
  pct: (n, d = 1) => n.toFixed(d) + "%",
  num: (n) => Math.round(n).toLocaleString("en-US"),
  days: (n) => n.toFixed(1) + "d",
};

// easeOutCubic
const easeOut = (t) => 1 - Math.pow(1 - t, 3);

// useInView — fires once when element is intersecting (any portion). Also
// fires immediately if the element is already on-screen at mount time, which
// matters for above-the-fold sections (hero) where IO may not deliver an
// initial entry on some browsers.
const useInView = (ref, threshold = 0) => {
  const [seen, setSeen] = React.useState(false);
  React.useEffect(() => {
    if (!ref.current || seen) return;
    const el = ref.current;
    // immediate check
    const r = el.getBoundingClientRect();
    const vh = window.innerHeight || document.documentElement.clientHeight;
    if (r.top < vh && r.bottom > 0) {
      setSeen(true);
      return;
    }
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => {
        if (e.isIntersecting) {
          setSeen(true);
          io.disconnect();
        }
      });
    }, { threshold: 0 });
    io.observe(el);
    return () => io.disconnect();
  }, [ref, threshold, seen]);
  return seen;
};

// useCounter — animates a number from 0 → target over `dur` ms once `start` is true
const useCounter = (target, dur = 1400, start = true, decimals = 0) => {
  const [val, setVal] = React.useState(0);
  React.useEffect(() => {
    if (!start) return;
    let raf;
    const t0 = performance.now();
    const tick = (now) => {
      const t = Math.min(1, (now - t0) / dur);
      const v = target * easeOut(t);
      setVal(decimals === 0 ? Math.round(v) : Number(v.toFixed(decimals)));
      if (t < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [target, dur, start, decimals]);
  return val;
};

// useProgress — 0→1 over dur ms once start is true
const useProgress = (dur = 1400, start = true, delay = 0) => {
  const [p, setP] = React.useState(0);
  React.useEffect(() => {
    if (!start) return;
    let raf, started = false;
    const t0 = performance.now() + delay;
    const tick = (now) => {
      if (now < t0) { raf = requestAnimationFrame(tick); return; }
      const t = Math.min(1, (now - t0) / dur);
      setP(easeOut(t));
      if (t < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [dur, start, delay]);
  return p;
};

// useIsMobile — true when viewport width is at or below the breakpoint.
// Re-evaluates on resize so orientation changes update the layout.
const useIsMobile = (bp = 768) => {
  const get = () => typeof window !== "undefined" && window.innerWidth <= bp;
  const [m, setM] = React.useState(get);
  React.useEffect(() => {
    const onR = () => setM(get());
    window.addEventListener("resize", onR);
    return () => window.removeEventListener("resize", onR);
  }, [bp]);
  return m;
};

window.fmt = fmt;
window.easeOut = easeOut;
window.useInView = useInView;
window.useCounter = useCounter;
window.useProgress = useProgress;
window.useIsMobile = useIsMobile;

// Brand theme — sourced from ROI_DATA.colors
// Swap primary/accent in data.js to match the practice's brand
window.THEME = window.ROI_DATA.colors || { primary: "#00A3FF", accent: "#FF8B4A" };

// Generate rgba from hex at given opacity
window.colorAt = (hex, opacity) => {
  const r = parseInt(hex.slice(1,3), 16);
  const g = parseInt(hex.slice(3,5), 16);
  const b = parseInt(hex.slice(5,7), 16);
  return `rgba(${r},${g},${b},${opacity})`;
};
