// Cursor.jsx — JS-driven dot + ring cursor, take three.
//
// What broke last time, and how this is fixed:
//
//   1. mix-blend-mode: difference  → invisible against same-tone
//      backgrounds (a white ring on a white page = nothing). FIX:
//      removed entirely. Each cursor element uses a solid dual-tone
//      design — dark fill with a 1px white halo — so it always reads
//      against any background.
//
//   2. mouseleave/mouseenter opacity toggling → if a child element's
//      mouseleave fires without mouseenter on parent, the cursor got
//      stuck at opacity:0. FIX: removed all opacity toggling. Cursor
//      stays at opacity:1 always; if mouse exits the doc the cursor
//      simply rests at its last on-screen position until next move.
//
//   3. cursor: none on a page where Cursor.jsx silently fails to
//      mount  → no cursor at all. FIX: index.html sets `cursor: url(
//      /assets/cursor.svg) ... , auto` as the BASE OS cursor — so
//      even if this React component never runs, the user sees a
//      proper cursor. This component just adds a richer ring on top
//      that lerps and grows on hover. The OS cursor underneath
//      stays visible the whole time.
//
// Behaviour:
//   · DOT  small dark dot pinned 1:1 to the actual pointer (no lag)
//   · RING larger ring lerping behind the dot at k=0.18 (soft follow)
//   · Hover detection (a, button, etc.): ring grows + accent-fills
//
// All visual states are driven by CSS variables / classes from
// index.html. This file just updates the CSS vars on every move.

const { useEffect, useRef } = React;

function Cursor() {
  const dotRef   = useRef(null);
  const ringRef  = useRef(null);
  const arrowRef = useRef(null);

  useEffect(() => {
    if (window.matchMedia('(pointer: coarse)').matches) return;
    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;

    let mx = window.innerWidth / 2;
    let my = window.innerHeight / 2;
    let rx = mx, ry = my;
    let raf = 0;

    const onMove = (e) => {
      mx = e.clientX;
      my = e.clientY;
      if (dotRef.current) {
        dotRef.current.style.setProperty('--x', mx + 'px');
        dotRef.current.style.setProperty('--y', my + 'px');
      }
      // Arrow follows the pointer 1:1 (no lerp) when active — feels
      // crisper for a navigation cue than a soft trail.
      if (arrowRef.current) {
        arrowRef.current.style.setProperty('--x', mx + 'px');
        arrowRef.current.style.setProperty('--y', my + 'px');
      }
    };

    const tick = () => {
      // Soft lerp on the ring — k=0.18 gives a gentle trail without
      // ever feeling laggy. Dot stays pinned 1:1 to the actual cursor.
      rx += (mx - rx) * 0.18;
      ry += (my - ry) * 0.18;
      if (ringRef.current) {
        ringRef.current.style.setProperty('--x', rx.toFixed(2) + 'px');
        ringRef.current.style.setProperty('--y', ry.toFixed(2) + 'px');
      }
      raf = requestAnimationFrame(tick);
    };

    // Single document-level hover delegate — keeps the cursor reactive
    // to dynamic content (modals, drawers, mounted-later elements).
    // Selectors collected from the spots in this codebase that look
    // interactive when hovered: links, buttons, anything with
    // role="button", inputs, and the explicit data-cursor opt-in.
    //
    // The hero nav zones use [data-hero-nav="left"|"right"] — when the
    // pointer enters one, we swap the dot+ring out for a big chevron
    // arrow (CSS handles the visibility via `qw-cursor-arrow-left/right`
    // classes on <html>).
    const HOVERABLE = 'a, button, [role="button"], [data-cursor="hover"], input, textarea, select';
    const onOver = (e) => {
      const t = e.target;
      if (!t?.closest) return;
      // Hero-nav arrow takes precedence — when over a left/right zone
      // we don't also want the small "hover" ring class.
      const navEl = t.closest('[data-hero-nav]');
      if (navEl) {
        const dir = navEl.getAttribute('data-hero-nav');
        document.documentElement.classList.add('qw-cursor-arrow-' + dir);
        document.documentElement.classList.remove('qw-cursor-arrow-' + (dir === 'left' ? 'right' : 'left'));
        document.documentElement.classList.remove('qw-cursor-hover');
        return;
      }
      // Standard hoverable targets.
      if (t.closest(HOVERABLE)) {
        document.documentElement.classList.add('qw-cursor-hover');
      }
    };
    const onOut = (e) => {
      const next = e.relatedTarget;
      // If we're moving from a hero-nav zone to something else, clear
      // both arrow classes. If we're moving WITHIN the hero-nav zones
      // (left → right or vice versa) the next onOver handler will set
      // the right one again.
      const prevNav = e.target?.closest && e.target.closest('[data-hero-nav]');
      const nextNav = next?.closest && next.closest('[data-hero-nav]');
      if (prevNav && !nextNav) {
        document.documentElement.classList.remove('qw-cursor-arrow-left');
        document.documentElement.classList.remove('qw-cursor-arrow-right');
      }
      // Standard hoverable cleanup.
      if (!next?.closest || !next.closest(HOVERABLE)) {
        document.documentElement.classList.remove('qw-cursor-hover');
      }
    };

    // Tell the CSS we have a JS cursor so it can hide the OS cursor
    // exactly while we're rendering. If this component ever unmounts
    // unexpectedly the cleanup below restores the OS cursor.
    document.documentElement.classList.add('qw-cursor-on');

    window.addEventListener('mousemove',     onMove, { passive: true });
    document.addEventListener('pointerover', onOver, { capture: true });
    document.addEventListener('pointerout',  onOut,  { capture: true });
    raf = requestAnimationFrame(tick);

    return () => {
      window.removeEventListener('mousemove',    onMove);
      document.removeEventListener('pointerover', onOver, { capture: true });
      document.removeEventListener('pointerout',  onOut,  { capture: true });
      cancelAnimationFrame(raf);
      document.documentElement.classList.remove('qw-cursor-on');
      document.documentElement.classList.remove('qw-cursor-hover');
      document.documentElement.classList.remove('qw-cursor-arrow-left');
      document.documentElement.classList.remove('qw-cursor-arrow-right');
    };
  }, []);

  return (
    <>
      <div ref={dotRef}  className="qw-cursor-dot"  aria-hidden />
      <div ref={ringRef} className="qw-cursor-ring" aria-hidden />
      {/* Hero-nav arrow — simplest possible chevron. Two straight
          lines meeting at an apex on the right, wide opening angle
          (~120°), thin stroke, rounded ends. No curves, no extra
          parts. The .qw-cursor-arrow-left CSS state flips it via
          scaleX(-1). */}
      <div ref={arrowRef} className="qw-cursor-arrow" aria-hidden>
        <svg viewBox="0 0 40 40">
          <path className="stroke" d="M 14 8 L 28 20 L 14 32"/>
        </svg>
      </div>
    </>
  );
}

window.Cursor = Cursor;
