/* blueyetutor — Hero particles for /login.
 *
 * Dandelion seeds drift slowly upward across the navy hero. When the cursor
 * approaches a seed, the cursor's velocity is *imparted* to nearby seeds —
 * they get blown away in the direction the mouse is moving, then settle
 * back to baseline drift. Seeds that fly off the canvas respawn from the
 * bottom-right (as if a fresh gust pushed more in).
 */

function HeroParticles() {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    let raf = 0, w = 0, h = 0;
    const dpr = Math.min(window.devicePixelRatio || 1, 2);

    const mouse = { x: -9999, y: -9999, vx: 0, vy: 0, active: false, lastT: 0 };

    const N = 46;                                  // sparse but visible
    const seeds = Array.from({ length: N }, () => spawn(true));

    function spawn(initial) {
      const fromBottomRight = !initial && Math.random() < 0.55;
      return {
        x: fromBottomRight ? 0.9 + Math.random() * 0.15 : Math.random(),
        y: initial ? Math.random() : fromBottomRight ? 0.8 + Math.random() * 0.25 : 1.08,
        baseVx: -0.00006 - Math.random() * 0.00008,    // gentler leftward base
        baseVy: -0.00012 - Math.random() * 0.00016,    // gentler upward base
        vx: 0, vy: 0,
        r: 1.2 + Math.random() * 1.8,
        spokes: 6 + Math.floor(Math.random() * 4),
        rot: Math.random() * Math.PI * 2,
        spin: (Math.random() - 0.5) * 0.0004,
        phase: Math.random() * Math.PI * 2,
        wobble: 0.0005 + Math.random() * 0.0007,
        alpha: 0,
        life: 0,
      };
    }

    function resize() {
      const rect = canvas.getBoundingClientRect();
      w = rect.width; h = rect.height;
      canvas.width  = Math.floor(w * dpr);
      canvas.height = Math.floor(h * dpr);
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    }
    const onResize = () => resize();
    const onMove = e => {
      const rect = canvas.getBoundingClientRect();
      const nx = e.clientX - rect.left;
      const ny = e.clientY - rect.top;
      const now = performance.now();
      const dt = Math.max(8, now - (mouse.lastT || now));
      if (mouse.active) {
        // smooth mouse velocity (px / ms)
        const newVx = (nx - mouse.x) / dt;
        const newVy = (ny - mouse.y) / dt;
        mouse.vx = mouse.vx * 0.5 + newVx * 0.5;
        mouse.vy = mouse.vy * 0.5 + newVy * 0.5;
      }
      mouse.x = nx; mouse.y = ny; mouse.lastT = now; mouse.active = true;
    };
    const onLeave = () => { mouse.active = false; mouse.vx = 0; mouse.vy = 0; };

    resize();
    window.addEventListener("resize", onResize);
    canvas.addEventListener("mousemove", onMove);
    canvas.addEventListener("mouseleave", onLeave);

    let last = performance.now();
    const tick = (t) => {
      const dt = Math.min(64, t - last); last = t;

      // mouse velocity damping over time (so impulse fades when cursor stops)
      mouse.vx *= 0.9;
      mouse.vy *= 0.9;

      ctx.clearRect(0, 0, w, h);

      // soft inner glow
      const grad = ctx.createRadialGradient(w * 0.5, h * 0.35, 0, w * 0.5, h * 0.35, Math.max(w, h) * 0.85);
      grad.addColorStop(0, "rgba(255,255,255,0.06)");
      grad.addColorStop(1, "rgba(255,255,255,0)");
      ctx.fillStyle = grad;
      ctx.fillRect(0, 0, w, h);

      const R = 80;                              // mouse influence radius (px)
      const Rsq = R * R;

      for (let s of seeds) {
        // wobble + spin
        s.phase += s.wobble * dt;
        s.rot += s.spin * dt;
        const wobbleX = Math.sin(s.phase) * 0.00007 * dt;
        s.life += dt;

        // baseline drift settles vx/vy back
        s.vx += (s.baseVx - s.vx) * 0.030;
        s.vy += (s.baseVy - s.vy) * 0.030;

        // mouse "brush" — gentle nudge for nearby seeds. Quartic falloff so
        // only seeds very close get noticeable motion; far ones barely move.
        if (mouse.active) {
          const px = s.x * w, py = s.y * h;
          const dx = px - mouse.x, dy = py - mouse.y;
          const dsq = dx * dx + dy * dy;
          if (dsq < Rsq) {
            const d = Math.sqrt(dsq) || 1;
            const fall = 1 - d / R;
            const sFall = fall * fall * fall;
            // very mild repulsion away from cursor
            const repel = sFall * 0.00008;
            s.vx += (dx / d) * repel;
            s.vy += (dy / d) * repel;
            // soft velocity transfer — brushing direction nudges drift
            s.vx += mouse.vx * sFall * 0.0014;
            s.vy += mouse.vy * sFall * 0.0014;
            // tiny tumble
            s.spin += (Math.random() - 0.5) * sFall * 0.00010;
            s.flare = Math.max(s.flare || 0, sFall * 0.3);
          }
        }
        s.flare = (s.flare || 0) * 0.94;

        // integrate
        s.x += s.vx * dt + wobbleX;
        s.y += s.vy * dt;

        // respawn when offscreen
        if (s.y < -0.1 || s.x < -0.12 || s.x > 1.12) {
          Object.assign(s, spawn(false));
        }

        // fade by life + edge proximity
        const lifeFade = Math.min(1, s.life / 600);
        const edgeFade = Math.min(1, (1 - s.y) * 1.4, s.y * 8, (1 - Math.abs(s.x - 0.5) * 1.7));
        s.alpha = Math.max(0, Math.min(1, lifeFade * edgeFade));

        const px = s.x * w, py = s.y * h;
        ctx.save();
        ctx.translate(px, py);
        ctx.rotate(s.rot);

        // outer spokes
        ctx.globalAlpha = s.alpha * (0.5 + s.flare * 0.5);
        ctx.strokeStyle = "rgba(255,255,255,1)";
        ctx.lineWidth = 0.7;
        const spokeLen = s.r * 4.5;
        for (let k = 0; k < s.spokes; k++) {
          const a = (k / s.spokes) * Math.PI * 2;
          ctx.beginPath();
          ctx.moveTo(Math.cos(a) * 0.7, Math.sin(a) * 0.7);
          ctx.lineTo(Math.cos(a) * spokeLen, Math.sin(a) * spokeLen);
          ctx.stroke();
        }
        // center dot
        ctx.globalAlpha = s.alpha * (0.85 + s.flare * 0.15);
        ctx.fillStyle = "rgba(255,255,255,1)";
        ctx.beginPath();
        ctx.arc(0, 0, s.r * 0.55, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();
      }

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

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("resize", onResize);
      canvas.removeEventListener("mousemove", onMove);
      canvas.removeEventListener("mouseleave", onLeave);
    };
  }, []);

  return <canvas ref={ref} aria-hidden="true" />;
}

Object.assign(window, { HeroParticles });
