// landing-anim.jsx — cinematic motion components for the landing hero
// ParticleField, CursorGlow, WordReveal, LiveBars, LiveWave, LiveTimer,
// Typewriter, AnimatedBlobs, ConnectingLine.

// ── ParticleField ───────────────────────────────────────────────────────────
// Canvas of slowly-drifting dots. Sized to its parent (must be position:relative
// or absolutely sized). Respects prefers-reduced-motion.
function ParticleField({ count = 36, opacity = 0.5 }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    const canvas = ref.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    let dpr = Math.min(2, window.devicePixelRatio || 1);
    let W = 0, H = 0, particles = [];
    const reset = () => {
      W = canvas.offsetWidth; H = canvas.offsetHeight;
      canvas.width = W * dpr; canvas.height = H * dpr;
      particles = Array.from({ length: count }, () => ({
        x: Math.random() * W * dpr,
        y: Math.random() * H * dpr,
        vx: (Math.random() - 0.5) * 0.22 * dpr,
        vy: (Math.random() - 0.5) * 0.22 * dpr,
        r: (Math.random() * 1.6 + 0.5) * dpr,
        a: Math.random() * 0.5 + 0.3,
      }));
    };
    reset();
    window.addEventListener('resize', reset);

    let raf;
    const draw = () => {
      const css = getComputedStyle(document.documentElement);
      const accent  = css.getPropertyValue('--accent').trim();
      const accent2 = css.getPropertyValue('--accent2').trim();
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      particles.forEach((p, i) => {
        p.x += p.vx; p.y += p.vy;
        if (p.x < 0 || p.x > canvas.width)  p.vx *= -1;
        if (p.y < 0 || p.y > canvas.height) p.vy *= -1;
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
        ctx.fillStyle = i % 3 === 0 ? accent2 : accent;
        ctx.globalAlpha = p.a * opacity;
        ctx.fill();
      });
      ctx.globalAlpha = 1;
      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => { cancelAnimationFrame(raf); window.removeEventListener('resize', reset); };
  }, [count, opacity]);
  return <canvas ref={ref} className="mo-particles" aria-hidden/>;
}

// ── CursorGlow ──────────────────────────────────────────────────────────────
// Soft accent-coloured glow that lerps toward the pointer inside its parent.
function CursorGlow({ size = 360 }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    const el = ref.current; if (!el) return;
    const parent = el.parentElement; if (!parent) return;
    let tx = parent.offsetWidth / 2, ty = parent.offsetHeight / 2;
    let x = tx, y = ty;
    const move = (e) => {
      const r = parent.getBoundingClientRect();
      tx = e.clientX - r.left;
      ty = e.clientY - r.top;
    };
    let raf;
    const tick = () => {
      x += (tx - x) * 0.08;
      y += (ty - y) * 0.08;
      el.style.transform = `translate(${x}px, ${y}px) translate(-50%, -50%)`;
      raf = requestAnimationFrame(tick);
    };
    tick();
    parent.addEventListener('mousemove', move);
    return () => {
      cancelAnimationFrame(raf);
      parent.removeEventListener('mousemove', move);
    };
  }, []);
  return (
    <div ref={ref} className="cursor-glow" aria-hidden
         style={{ width: size, height: size }}/>
  );
}

// ── WordReveal ──────────────────────────────────────────────────────────────
// Word-by-word fade+rise on mount. Splits string children at whitespace into
// individual `.word-reveal` spans, each with its own --wr-delay. React-element
// children (e.g. <span class="grad-text">) get the class merged onto them
// rather than wrapped, so they keep their own display/clip-path chain.
function WordReveal({ children, baseDelay = 0, step = 60 }) {
  const out = [];
  let idx = 0;

  const wrapText = (part, key) => {
    const delay = baseDelay + idx * step;
    idx++;
    return (
      <span key={key} className="word-reveal" style={{ '--wr-delay': `${delay}ms` }}>
        {part}
      </span>
    );
  };

  const process = (c, key) => {
    if (c == null || c === false || c === true) return;
    if (Array.isArray(c)) {
      c.forEach((cc, i) => process(cc, `${key}-${i}`));
      return;
    }
    if (typeof c === 'string' || typeof c === 'number') {
      const parts = String(c).split(/(\s+)/);
      parts.forEach((part, i) => {
        if (!part) return;
        if (/^\s+$/.test(part)) {
          out.push(part);
        } else {
          out.push(wrapText(part, `${key}-w${i}`));
        }
      });
      return;
    }
    // React.Fragment carries children but strips className/style on cloneElement.
    // Recurse into its children so a `<>…</>` wrapper is transparent.
    if (c.type === React.Fragment) {
      React.Children.forEach(c.props.children, (cc, i) => process(cc, `${key}-f${i}`));
      return;
    }
    // The host editor wraps JSX text children in a runtime <z i="…"> element
    // (rendered as <span class="__om-t">). Detect it and unwrap to the underlying
    // string so we can word-split.
    if (c.props && 'i' in c.props && typeof c.props.children === 'string') {
      process(c.props.children, key);
      return;
    }
    // Plain React element  merge word-reveal class onto it directly.
    const delay = baseDelay + idx * step;
    idx++;
    const className = ((c.props && c.props.className) || '') + ' word-reveal';
    out.push(React.cloneElement(c, {
      key: c.key ?? key,
      className,
      style: { ...(c.props && c.props.style), '--wr-delay': `${delay}ms` },
    }));
  };

  React.Children.forEach(children, (c, i) => process(c, `wr-${i}`));
  return <>{out}</>;
}

// ── LiveBars ────────────────────────────────────────────────────────────────
// Bars whose heights shuffle on an interval — looks like live metrics.
function LiveBars({ count = 12, interval = 1400 }) {
  const rand = () => Math.random() * 70 + 25;
  const [heights, setHeights] = React.useState(
    () => Array.from({ length: count }, rand)
  );
  React.useEffect(() => {
    if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    const id = setInterval(
      () => setHeights(Array.from({ length: count }, rand)),
      interval
    );
    return () => clearInterval(id);
  }, [count, interval]);
  return (
    <div className="live-bars">
      {heights.map((h, i) => (
        <i key={i} style={{ height: h + '%' }}/>
      ))}
    </div>
  );
}

// ── LiveWave ────────────────────────────────────────────────────────────────
// Voice-style waveform that pulses continuously via rAF.
function LiveWave({ bars = 22 }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    const el = ref.current; if (!el) return;
    const items = el.querySelectorAll('i');
    let raf, t = 0;
    const tick = () => {
      t += 0.07;
      items.forEach((b, i) => {
        const h = 18 + Math.abs(Math.sin(t + i * 0.55) * Math.cos(t * 0.6 + i * 0.2)) * 72;
        b.style.height = h.toFixed(1) + '%';
      });
      raf = requestAnimationFrame(tick);
    };
    tick();
    return () => cancelAnimationFrame(raf);
  }, [bars]);
  return (
    <div className="live-wave" ref={ref}>
      {Array.from({ length: bars }).map((_, i) => <i key={i}/>)}
    </div>
  );
}

// ── LiveTimer ───────────────────────────────────────────────────────────────
// Increments a m:ss display every second.
function LiveTimer({ start = 0, max = 600 }) {
  const [t, setT] = React.useState(start);
  React.useEffect(() => {
    const id = setInterval(() => setT(v => (v + 1) % max), 1000);
    return () => clearInterval(id);
  }, [max]);
  const m = Math.floor(t / 60);
  const s = String(t % 60).padStart(2, '0');
  return <>{m}:{s}</>;
}

// ── Typewriter ──────────────────────────────────────────────────────────────
// Cycles through phrases, typing/erasing on a loop. Drives a blinking caret.
function Typewriter({ phrases, speed = 55, eraseSpeed = 28, pauseAfter = 1600 }) {
  const [text, setText] = React.useState('');
  const [phase, setPhase] = React.useState('type'); // type | hold | erase
  const [idx, setIdx]   = React.useState(0);
  React.useEffect(() => {
    if (matchMedia('(prefers-reduced-motion: reduce)').matches) {
      setText(phrases[0]); return;
    }
    const phrase = phrases[idx];
    let timer;
    if (phase === 'type') {
      if (text.length < phrase.length) {
        timer = setTimeout(() => setText(phrase.slice(0, text.length + 1)), speed);
      } else {
        timer = setTimeout(() => setPhase('erase'), pauseAfter);
      }
    } else if (phase === 'erase') {
      if (text.length > 0) {
        timer = setTimeout(() => setText(phrase.slice(0, text.length - 1)), eraseSpeed);
      } else {
        setPhase('type');
        setIdx((idx + 1) % phrases.length);
      }
    }
    return () => clearTimeout(timer);
  }, [text, phase, idx, phrases, speed, eraseSpeed, pauseAfter]);
  return (
    <span className="typewriter">
      {text}<span className="typewriter-caret"/>
    </span>
  );
}

// ── ConnectingLine ──────────────────────────────────────────────────────────
// Animated SVG arc with a "data packet" travelling along it. Connects two
// elements by their ids; updates on scroll/resize.
function ConnectingLine({ fromId, toId, curve = 0.35 }) {
  const ref = React.useRef(null);
  const [path, setPath] = React.useState('');
  React.useEffect(() => {
    const wrap = ref.current; if (!wrap) return;
    const compute = () => {
      const a = document.getElementById(fromId);
      const b = document.getElementById(toId);
      const w = wrap.getBoundingClientRect();
      if (!a || !b) return;
      const ar = a.getBoundingClientRect();
      const br = b.getBoundingClientRect();
      const x1 = ar.left + ar.width / 2 - w.left;
      const y1 = ar.top  + ar.height / 2 - w.top;
      const x2 = br.left + br.width / 2 - w.left;
      const y2 = br.top  + br.height / 2 - w.top;
      const mx = (x1 + x2) / 2;
      const my = (y1 + y2) / 2 - Math.abs(x2 - x1) * curve;
      setPath(`M ${x1} ${y1} Q ${mx} ${my} ${x2} ${y2}`);
    };
    compute();
    window.addEventListener('resize', compute);
    const ro = new ResizeObserver(compute);
    ro.observe(document.body);
    return () => { window.removeEventListener('resize', compute); ro.disconnect(); };
  }, [fromId, toId, curve]);
  return (
    <svg ref={ref} className="connecting-line" aria-hidden>
      <defs>
        <linearGradient id={`cl-${fromId}-${toId}`} x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%"  stopColor="var(--accent)"  stopOpacity="0"/>
          <stop offset="50%" stopColor="var(--accent)"  stopOpacity="0.5"/>
          <stop offset="100%" stopColor="var(--accent2)" stopOpacity="0"/>
        </linearGradient>
      </defs>
      <path d={path} fill="none" stroke={`url(#cl-${fromId}-${toId})`} strokeWidth="1.5" strokeDasharray="4 6">
        <animate attributeName="stroke-dashoffset" from="0" to="-200" dur="6s" repeatCount="indefinite"/>
      </path>
    </svg>
  );
}

Object.assign(window, {
  ParticleField, CursorGlow, WordReveal,
  LiveBars, LiveWave, LiveTimer, Typewriter, ConnectingLine,
});
