// ============================================================
// Beyond the Falls - Fairy-dust cursor magic
//   • FairyDust    : canvas particle trail w/ gentle gravity (a fairy's wand)
//   • useMagicText : briefly tints HTML text near the cursor, then fades
//   • fairyUtil    : shared color helpers + brand-magic palette
// ============================================================
const { useEffect: useEffectF, useRef: useRefF } = React;

const RM_FAIRY = !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);

// hsl(h 0-360, s 0-1, l 0-1) -> [r,g,b] 0-255
function hslF(h, s, l) {
  h = ((h % 360) + 360) % 360;
  const c = (1 - Math.abs(2 * l - 1)) * s;
  const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
  const m = l - c / 2;
  let r = 0, g = 0, b = 0;
  if (h < 60) [r, g, b] = [c, x, 0]; else if (h < 120) [r, g, b] = [x, c, 0];
  else if (h < 180) [r, g, b] = [0, c, x]; else if (h < 240) [r, g, b] = [0, x, c];
  else if (h < 300) [r, g, b] = [x, 0, c]; else [r, g, b] = [c, 0, x];
  return [(r + m) * 255, (g + m) * 255, (b + m) * 255];
}
const lerpF = (a, b, t) => a + (b - a) * t;

// soft 4-point sparkle
function drawStar(ctx, x, y, r, rot) {
  ctx.save();
  ctx.translate(x, y);
  ctx.rotate(rot);
  ctx.beginPath();
  for (let i = 0; i < 4; i++) {
    const a = (i / 4) * Math.PI * 2;
    ctx.lineTo(Math.cos(a) * r, Math.sin(a) * r);
    const a2 = a + Math.PI / 4;
    ctx.lineTo(Math.cos(a2) * r * 0.34, Math.sin(a2) * r * 0.34);
  }
  ctx.closePath();
  ctx.fill();
  ctx.restore();
}

// on-brand fairy palette - biolum/acid greens with a whisper of lavender & pink
const FAIRY_PALETTE = ['#7CFFCB', '#C6FF4A', '#A8FFE0', '#E9FFB0', '#BBA6FF', '#FFC2EE'];

// ------------------------------------------------------------
// Canvas particle trail. Mounts a canvas covering targetRef and
// emits sparkles on mousemove that drift up, then fall with gravity.
// ------------------------------------------------------------
function FairyDust({ targetRef, palette = FAIRY_PALETTE, density = 1, gravity = 0.055, zIndex = 40 }) {
  const canvasRef = useRefF(null);

  useEffectF(() => {
    const target = targetRef && targetRef.current;
    const canvas = canvasRef.current;
    if (!target || !canvas || RM_FAIRY) return;

    const ctx = canvas.getContext('2d');
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    let W = 0, H = 0;

    const resize = () => {
      const r = target.getBoundingClientRect();
      W = r.width; H = r.height;
      canvas.width = Math.max(1, Math.round(W * dpr));
      canvas.height = Math.max(1, Math.round(H * dpr));
      canvas.style.width = W + 'px';
      canvas.style.height = H + 'px';
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(target);

    const parts = [];
    const MAX = 420;
    const rand = (a, b) => a + Math.random() * (b - a);
    const pick = () => palette[(Math.random() * palette.length) | 0];
    let lx = 0, ly = 0, lt = 0, has = false;

    const spawn = (x, y, speed) => {
      const count = Math.round(rand(1, 3) * density * Math.min(1 + speed * 0.05, 3.2));
      for (let i = 0; i < count; i++) {
        if (parts.length > MAX) break;
        const a = Math.random() * Math.PI * 2;
        const sp = rand(0.2, 1.7);
        parts.push({
          x: x + rand(-5, 5), y: y + rand(-5, 5),
          vx: Math.cos(a) * sp + rand(-0.5, 0.5),
          vy: Math.sin(a) * sp - rand(0.3, 1.3),   // little upward puff first
          life: 1, decay: rand(0.011, 0.024),
          size: rand(1.1, 3.6),
          color: pick(),
          star: Math.random() < 0.24,
          spin: rand(0, Math.PI * 2),
          twk: rand(0.1, 0.3),
        });
      }
    };

    const onMove = (e) => {
      const r = target.getBoundingClientRect();
      const x = e.clientX - r.left, y = e.clientY - r.top;
      const now = performance.now();
      let speed = 0;
      if (has) { const dt = Math.max(now - lt, 1); speed = (Math.hypot(x - lx, y - ly) / dt) * 16; }
      lx = x; ly = y; lt = now; has = true;
      spawn(x, y, speed);
    };
    target.addEventListener('mousemove', onMove);

    let raf = 0, frame = 0;
    const draw = () => {
      frame++;
      ctx.clearRect(0, 0, W, H);
      ctx.globalCompositeOperation = 'lighter';
      for (let i = parts.length - 1; i >= 0; i--) {
        const p = parts[i];
        p.vy += gravity;          // gravity pulls the dust down
        p.vx *= 0.985;
        p.x += p.vx; p.y += p.vy;
        p.life -= p.decay;
        if (p.life <= 0) { parts.splice(i, 1); continue; }
        const tw = 0.65 + 0.35 * Math.sin(frame * p.twk + p.spin); // twinkle
        ctx.globalAlpha = Math.max(0, p.life) * tw;
        ctx.shadowBlur = 9;
        ctx.shadowColor = p.color;
        ctx.fillStyle = p.color;
        if (p.star) {
          drawStar(ctx, p.x, p.y, p.size * 1.7 * (0.4 + p.life), p.spin + (1 - p.life) * 5);
        } else {
          ctx.beginPath();
          ctx.arc(p.x, p.y, Math.max(0.4, p.size * p.life), 0, Math.PI * 2);
          ctx.fill();
        }
      }
      ctx.globalAlpha = 1;
      ctx.shadowBlur = 0;
      ctx.globalCompositeOperation = 'source-over';
      raf = requestAnimationFrame(draw);
    };
    raf = requestAnimationFrame(draw);

    return () => {
      cancelAnimationFrame(raf);
      ro.disconnect();
      target.removeEventListener('mousemove', onMove);
    };
  }, []);

  return <canvas ref={canvasRef} style={{ position: 'absolute', inset: 0, pointerEvents: 'none', zIndex }} aria-hidden="true" />;
}

// ------------------------------------------------------------
// Briefly tint an HTML text element with an iridescent glow that
// follows the cursor, fading back to its base color when idle.
// elRef     : the text element to tint
// targetRef : the element to listen on for mousemove/leave
// baseRGBA  : [r,g,b,a] the resting text color
// ------------------------------------------------------------
function useMagicText(elRef, targetRef, baseRGBA, radius = 240) {
  useEffectF(() => {
    const el = elRef && elRef.current;
    const target = targetRef && targetRef.current;
    if (!el || !target) return;

    const [br, bg, bb, ba] = baseRGBA;
    const baseStr = `rgba(${br},${bg},${bb},${ba})`;

    el.style.webkitBackgroundClip = 'text';
    el.style.backgroundClip = 'text';
    el.style.webkitTextFillColor = 'transparent';
    el.style.color = 'transparent';
    el.style.backgroundImage = `radial-gradient(circle ${radius}px at 50% 50%, ${baseStr} 0%, ${baseStr} 65%)`;

    if (RM_FAIRY) return;

    const st = { tx: 0, ty: 0, act: 0, tact: 0, t: 0 };
    const onMove = (e) => { const r = el.getBoundingClientRect(); st.tx = e.clientX - r.left; st.ty = e.clientY - r.top; st.tact = 1; };
    const onLeave = () => { st.tact = 0; };
    target.addEventListener('mousemove', onMove);
    target.addEventListener('mouseleave', onLeave);

    let raf = 0;
    const tick = () => {
      st.act += (st.tact - st.act) * 0.06;
      if (st.act > 0.01) st.t += 0.012;
      const h = 150 + 85 * Math.sin(st.t);
      const c = hslF(h, 0.85, 0.62);
      const r = Math.round(lerpF(br, c[0], st.act));
      const g = Math.round(lerpF(bg, c[1], st.act));
      const b = Math.round(lerpF(bb, c[2], st.act));
      const a = lerpF(ba, 1, st.act);
      el.style.backgroundImage =
        `radial-gradient(circle ${radius}px at ${st.tx}px ${st.ty}px, rgba(${r},${g},${b},${a}) 0%, rgba(${r},${g},${b},${a * 0.5 + ba * 0.5}) 28%, ${baseStr} 66%)`;
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);

    return () => {
      cancelAnimationFrame(raf);
      target.removeEventListener('mousemove', onMove);
      target.removeEventListener('mouseleave', onLeave);
    };
  }, []);
}

window.fairyUtil = { hslF, lerpF, drawStar, PALETTE: FAIRY_PALETTE, RM: RM_FAIRY };
Object.assign(window, { FairyDust, useMagicText });
