/* global React */
const { useEffect, useRef, useState } = React;

/* =========================================================
   CursorSpotlight — page-wide soft radial glow that follows
   the cursor, blending differently on dark vs light bg.
   ========================================================= */
function CursorSpotlight() {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let raf = 0, tx = window.innerWidth / 2, ty = window.innerHeight / 2;
    let cx = tx, cy = ty;

    const onMove = (e) => {
      tx = e.clientX; ty = e.clientY;
      el.classList.add("visible");
    };
    const onLeave = () => el.classList.remove("visible");
    const tick = () => {
      cx += (tx - cx) * 0.18;
      cy += (ty - cy) * 0.18;
      el.style.setProperty("--mx", cx + "px");
      el.style.setProperty("--my", cy + "px");
      // also expose page-wide for hero parallax
      document.documentElement.style.setProperty("--mx-pct", (cx / window.innerWidth).toFixed(3));
      document.documentElement.style.setProperty("--my-pct", (cy / window.innerHeight).toFixed(3));
      raf = requestAnimationFrame(tick);
    };
    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseleave", onLeave);
    raf = requestAnimationFrame(tick);
    return () => {
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("mouseleave", onLeave);
      cancelAnimationFrame(raf);
    };
  }, []);
  return <div ref={ref} className="cursor-spotlight" aria-hidden="true"/>;
}

/* =========================================================
   ParticleField — floating dots with mouse repulsion.
   Lightweight canvas, ~60 particles, soft connections.
   ========================================================= */
function ParticleField({ density = 0.00006, color = "rgba(76,175,99,0.55)", linkColor = "rgba(76,175,99,0.18)", height = "100%", interactive = true }) {
  const canvasRef = useRef(null);
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    let w = 0, h = 0, particles = [], mouse = { x: -9999, y: -9999, active: false };
    let raf = 0;

    const resize = () => {
      const r = canvas.getBoundingClientRect();
      w = r.width; h = r.height;
      canvas.width = w * dpr; canvas.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      const target = Math.max(28, Math.min(120, Math.floor(w * h * density)));
      particles = new Array(target).fill(0).map(() => ({
        x: Math.random() * w,
        y: Math.random() * h,
        vx: (Math.random() - 0.5) * 0.25,
        vy: (Math.random() - 0.5) * 0.25,
        r: Math.random() * 1.6 + 0.6,
      }));
    };
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(canvas);

    const onMove = (e) => {
      const r = canvas.getBoundingClientRect();
      mouse.x = e.clientX - r.left;
      mouse.y = e.clientY - r.top;
      mouse.active = mouse.x >= 0 && mouse.y >= 0 && mouse.x <= w && mouse.y <= h;
    };
    const onLeave = () => { mouse.active = false; };
    if (interactive) {
      window.addEventListener("mousemove", onMove);
      window.addEventListener("mouseleave", onLeave);
    }

    const draw = () => {
      ctx.clearRect(0, 0, w, h);

      // update
      for (const p of particles) {
        p.x += p.vx; p.y += p.vy;
        if (p.x < 0 || p.x > w) p.vx *= -1;
        if (p.y < 0 || p.y > h) p.vy *= -1;

        if (mouse.active) {
          const dx = p.x - mouse.x, dy = p.y - mouse.y;
          const d2 = dx*dx + dy*dy;
          if (d2 < 14000) {
            const f = (1 - d2 / 14000) * 0.6;
            p.x += (dx / Math.sqrt(d2 || 1)) * f;
            p.y += (dy / Math.sqrt(d2 || 1)) * f;
          }
        }
      }

      // links
      ctx.strokeStyle = linkColor;
      ctx.lineWidth = 1;
      for (let i = 0; i < particles.length; i++) {
        for (let j = i + 1; j < particles.length; j++) {
          const a = particles[i], b = particles[j];
          const dx = a.x - b.x, dy = a.y - b.y;
          const d2 = dx*dx + dy*dy;
          if (d2 < 12000) {
            ctx.globalAlpha = 1 - d2 / 12000;
            ctx.beginPath();
            ctx.moveTo(a.x, a.y);
            ctx.lineTo(b.x, b.y);
            ctx.stroke();
          }
        }
      }
      ctx.globalAlpha = 1;

      // dots
      ctx.fillStyle = color;
      for (const p of particles) {
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
        ctx.fill();
      }

      raf = requestAnimationFrame(draw);
    };
    raf = requestAnimationFrame(draw);

    return () => {
      cancelAnimationFrame(raf);
      ro.disconnect();
      if (interactive) {
        window.removeEventListener("mousemove", onMove);
        window.removeEventListener("mouseleave", onLeave);
      }
    };
  }, [density, color, linkColor, interactive]);

  return (
    <canvas
      ref={canvasRef}
      className="particle-canvas"
      style={{ height }}
      aria-hidden="true"
    />
  );
}

/* =========================================================
   Reveal — IntersectionObserver-driven scroll reveal.
   ========================================================= */
function Reveal({ children, as: Tag = "div", stagger = false, threshold = 0.18, className = "", style }) {
  const ref = useRef(null);
  const [shown, setShown] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if ("IntersectionObserver" in window) {
      const obs = new IntersectionObserver((entries) => {
        for (const e of entries) {
          if (e.isIntersecting) {
            setShown(true);
            obs.disconnect();
          }
        }
      }, { threshold });
      obs.observe(el);
      return () => obs.disconnect();
    } else {
      setShown(true);
    }
  }, [threshold]);
  const cls = (stagger ? "reveal-stagger " : "reveal ") + (shown ? "in " : "") + className;
  return <Tag ref={ref} className={cls} style={style}>{children}</Tag>;
}

/* =========================================================
   MagneticBtn — subtle pull-toward-cursor on a button
   ========================================================= */
function magneticHandler(e) {
  const el = e.currentTarget;
  const r = el.getBoundingClientRect();
  const x = e.clientX - r.left;
  const y = e.clientY - r.top;
  el.style.setProperty("--bx", x + "px");
  el.style.setProperty("--by", y + "px");
  const dx = (x - r.width / 2) / r.width;
  const dy = (y - r.height / 2) / r.height;
  el.style.transform = `translate(${dx * 6}px, ${dy * 6}px)`;
}
function magneticReset(e) {
  e.currentTarget.style.transform = "";
}

/* Apply magnetic effect to all .btn-primary / .btn-secondary buttons */
function MagneticBindings() {
  useEffect(() => {
    const apply = () => {
      const btns = document.querySelectorAll(".btn-primary, .btn-secondary");
      btns.forEach(b => {
        if (b.dataset.magnetic) return;
        b.dataset.magnetic = "1";
        b.addEventListener("mousemove", magneticHandler);
        b.addEventListener("mouseleave", magneticReset);
      });
    };
    apply();
    const mo = new MutationObserver(apply);
    mo.observe(document.body, { childList: true, subtree: true });
    return () => mo.disconnect();
  }, []);
  return null;
}

/* =========================================================
   TiltCard wrapper — adds 3D tilt + cursor glow
   ========================================================= */
function TiltCard({ children, className = "", maxTilt = 6, ...rest }) {
  const ref = useRef(null);
  const onMove = (e) => {
    const el = ref.current; if (!el) return;
    const r = el.getBoundingClientRect();
    const x = e.clientX - r.left, y = e.clientY - r.top;
    const rx = ((y / r.height) - 0.5) * -maxTilt;
    const ry = ((x / r.width) - 0.5) * maxTilt;
    el.style.transform = `perspective(900px) rotateX(${rx}deg) rotateY(${ry}deg) translateZ(0)`;
    el.style.setProperty("--cx", x + "px");
    el.style.setProperty("--cy", y + "px");
  };
  const onLeave = () => {
    const el = ref.current; if (!el) return;
    el.style.transform = "";
  };
  return (
    <div
      ref={ref}
      className={"tilt-card " + className}
      onMouseMove={onMove}
      onMouseLeave={onLeave}
      {...rest}
    >
      {children}
    </div>
  );
}

/* =========================================================
   CountUp — animates a number from 0 to target when in view
   ========================================================= */
function CountUp({ to, duration = 1400, prefix = "", suffix = "", decimals = 0, className = "", trigger = true }) {
  const [val, setVal] = useState(0);
  const ref = useRef(null);
  const started = useRef(false);
  useEffect(() => {
    if (!trigger) return;
    const el = ref.current; if (!el) return;
    const start = () => {
      if (started.current) return;
      started.current = true;
      const startTs = performance.now();
      const tick = (t) => {
        const p = Math.min(1, (t - startTs) / duration);
        const eased = 1 - Math.pow(1 - p, 3);
        setVal(to * eased);
        if (p < 1) requestAnimationFrame(tick);
      };
      requestAnimationFrame(tick);
    };
    if ("IntersectionObserver" in window) {
      const obs = new IntersectionObserver((entries) => {
        for (const e of entries) if (e.isIntersecting) { start(); obs.disconnect(); }
      }, { threshold: 0.5 });
      obs.observe(el);
      return () => obs.disconnect();
    } else {
      start();
    }
  }, [to, duration, trigger]);
  const formatted = decimals > 0
    ? val.toFixed(decimals)
    : Math.round(val).toLocaleString();
  return <span ref={ref} className={"count-up " + className}>{prefix}{formatted}{suffix}</span>;
}

Object.assign(window, { CursorSpotlight, ParticleField, Reveal, MagneticBindings, TiltCard, CountUp });
