// Trivan — primitives d'animation : texte cinétique, compteurs, rotation de mots, défilement
// Exporte sur window : SplitWords, CountUp, WordRotate, Marquee

const { useState: useAnimState, useEffect: useAnimEffect, useRef: useAnimRef } = React;

/* Révèle un texte mot par mot (masque + translation).
   À utiliser dans un élément portant data-reveal="split". */
function SplitWords({ text, start = 0 }) {
  const words = String(text).split(" ");
  return (
    <React.Fragment>
      {words.map((w, i) => (
        <React.Fragment key={i}>
          <span className="sw"><span className="sww" style={{ "--w": start + i }}>{w}</span></span>
          {i < words.length - 1 ? " " : null}
        </React.Fragment>
      ))}
    </React.Fragment>
  );
}

/* Compteur animé déclenché à l'apparition à l'écran. */
function CountUp({ to, decimals = 0, suffix = "", duration = 1400 }) {
  const ref = useAnimRef(null);
  const [val, setVal] = useAnimState(0);

  useAnimEffect(() => {
    const el = ref.current;
    if (!el) return;
    let raf = 0;
    let started = false;
    const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    const run = () => {
      if (started) return;
      started = true;
      window.removeEventListener("scroll", onScroll);
      if (reduce) { setVal(to); return; }
      const t0 = performance.now();
      const tick = (now) => {
        const p = Math.min(1, (now - t0) / duration);
        const e = 1 - Math.pow(1 - p, 3);
        setVal(to * e);
        if (p < 1) raf = requestAnimationFrame(tick);
      };
      raf = requestAnimationFrame(tick);
    };
    const check = () => {
      const r = el.getBoundingClientRect();
      if (r.top < window.innerHeight - 20 && r.bottom > 0) run();
    };
    const onScroll = () => check();
    check();
    const t = setTimeout(check, 150);
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => {
      clearTimeout(t);
      window.removeEventListener("scroll", onScroll);
      cancelAnimationFrame(raf);
    };
  }, [to]);

  const fmt = val.toLocaleString("fr-FR", {
    minimumFractionDigits: decimals,
    maximumFractionDigits: decimals,
  });
  return <span ref={ref} style={{ fontVariantNumeric: "tabular-nums" }}>{fmt}{suffix}</span>;
}

/* Fait tourner une liste de mots au même emplacement (pile en grille,
   le mot courant fond/translate). Largeur réservée par le mot le plus long. */
function WordRotate({ words, className = "", interval = 2400 }) {
  const [idx, setIdx] = useAnimState(0);

  useAnimEffect(() => {
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    const t = setInterval(() => setIdx((v) => (v + 1) % words.length), interval);
    return () => clearInterval(t);
  }, [words.length, interval]);

  return (
    <span className={"wr " + className}>
      {words.map((w, k) => (
        <span key={w} className={"wr-w" + (k === idx ? " on" : "")} aria-hidden={k !== idx}>
          {w}
        </span>
      ))}
    </span>
  );
}

/* Bande défilante en boucle continue : le contenu est répété trois fois,
   l'animation translate d'un tiers de la piste (boucle invisible). */
function Marquee({ children, duration = 40, reverse = false }) {
  return (
    <div className="marq">
      <div
        className="marq-track"
        style={{ "--marq-dur": duration + "s", animationDirection: reverse ? "reverse" : "normal" }}
      >
        <div className="marq-seg">{children}</div>
        <div className="marq-seg" aria-hidden="true">{children}</div>
        <div className="marq-seg" aria-hidden="true">{children}</div>
      </div>
    </div>
  );
}

Object.assign(window, { SplitWords, CountUp, WordRotate, Marquee });
