/* rathmind — motion.jsx
 * Global interactivity:
 *   - MagneticCursor: custom cursor, dot + outline ring, grows over links/buttons,
 *     magnetic pull on [data-magnetic] targets.
 *   - RouteCurtain: ink-sweep overlay that plays on every route change.
 *   - KineticTitle: wraps inline text, splits to spans, letters tilt/track cursor.
 *
 * No deps. Works with the existing React 18 setup.
 */

const Motion = {};

// ─── Cursor ────────────────────────────────────────────────────
Motion.MagneticCursor = function() {
  const dotRef = React.useRef(null);
  const ringRef = React.useRef(null);
  const labelRef = React.useRef(null);

  const target = React.useRef({ x: 0, y: 0 });        // true mouse
  const ring = React.useRef({ x: 0, y: 0 });          // eased ring
  const dot = React.useRef({ x: 0, y: 0 });           // eased dot (tighter)
  const state = React.useRef({ hovered: null, label: '', pulled: null, pullOffset: { x: 0, y: 0 } });

  React.useEffect(() => {
    // Skip on touch devices
    if (matchMedia('(pointer: coarse)').matches) return;

    const onMove = (e) => {
      target.current.x = e.clientX;
      target.current.y = e.clientY;

      // Magnetic pull on nearest magnetic target
      const mags = document.querySelectorAll('[data-magnetic], a, button');
      let best = null, bestD = 80; // radius
      mags.forEach(el => {
        const r = el.getBoundingClientRect();
        const cx = r.left + r.width / 2, cy = r.top + r.height / 2;
        const dx = e.clientX - cx, dy = e.clientY - cy;
        const d = Math.hypot(dx, dy);
        const mag = el.hasAttribute('data-magnetic');
        const radius = mag ? 120 : Math.max(r.width, r.height) / 2 + 24;
        if (d < radius && d < bestD) {
          best = { el, cx, cy, dx, dy, d, radius, mag };
          bestD = d;
        }
      });

      if (best && best.mag) {
        // Pull the target itself slightly toward the cursor
        const strength = 0.35 * (1 - best.d / best.radius);
        best.el.style.transform = `translate(${-best.dx * strength}px, ${-best.dy * strength}px)`;
        state.current.pulled = best.el;
      } else if (state.current.pulled) {
        state.current.pulled.style.transform = '';
        state.current.pulled = null;
      }

      if (best) {
        state.current.hovered = best.el;
        state.current.label =
          best.el.dataset.cursor ||
          (best.el.tagName === 'A' ? 'open' : best.el.tagName === 'BUTTON' ? 'tap' : '') || '';
      } else {
        state.current.hovered = null;
        state.current.label = '';
        if (state.current.pulled) { state.current.pulled.style.transform = ''; state.current.pulled = null; }
      }
    };

    const onOut = () => {
      if (state.current.pulled) { state.current.pulled.style.transform = ''; state.current.pulled = null; }
    };

    window.addEventListener('mousemove', onMove, { passive: true });
    window.addEventListener('mouseleave', onOut);

    let raf = 0;
    const tick = () => {
      // Ease
      const easeR = 0.14, easeD = 0.32;
      ring.current.x += (target.current.x - ring.current.x) * easeR;
      ring.current.y += (target.current.y - ring.current.y) * easeR;
      dot.current.x += (target.current.x - dot.current.x) * easeD;
      dot.current.y += (target.current.y - dot.current.y) * easeD;

      const d = dotRef.current, r = ringRef.current, l = labelRef.current;
      const hov = state.current.hovered;
      // Arrow cursor tip sits AT the mouse point (top-left anchored, not centered)
      if (d) d.style.transform = `translate(${dot.current.x}px, ${dot.current.y}px)`;
      if (r) {
        const scale = hov ? (hov.hasAttribute('data-magnetic') ? 2.8 : 2.2) : 1;
        r.style.transform = `translate(${ring.current.x}px, ${ring.current.y}px) translate(-50%, -50%) scale(${scale})`;
        r.style.opacity = hov ? '1' : '0.5';
        r.style.borderColor = hov ? 'var(--ink)' : 'var(--ink-3, #8c8474)';
        r.style.mixBlendMode = hov ? 'difference' : 'normal';
      }
      if (l) {
        l.style.transform = `translate(${ring.current.x + 22}px, ${ring.current.y + 22}px)`;
        l.textContent = state.current.label;
        l.style.opacity = state.current.label ? '1' : '0';
      }

      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);

    document.documentElement.classList.add('has-custom-cursor');

    // Hide system cursor when it leaves / show when it returns
    const onEnter = () => document.documentElement.classList.add('has-custom-cursor');
    const onLeave = () => document.documentElement.classList.remove('has-custom-cursor');
    document.addEventListener('mouseenter', onEnter);
    document.addEventListener('mouseleave', onLeave);

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseleave', onOut);
      document.removeEventListener('mouseenter', onEnter);
      document.removeEventListener('mouseleave', onLeave);
      document.documentElement.classList.remove('has-custom-cursor');
    };
  }, []);

  // Hidden on touch
  if (typeof matchMedia !== 'undefined' && matchMedia('(pointer: coarse)').matches) return null;

  return (
    <>
      <style>{`
        html.has-custom-cursor, html.has-custom-cursor * { cursor: none !important; }
        @media (pointer: coarse) {
          html.has-custom-cursor, html.has-custom-cursor * { cursor: auto !important; }
        }
      `}</style>
      <div ref={ringRef} style={{
        position: 'fixed', top: 0, left: 0, width: 28, height: 28,
        border: '1px solid var(--ink)', borderRadius: '50%',
        pointerEvents: 'none', zIndex: 99998,
        transition: 'opacity 220ms, border-color 220ms, mix-blend-mode 0s',
        willChange: 'transform',
      }}/>
      <svg ref={dotRef} width="22" height="22" viewBox="0 0 22 22" style={{
        position: 'fixed', top: 0, left: 0,
        pointerEvents: 'none', zIndex: 99999,
        willChange: 'transform',
        filter: 'drop-shadow(0 1px 2px rgba(0,0,0,0.15))',
      }}>
        {/* Classic arrow cursor — tip at (1,1) so the hotspot matches mouse position */}
        <path
          d="M1 1 L1 16 L5 12 L7.5 18 L10 17 L7.8 11 L13 11 Z"
          fill="var(--ink)"
          stroke="var(--paper)"
          strokeWidth="1"
          strokeLinejoin="round"
        />
      </svg>
      <div ref={labelRef} className="num" style={{
        position: 'fixed', top: 0, left: 0,
        padding: '4px 8px', background: 'var(--ink)', color: 'var(--paper)',
        fontSize: 9, letterSpacing: '0.14em',
        pointerEvents: 'none', zIndex: 99999, opacity: 0,
        transition: 'opacity 160ms', whiteSpace: 'nowrap',
      }}/>
    </>
  );
};

// ─── Route curtain ─────────────────────────────────────────────
// Renders an overlay that animates in on route change and out again.
// Usage: <Motion.RouteCurtain routeKey={route}/> — any change to routeKey triggers.
Motion.RouteCurtain = function({ routeKey }) {
  const [phase, setPhase] = React.useState('idle'); // 'idle' | 'in' | 'out'
  const first = React.useRef(true);

  React.useEffect(() => {
    if (first.current) { first.current = false; return; }
    setPhase('in');
    const t1 = setTimeout(() => setPhase('out'), 520);
    const t2 = setTimeout(() => setPhase('idle'), 1040);
    return () => { clearTimeout(t1); clearTimeout(t2); };
  }, [routeKey]);

  // 12 vertical stripes sweep across
  const stripes = 12;
  return (
    <div aria-hidden style={{
      position: 'fixed', inset: 0, zIndex: 9000,
      pointerEvents: 'none', display: 'flex',
    }}>
      {Array.from({ length: stripes }).map((_, i) => {
        const delay = (i / stripes) * 0.22; // staggered
        const t = phase === 'in' ? 1 : phase === 'out' ? 0 : 0;
        // 'in' → fully covers; 'out' → exits upward
        const y = phase === 'idle' ? '100%'
                : phase === 'in' ? '0%'
                : '-100%';
        return (
          <div key={i} style={{
            flex: 1, background: '#0b0b0d',
            transform: `translateY(${y})`,
            transition: `transform 620ms cubic-bezier(.76,0,.24,1)`,
            transitionDelay: `${delay}s`,
          }}/>
        );
      })}
      {phase === 'in' && (
        <div style={{
          position: 'absolute', inset: 0, display: 'flex',
          alignItems: 'center', justifyContent: 'center',
          fontFamily: 'var(--serif)', fontSize: 26, fontStyle: 'italic',
          color: 'var(--paper)', letterSpacing: '-0.01em',
          animation: 'curtainFade 520ms ease',
        }}>
          <style>{`@keyframes curtainFade { from { opacity: 0 } to { opacity: 1 } }`}</style>
          · rathmind ·
        </div>
      )}
    </div>
  );
};

// ─── Kinetic title ─────────────────────────────────────────────
// Splits a string into letter spans that tilt toward the cursor.
// Accepts a plain string; wrap manually around <em> if needed using spans input.
Motion.KineticTitle = function({ text, style, accent }) {
  const ref = React.useRef(null);

  React.useEffect(() => {
    const root = ref.current;
    if (!root) return;
    const spans = [...root.querySelectorAll('span.k-l')];
    const onMove = (e) => {
      spans.forEach(s => {
        const r = s.getBoundingClientRect();
        const cx = r.left + r.width / 2, cy = r.top + r.height / 2;
        const dx = e.clientX - cx, dy = e.clientY - cy;
        const d = Math.hypot(dx, dy);
        const radius = 240;
        if (d < radius) {
          const f = 1 - d / radius;
          const tx = -dx * 0.08 * f, ty = -dy * 0.08 * f;
          const rz = (dx * 0.04) * f;
          s.style.transform = `translate(${tx}px, ${ty}px) rotate(${rz}deg)`;
        } else {
          s.style.transform = '';
        }
      });
    };
    window.addEventListener('mousemove', onMove, { passive: true });
    return () => window.removeEventListener('mousemove', onMove);
  }, [text]);

  // Preserve whitespace + a dot glyph special-cased
  const parts = [];
  let key = 0;
  for (const ch of text) {
    if (ch === ' ') parts.push(<span key={key++} style={{ display: 'inline-block', width: '0.3em' }}>&nbsp;</span>);
    else if (ch === '\n') parts.push(<br key={key++}/>);
    else parts.push(
      <span key={key++} className="k-l" style={{
        display: 'inline-block', transition: 'transform 320ms cubic-bezier(.2,.7,.2,1)',
        willChange: 'transform',
      }}>{ch}</span>
    );
  }
  return <span ref={ref} style={style}>{parts}</span>;
};

window.Motion = Motion;
