// Letter-by-letter animated heading (ported from the framer-motion
// AnimatedText component to this project's React/Babel + GSAP setup).
// No underline floor line. Animates when scrolled into view.
//
// Usage:
//   <AnimatedText text="ARNEL BISCARRA" accentFrom={6} stack />
//   <AnimatedText parts={[{t:'ABOUT '},{t:'ME',accent:true},{t:'.'}]} />
function AnimatedText({ text, parts, className = '', accentFrom = null, stack = false, duration = 0.5, delay = 0.05, repel = false, repelRadius = 110, repelStrength = 14 }) {
  const ref = React.useRef(null);

  // Flatten to a list of letters carrying { ch, accent }.
  const letters = [];
  let label = '';
  if (parts && parts.length) {
    parts.forEach((p) => {
      label += p.t;
      Array.from(p.t).forEach((ch) => letters.push({ ch, accent: !!p.accent }));
    });
  } else {
    label = text || '';
    Array.from(label).forEach((ch, i) => letters.push({ ch, accent: accentFrom != null && i >= accentFrom }));
  }

  // Group letters into words (split on spaces) so words wrap as units.
  const words = [];
  let cur = [];
  letters.forEach((l) => {
    if (l.ch === ' ') { words.push(cur); cur = []; }
    else cur.push(l);
  });
  words.push(cur);

  React.useEffect(() => {
    if (!ref.current || !window.gsap) return;
    const spans = ref.current.querySelectorAll('.at-letter');
    window.gsap.set(spans, { yPercent: 110, opacity: 0 });

    const play = () => {
      window.gsap.to(spans, {
        yPercent: 0, opacity: 1, ease: 'back.out(1.7)', duration, stagger: delay, delay: 0.1,
        onComplete: () => { ref.current && ref.current.querySelectorAll('.at-letter-wrap').forEach((w) => { w.style.overflow = 'visible'; }); },
      });
    };

    const io = new IntersectionObserver((entries, obs) => {
      entries.forEach((e) => { if (e.isIntersecting) { play(); obs.disconnect(); } });
    }, { threshold: 0.25 });
    io.observe(ref.current);
    return () => io.disconnect();
  }, []);

  // Cursor repel: each letter springs away from the pointer, eases back to rest.
  React.useEffect(() => {
    if (!repel || !ref.current || !window.gsap) return;
    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    // Touch devices have no real cursor, so "pointermove" instead fires
    // during scroll/touch-drag, scattering the letters as you scroll.
    if (!window.matchMedia('(hover: hover) and (pointer: fine)').matches) return;
    const el = ref.current;
    const letters = Array.from(el.querySelectorAll('.at-letter'));
    if (!letters.length) return;
    const state = letters.map((letter) => ({
      letter, ox: 0, oy: 0,
      xTo: window.gsap.quickTo(letter, 'x', { duration: 0.55, ease: 'power3.out' }),
      yTo: window.gsap.quickTo(letter, 'y', { duration: 0.55, ease: 'power3.out' }),
    }));

    const measure = () => {
      const cr = el.getBoundingClientRect();
      state.forEach((s) => {
        const lr = s.letter.getBoundingClientRect();
        s.ox = lr.left - cr.left + lr.width / 2;
        s.oy = lr.top - cr.top + lr.height / 2;
      });
    };
    requestAnimationFrame(() => requestAnimationFrame(measure));
    // The entrance drop-in (see the effect above: each letter animates in
    // via yPercent over `duration` + a per-letter `stagger` delay, up to
    // ~1.2s for a 13-letter heading) is still playing 2 frames after
    // mount, so that early measure() captures mid-animation positions —
    // every distance came out wrong for the rest of the page's life,
    // since nothing else ever re-measured until the cursor happened to
    // leave and re-enter. Re-measure once more after the entrance has
    // certainly settled.
    const settleTimer = setTimeout(measure, 1400);
    el.addEventListener('pointerenter', measure);

    const onMove = (e) => {
      const cr = el.getBoundingClientRect();
      const mx = e.clientX - cr.left, my = e.clientY - cr.top;
      state.forEach((s) => {
        const dx = s.ox - mx, dy = s.oy - my;
        const dist = Math.sqrt(dx * dx + dy * dy);
        if (dist < repelRadius && dist > 0) {
          const force = Math.pow(1 - dist / repelRadius, 2) * repelStrength;
          const angle = Math.atan2(dy, dx);
          s.xTo(Math.cos(angle) * force);
          s.yTo(Math.sin(angle) * force);
        } else {
          s.xTo(0); s.yTo(0);
        }
      });
    };
    const onLeave = () => state.forEach((s) => { s.xTo(0); s.yTo(0); });

    el.addEventListener('pointermove', onMove);
    el.addEventListener('pointerleave', onLeave);
    window.addEventListener('resize', measure);
    return () => {
      clearTimeout(settleTimer);
      el.removeEventListener('pointerenter', measure);
      el.removeEventListener('pointermove', onMove);
      el.removeEventListener('pointerleave', onLeave);
      window.removeEventListener('resize', measure);
    };
  }, [repel, repelRadius, repelStrength]);

  let li = 0;
  return (
    <span className={`animated-text ${stack ? 'at-stack' : ''} ${className}`} ref={ref}>
      <span className="sr-only">{label}</span>
      {words.map((word, wi) => (
        <React.Fragment key={wi}>
          <span className="at-word">
            {word.map((l) => {
              const key = li++;
              return (
                <span key={key} className="at-letter-wrap" aria-hidden="true">
                  <span className="at-letter" style={l.accent ? { color: 'var(--color-accent)' } : undefined}>{l.ch}</span>
                </span>
              );
            })}
          </span>
          {wi < words.length - 1 && (stack ? <span className="at-break" aria-hidden="true"></span> : <span className="at-gap" aria-hidden="true">&nbsp;</span>)}
        </React.Fragment>
      ))}
    </span>
  );
}

window.AnimatedText = AnimatedText;

// Typewriter: cycles through a list of words, typing then deleting each.
function Typewriter({ words, speed = 70, delayBetweenWords = 1600, className = '', cursor = true, cursorChar = '|' }) {
  const [displayText, setDisplayText] = React.useState('');
  const [isDeleting, setIsDeleting] = React.useState(false);
  const [wordIndex, setWordIndex] = React.useState(0);
  const [charIndex, setCharIndex] = React.useState(0);
  const [showCursor, setShowCursor] = React.useState(true);
  const currentWord = words[wordIndex];

  React.useEffect(() => {
    const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (reduced) { setDisplayText(currentWord); return; }
    const timeout = setTimeout(() => {
      if (!isDeleting) {
        if (charIndex < currentWord.length) {
          setDisplayText(currentWord.substring(0, charIndex + 1));
          setCharIndex(charIndex + 1);
        } else {
          setTimeout(() => setIsDeleting(true), delayBetweenWords);
        }
      } else {
        if (charIndex > 0) {
          setDisplayText(currentWord.substring(0, charIndex - 1));
          setCharIndex(charIndex - 1);
        } else {
          setIsDeleting(false);
          setWordIndex((prev) => (prev + 1) % words.length);
        }
      }
    }, isDeleting ? speed / 2 : speed);
    return () => clearTimeout(timeout);
  }, [charIndex, currentWord, isDeleting, speed, delayBetweenWords, wordIndex, words]);

  React.useEffect(() => {
    if (!cursor) return;
    const iv = setInterval(() => setShowCursor((p) => !p), 500);
    return () => clearInterval(iv);
  }, [cursor]);

  return (
    <span className={className}>
      {displayText}
      {cursor && <span aria-hidden="true" style={{ marginLeft: 4, opacity: showCursor ? 1 : 0, color: 'var(--color-accent)' }}>{cursorChar}</span>}
    </span>
  );
}

window.Typewriter = Typewriter;
