/* Motion helpers for the site kit. Everything degrades to static under
   prefers-reduced-motion (handled in tokens/animation.css). */

function useInView(ref, { once = true, threshold = 0.18, rootMargin = '0px 0px -8% 0px' } = {}) {
  const [inView, setInView] = React.useState(false);
  React.useEffect(() => {
    const el = ref.current;
    if (!el || typeof IntersectionObserver === 'undefined') { setInView(true); return; }
    // Already on screen at mount (hero, above-the-fold): reveal without waiting.
    const r = el.getBoundingClientRect();
    const vh = window.innerHeight || 800;
    if (r.top < vh && r.bottom > 0) { setInView(true); if (once) return; }
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting) { setInView(true); if (once) io.unobserve(el); }
        else if (!once) setInView(false);
      });
    }, { threshold, rootMargin });
    io.observe(el);
    // Safety net: never leave content invisible if the observer never fires.
    const t = setTimeout(() => setInView(true), 1200);
    return () => { io.disconnect(); clearTimeout(t); };
  }, []);
  return inView;
}

/** Scroll-triggered reveal. mode: up | left | right | scale | clip | blur */
function Reveal({ mode = 'up', delay = 0, as = 'div', className = '', style, children, ...rest }) {
  const ref = React.useRef(null);
  const inView = useInView(ref);
  const Tag = as;
  return (
    <Tag
      ref={ref}
      data-m={mode}
      className={'vsp-rev ' + (inView ? 'is-in ' : '') + className}
      style={{ transitionDelay: delay + 'ms', ...style }}
      {...rest}
    >
      {children}
    </Tag>
  );
}

/** Wraps children in Reveals with an incremental delay. */
function Stagger({ mode = 'up', step = 90, start = 0, style, className, children }) {
  return (
    <div className={className} style={style}>
      {React.Children.map(children, (child, i) => (
        <Reveal mode={mode} delay={start + i * step}>{child}</Reveal>
      ))}
    </div>
  );
}

/** Counts up to `to` when scrolled into view. */
function Counter({ to, decimals = 0, duration = 1400, prefix = '', suffix = '', style }) {
  const ref = React.useRef(null);
  const inView = useInView(ref);
  const [val, setVal] = React.useState(0);
  React.useEffect(() => {
    if (!inView) return;
    if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) { setVal(to); return; }
    let raf, t0;
    const tick = (t) => {
      if (!t0) t0 = t;
      const p = Math.min(1, (t - t0) / duration);
      setVal(to * (1 - Math.pow(1 - p, 3)));
      if (p < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    // Safety net: never leave the number stuck at zero.
    const safety = setTimeout(() => setVal(to), duration + 600);
    return () => { cancelAnimationFrame(raf); clearTimeout(safety); };
  }, [inView]);
  const shown = decimals ? val.toFixed(decimals).replace('.', ',') : Math.round(val).toString();
  return <span ref={ref} style={style}>{prefix}{shown}{suffix}</span>;
}

/** Vertical parallax on scroll. */
function Parallax({ depth = 40, children, style, className }) {
  const ref = React.useRef(null);
  const [y, setY] = React.useState(0);
  React.useEffect(() => {
    if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    let raf = null;
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => {
        raf = null;
        const el = ref.current;
        if (!el) return;
        const r = el.getBoundingClientRect();
        const vh = window.innerHeight || 800;
        const p = (r.top + r.height / 2 - vh / 2) / vh;
        setY(Math.max(-1, Math.min(1, p)) * depth);
      });
    };
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    return () => { window.removeEventListener('scroll', onScroll); window.removeEventListener('resize', onScroll); };
  }, [depth]);
  return (
    <div ref={ref} className={className} style={{ ...style, transform: 'translate3d(0,' + y.toFixed(1) + 'px,0)', willChange: 'transform' }}>
      {children}
    </div>
  );
}

/** Infinite horizontal ticker. */
function Marquee({ items = [], speed = 34, separator = '·', style }) {
  const run = [...items, ...items];
  return (
    <div style={{ overflow: 'hidden', ...style }}>
      <div style={{ display: 'flex', width: 'max-content', animation: 'vsp-marquee ' + speed + 's linear infinite' }}>
        {run.map((it, i) => (
          <span key={i} style={{ display: 'inline-flex', alignItems: 'center', gap: 'var(--space-6)', paddingRight: 'var(--space-6)', fontFamily: 'var(--font-ui)', fontWeight: 700, letterSpacing: 'var(--ls-eyebrow)', textTransform: 'uppercase', fontSize: 'var(--text-sm)', whiteSpace: 'nowrap' }}>
            {it}<span style={{ opacity: .45 }}>{separator}</span>
          </span>
        ))}
      </div>
    </div>
  );
}

/** Pulsing ring behind a circular element (used on the WhatsApp FAB). */
function PulseRing({ color = 'var(--teal-500)', size = 56 }) {
  return (
    <>
      {[0, 1].map((i) => (
        <span key={i} style={{
          position: 'absolute', inset: 0, width: size, height: size, borderRadius: '50%',
          background: color, animation: 'vsp-pulse-ring 2.6s var(--ease-out) infinite',
          animationDelay: i * 1.3 + 's', pointerEvents: 'none',
        }} />
      ))}
    </>
  );
}

Object.assign(window, { useInView, Reveal, Stagger, Counter, Parallax, Marquee, PulseRing });
