// Motion-kit — shared animation primitives used across all three directions.
//   • IntersectionObserver-based scroll reveals (.mk-reveal)
//   • CountUp (number rolls up when scrolled into view)
//   • useInView hook

(function initReveals() {
  if (typeof window === 'undefined') return;
  if (window.__mkRevealsInit) return;
  window.__mkRevealsInit = true;

  // Inject reveal CSS once
  const style = document.createElement('style');
  style.textContent = `
    .mk-reveal { opacity: 0; transform: translateY(16px); transition: opacity 0.7s cubic-bezier(.2,.6,.2,1), transform 0.7s cubic-bezier(.2,.6,.2,1); will-change: opacity, transform; }
    .mk-reveal.mk-in { opacity: 1; transform: none; }
    .mk-reveal-delay-1 { transition-delay: 0.08s; }
    .mk-reveal-delay-2 { transition-delay: 0.16s; }
    .mk-reveal-delay-3 { transition-delay: 0.24s; }
    @media (prefers-reduced-motion: reduce) {
      .mk-reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
    }
  `;
  document.head.appendChild(style);

  // Set up a single observer that watches all .mk-reveal nodes.
  const io = new IntersectionObserver((entries) => {
    entries.forEach((e) => {
      if (e.isIntersecting) {
        e.target.classList.add('mk-in');
        io.unobserve(e.target);
      }
    });
  }, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' });

  // Scan existing + watch for new ones via MutationObserver
  const scan = (root) => {
    root.querySelectorAll('.mk-reveal:not(.mk-in)').forEach((n) => io.observe(n));
  };
  const mo = new MutationObserver((muts) => {
    muts.forEach((m) => m.addedNodes.forEach((n) => {
      if (!(n instanceof Element)) return;
      if (n.classList && n.classList.contains('mk-reveal') && !n.classList.contains('mk-in')) io.observe(n);
      scan(n);
    }));
  });
  // Kick off once the DOM is ready
  const start = () => { scan(document.body); mo.observe(document.body, { childList: true, subtree: true }); };
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
  else start();
})();

// Reveal wrapper — drop around any block to fade+rise it on scroll.
function Reveal({ children, delay = 0, as: As = 'div', style, className = '', ...rest }) {
  const cls = ['mk-reveal', delay ? `mk-reveal-delay-${delay}` : '', className].filter(Boolean).join(' ');
  return <As className={cls} style={style} {...rest}>{children}</As>;
}

// useInView — returns [ref, inView] using IntersectionObserver.
function useInView(opts = {}) {
  const ref = React.useRef(null);
  const [inView, setInView] = React.useState(false);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting) { setInView(true); io.unobserve(e.target); } });
    }, { threshold: opts.threshold ?? 0.35 });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return [ref, inView];
}

// CountUp — number counts up from 0 (or `from`) to `to` when scrolled into view.
function CountUp({ to, from = 0, duration = 1400, format = (v) => v.toLocaleString('en-AU'), prefix = '', suffix = '', style, className }) {
  const [ref, inView] = useInView({ threshold: 0.4 });
  const [v, setV] = React.useState(from);
  React.useEffect(() => {
    if (!inView) return;
    const reduce = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
    if (reduce) { setV(to); return; }
    const start = performance.now();
    let raf;
    const tick = (now) => {
      const t = Math.min(1, (now - start) / duration);
      const eased = 1 - Math.pow(1 - t, 3);
      setV(from + (to - from) * eased);
      if (t < 1) raf = requestAnimationFrame(tick);
      else setV(to);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [inView, to, from, duration]);
  const isNum = typeof to === 'number';
  return (
    <span ref={ref} className={className} style={{ fontVariantNumeric: 'tabular-nums', ...style }}>
      {prefix}{isNum ? format(Math.round(v)) : to}{suffix}
    </span>
  );
}

// useTween — animates a numeric value smoothly whenever it changes.
function useTween(target, { duration = 500, easing = (t) => 1 - Math.pow(1 - t, 3) } = {}) {
  const [v, setV] = React.useState(target);
  const fromRef = React.useRef(target);
  React.useEffect(() => {
    const reduce = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
    if (reduce) { setV(target); fromRef.current = target; return; }
    const from = fromRef.current;
    const start = performance.now();
    let raf;
    const tick = (now) => {
      const t = Math.min(1, (now - start) / duration);
      const eased = easing(t);
      setV(from + (target - from) * eased);
      if (t < 1) raf = requestAnimationFrame(tick);
      else { setV(target); fromRef.current = target; }
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [target, duration]);
  return v;
}

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

// KineticWord — cycles through a list of words with a quick slide+fade swap.
// Use inside a larger heading; it renders inline and italic by default.
function KineticWord({ words, interval = 2200, style }) {
  const [i, setI] = React.useState(0);
  const reduce = typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
  React.useEffect(() => {
    if (reduce) return;
    const id = setInterval(() => setI((n) => (n + 1) % words.length), interval);
    return () => clearInterval(id);
  }, [words.length, interval, reduce]);
  // measure widest word to prevent layout shift
  return (
    <span style={{ position: 'relative', display: 'inline-block', verticalAlign: 'baseline', ...style }}>
      {/* invisible sizer — widest word */}
      <span aria-hidden style={{ fontStyle: 'italic', fontWeight: 500, visibility: 'hidden', whiteSpace: 'nowrap' }}>
        {words.reduce((a, b) => (a.length >= b.length ? a : b))}
      </span>
      <span style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'baseline', overflow: 'hidden' }}>
        {words.map((w, j) => (
          <span key={j} style={{
            position: 'absolute', left: 0, whiteSpace: 'nowrap',
            fontStyle: 'italic', fontWeight: 500,
            opacity: j === i ? 1 : 0,
            transform: j === i ? 'translateY(0)' : (j === (i - 1 + words.length) % words.length ? 'translateY(-0.25em)' : 'translateY(0.25em)'),
            transition: 'opacity 420ms cubic-bezier(.2,.8,.2,1), transform 420ms cubic-bezier(.2,.8,.2,1)',
          }}>{w}</span>
        ))}
      </span>
    </span>
  );
}

Object.assign(window, { KineticWord });
