/* global React */
// Shared motion primitives for NAL marketing site.
const { useEffect, useRef, useState } = React;

// Triggers when element enters viewport. Returns ref + inView boolean.
function useReveal(opts = {}) {
  const ref = useRef(null);
  const [inView, setInView] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    const io = new IntersectionObserver(
      ([e]) => {
        if (e.isIntersecting) { setInView(true); if (opts.once !== false) io.disconnect(); }
        else if (opts.once === false) { setInView(false); }
      },
      { threshold: opts.threshold ?? 0.15, rootMargin: opts.rootMargin ?? '0px 0px -8% 0px' }
    );
    io.observe(ref.current);
    return () => io.disconnect();
  }, []);
  return [ref, inView];
}

// Fade+slide reveal.
function Reveal({ children, delay = 0, y = 20, x = 0, blur = false, as = 'div', className = '', style = {} }) {
  const [ref, inView] = useReveal();
  const Tag = as;
  return (
    <Tag
      ref={ref}
      className={className}
      style={{
        opacity: inView ? 1 : 0,
        transform: inView ? 'translate3d(0,0,0)' : `translate3d(${x}px,${y}px,0)`,
        filter: blur && !inView ? 'blur(8px)' : 'blur(0)',
        transition: `opacity 900ms cubic-bezier(.16,1,.3,1) ${delay}ms, transform 900ms cubic-bezier(.16,1,.3,1) ${delay}ms, filter 900ms cubic-bezier(.16,1,.3,1) ${delay}ms`,
        ...style,
      }}
    >{children}</Tag>
  );
}

// Stagger children by index * step (ms).
function Stagger({ children, step = 80, base = 0, y = 20 }) {
  return React.Children.map(children, (c, i) => (
    <Reveal delay={base + i * step} y={y}>{c}</Reveal>
  ));
}

// Per-word/char reveal for headlines. `by` = 'word' | 'char'.
function SplitReveal({ text, by = 'word', delay = 0, step = 40, style = {}, className = '' }) {
  const [ref, inView] = useReveal({ threshold: 0.3 });
  const parts = by === 'word' ? text.split(' ') : text.split('');
  return (
    <span ref={ref} className={className} style={{ display: 'inline-block', ...style }}>
      {parts.map((p, i) => (
        <span key={i} style={{ display: 'inline-block', overflow: 'hidden', verticalAlign: 'top', paddingBottom: '0.22em', marginBottom: '-0.22em', paddingRight: '0.08em', marginRight: '-0.08em' }}>
          <span style={{
            display: 'inline-block',
            transform: inView ? 'translate3d(0,0,0)' : 'translate3d(0,105%,0)',
            opacity: inView ? 1 : 0,
            transition: `transform 900ms cubic-bezier(.16,1,.3,1) ${delay + i * step}ms, opacity 700ms cubic-bezier(.16,1,.3,1) ${delay + i * step}ms`,
          }}>{p}{by === 'word' && i < parts.length - 1 ? '\u00A0' : ''}</span>
        </span>
      ))}
    </span>
  );
}

// Animate number from 0 to value when in view.
function useCountUp(value, duration = 1600) {
  const [ref, inView] = useReveal({ threshold: 0.4 });
  const [n, setN] = useState(0);
  useEffect(() => {
    if (!inView) return;
    const start = performance.now();
    let raf;
    const tick = (t) => {
      const p = Math.min(1, (t - start) / duration);
      const eased = 1 - Math.pow(1 - p, 3);
      setN(value * eased);
      if (p < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [inView, value, duration]);
  return [ref, n];
}

// Type-in effect.
function TypeIn({ text, charMs = 32, startDelay = 400, caret = true, style = {} }) {
  const [i, setI] = useState(0);
  const [started, setStarted] = useState(false);
  useEffect(() => {
    const t1 = setTimeout(() => setStarted(true), startDelay);
    return () => clearTimeout(t1);
  }, [startDelay]);
  useEffect(() => {
    if (!started) return;
    if (i >= text.length) return;
    const t = setTimeout(() => setI(i + 1), charMs);
    return () => clearTimeout(t);
  }, [i, started, text, charMs]);
  return (
    <span style={style}>
      {text.slice(0, i)}
      {caret && <span style={{ display: 'inline-block', width: '0.55em', background: 'currentColor', height: '1em', verticalAlign: '-0.15em', marginLeft: 2, animation: 'nal-blink 1s steps(1) infinite' }} />}
    </span>
  );
}

// Magnetic hover — subtle pull of children toward cursor.
function Magnetic({ children, strength = 18, style = {}, className = '' }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    const onMove = (e) => {
      const r = el.getBoundingClientRect();
      const x = e.clientX - r.left - r.width / 2;
      const y = e.clientY - r.top - r.height / 2;
      el.style.transform = `translate(${(x / r.width) * strength}px, ${(y / r.height) * strength}px)`;
    };
    const onLeave = () => { el.style.transform = 'translate(0,0)'; };
    const parent = el.parentElement;
    parent.addEventListener('mousemove', onMove);
    parent.addEventListener('mouseleave', onLeave);
    return () => { parent.removeEventListener('mousemove', onMove); parent.removeEventListener('mouseleave', onLeave); };
  }, [strength]);
  return (
    <span ref={ref} className={className} style={{ display: 'inline-block', transition: 'transform 380ms cubic-bezier(.16,1,.3,1)', ...style }}>
      {children}
    </span>
  );
}

// Marquee scroller.
function MarqueeRow({ children, duration = 40, reverse = false, style = {} }) {
  return (
    <div style={{ display: 'flex', overflow: 'hidden', ...style }}>
      <div style={{
        display: 'flex', gap: 48, whiteSpace: 'nowrap',
        animation: `nal-marquee ${duration}s linear infinite ${reverse ? 'reverse' : ''}`,
      }}>{children}{children}</div>
    </div>
  );
}

Object.assign(window, { useReveal, Reveal, Stagger, SplitReveal, useCountUp, TypeIn, Magnetic, MarqueeRow });
