// motion.jsx — shared animation primitives
// All effects are CSS-driven where possible (transform/opacity = GPU-friendly).

// ---------- CycleWord ----------
// Cycles through variants with typewriter-style char swap.
// `variants` is an array of strings, swapped every `interval` ms.
const CycleWord = ({ variants, interval = 2400, className = '' }) => {
  const [idx, setIdx] = React.useState(0);
  const [phase, setPhase] = React.useState('in'); // 'in' | 'out'

  React.useEffect(() => {
    if (!variants || variants.length < 2) return;
    let t1;
    const tick = () => {
      setPhase('out');
      t1 = setTimeout(() => {
        setIdx(i => (i + 1) % variants.length);
        setPhase('in');
      }, 380);
    };
    const id = setInterval(tick, interval);
    return () => { clearInterval(id); clearTimeout(t1); };
  }, [variants, interval]);

  const word = variants[idx] || '';
  return (
    <span className={`cycle-word ${className} cycle-${phase}`}>
      <span className="cycle-inner">
        {word.split('').map((ch, i) => (
          <span
            key={`${idx}-${i}`}
            className="cycle-ch"
            style={{ animationDelay: `${i * 22}ms` }}
          >{ch === ' ' ? ' ' : ch}</span>
        ))}
      </span>
    </span>
  );
};

// ---------- Reveal ----------
// IntersectionObserver-based scroll reveal. Adds .is-in once visible.
// When `stagger` is true, children automatically index via --i for CSS delays.
const Reveal = ({ children, className = '', stagger = false, as: Tag = 'div', delay = 0, ...rest }) => {
  const ref = React.useRef(null);
  const [shown, setShown] = React.useState(false);

  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (shown) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting) {
          setShown(true);
          io.disconnect();
        }
      });
    }, { rootMargin: '-8% 0px -8% 0px', threshold: 0.05 });
    io.observe(el);
    return () => io.disconnect();
  }, [shown]);

  let content = children;
  if (stagger && Array.isArray(children)) {
    content = children.map((c, i) => {
      if (!c) return c;
      if (typeof c === 'string' || typeof c === 'number') {
        return <span key={i} className="reveal-item" style={{ '--i': i }}>{c}</span>;
      }
      return React.cloneElement(c, {
        key: c.key ?? i,
        className: `${c.props.className || ''} reveal-item`.trim(),
        style: { ...(c.props.style || {}), '--i': i }
      });
    });
  }

  return (
    <Tag
      ref={ref}
      className={`reveal ${stagger ? 'reveal-stagger' : ''} ${shown ? 'is-in' : ''} ${className}`.trim()}
      style={{ '--reveal-delay': `${delay}ms`, ...(rest.style || {}) }}
      {...rest}
    >
      {content}
    </Tag>
  );
};

window.CycleWord = CycleWord;
window.Reveal = Reveal;
