/* global React */
// Cursor system + section background canvases + scroll motion

const { useEffect, useRef, useState } = React;

// ============================================================
// Cursor — 4 styles: off, aurora, spotlight, comet
// ============================================================
window.Cursor = function Cursor({ style = "aurora" }) {
  const dotRef = useRef(null);
  const auroraRef = useRef(null);
  const spotRef = useRef(null);
  const cometRef = useRef(null);

  useEffect(() => {
    document.body.dataset.cursorStyle = style;
    if (style === "off") return;

    let mx = window.innerWidth / 2, my = window.innerHeight / 2;
    let lx = mx, ly = my;     // lagged for aurora
    let raf = 0;
    let trail = []; // for comet

    function move(e) {
      mx = e.clientX; my = e.clientY;
      const t = e.target;
      const interactive = t && t.closest && t.closest("a, button, .service-card, .work-row, .chip, [data-cursor-hover]");
      if (dotRef.current) {
        dotRef.current.style.transform = `translate(${mx}px, ${my}px) translate(-50%, -50%)`;
        dotRef.current.classList.toggle("hover", !!interactive);
      }
      if (auroraRef.current) auroraRef.current.classList.toggle("hover", !!interactive);
      if (spotRef.current) {
        const radius = interactive ? 320 : 220;
        spotRef.current.style.background = `radial-gradient(circle ${radius}px at ${mx}px ${my}px, transparent 0, color-mix(in oklab, var(--bg) 70%, transparent) 60%, color-mix(in oklab, var(--bg) 92%, transparent) 100%)`;
      }
    }

    function loop() {
      // Aurora trails with easing
      lx += (mx - lx) * 0.14;
      ly += (my - ly) * 0.14;
      if (auroraRef.current) {
        auroraRef.current.style.transform = `translate(${lx}px, ${ly}px) translate(-50%, -50%)`;
      }
      // Comet — append to trail and draw
      if (style === "comet" && cometRef.current) {
        trail.push({ x: mx, y: my, t: performance.now() });
        if (trail.length > 22) trail.shift();
        const c = cometRef.current;
        const ctx = c.getContext("2d");
        const dpr = Math.min(window.devicePixelRatio || 1, 2);
        if (c.width !== window.innerWidth * dpr || c.height !== window.innerHeight * dpr) {
          c.width = window.innerWidth * dpr;
          c.height = window.innerHeight * dpr;
          c.style.width = window.innerWidth + "px";
          c.style.height = window.innerHeight + "px";
          ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
        }
        ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
        const accent = getComputedStyle(document.documentElement).getPropertyValue("--accent").trim() || "#c97a4a";
        for (let i = 1; i < trail.length; i++) {
          const a = trail[i - 1], b = trail[i];
          const alpha = i / trail.length;
          ctx.beginPath();
          ctx.moveTo(a.x, a.y);
          ctx.lineTo(b.x, b.y);
          ctx.lineWidth = 1 + alpha * 6;
          ctx.strokeStyle = hexA(accent, alpha * 0.85);
          ctx.lineCap = "round";
          ctx.stroke();
        }
        // bright head
        ctx.beginPath();
        ctx.arc(mx, my, 5, 0, Math.PI * 2);
        ctx.fillStyle = accent;
        ctx.fill();
      }
      raf = requestAnimationFrame(loop);
    }

    window.addEventListener("mousemove", move);
    raf = requestAnimationFrame(loop);
    return () => {
      window.removeEventListener("mousemove", move);
      cancelAnimationFrame(raf);
    };
  }, [style]);

  if (style === "off") return null;

  if (style === "aurora") {
    return React.createElement(React.Fragment, null,
      React.createElement("div", { ref: auroraRef, className: "cursor-aurora" }),
      React.createElement("div", { ref: dotRef, className: "cursor-dot" })
    );
  }
  if (style === "spotlight") {
    return React.createElement(React.Fragment, null,
      React.createElement("div", { ref: spotRef, className: "cursor-spot" }),
      React.createElement("div", { ref: dotRef, className: "cursor-dot" })
    );
  }
  if (style === "comet") {
    return React.createElement(React.Fragment, null,
      React.createElement("canvas", { ref: cometRef, className: "cursor-comet" })
    );
  }
  return null;
};

function hexA(hex, a) {
  hex = (hex || "#c97a4a").trim();
  if (hex.startsWith("#")) {
    let h = hex.slice(1);
    if (h.length === 3) h = h.split("").map(c => c + c).join("");
    const r = parseInt(h.slice(0,2), 16), g = parseInt(h.slice(2,4), 16), b = parseInt(h.slice(4,6), 16);
    return `rgba(${r},${g},${b},${a})`;
  }
  // resolve via canvas
  try {
    const t = document.createElement("canvas"); t.width = t.height = 1;
    const c = t.getContext("2d"); c.fillStyle = hex; c.fillRect(0,0,1,1);
    const [r,g,b] = c.getImageData(0,0,1,1).data;
    return `rgba(${r},${g},${b},${a})`;
  } catch { return `rgba(201,122,74,${a})`; }
}

// ============================================================
// SectionBg — ambient palette-aware moving background.
// `variant`: drift | grid | waves | rays
// ============================================================
window.SectionBg = function SectionBg({ variant = "drift" }) {
  const ref = useRef(null);
  useEffect(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    let w = 0, h = 0, raf = 0, t0 = performance.now();
    let visible = true;

    function resize() {
      const r = canvas.getBoundingClientRect();
      w = r.width; h = r.height;
      canvas.width = Math.floor(w * dpr); canvas.height = Math.floor(h * dpr);
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    }
    resize();
    const ro = new ResizeObserver(resize); ro.observe(canvas);
    const io = new IntersectionObserver((es) => { es.forEach(e => visible = e.isIntersecting); }, { threshold: 0.01 });
    io.observe(canvas);

    function read() {
      const cs = getComputedStyle(document.documentElement);
      return {
        accent: cs.getPropertyValue("--accent").trim() || "#c97a4a",
        ink: cs.getPropertyValue("--ink").trim() || "#fff",
      };
    }

    // Particle field for drift
    const N = 40;
    const parts = Array.from({ length: N }, () => ({
      x: Math.random(), y: Math.random(),
      vx: (Math.random() - 0.5) * 0.00010,
      vy: (Math.random() - 0.5) * 0.00010 - 0.00006,
      r: 0.5 + Math.random() * 1.4,
      a: 0.15 + Math.random() * 0.5,
    }));

    function frame(now) {
      if (!visible) { raf = requestAnimationFrame(frame); return; }
      const e = (now - t0) / 1000;
      const colors = read();
      ctx.clearRect(0, 0, w, h);

      if (variant === "drift") {
        // Two slow blobs
        ctx.globalCompositeOperation = "lighter";
        const positions = [
          { x: 0.18 + Math.sin(e * 0.06) * 0.08, y: 0.5 + Math.cos(e * 0.05) * 0.12, r: 0.55 },
          { x: 0.78 + Math.cos(e * 0.07) * 0.06, y: 0.45 + Math.sin(e * 0.06) * 0.10, r: 0.45 },
        ];
        positions.forEach(p => {
          const cx = p.x * w, cy = p.y * h, rr = p.r * Math.max(w, h);
          const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, rr);
          g.addColorStop(0, hexA(colors.accent, 0.10));
          g.addColorStop(0.5, hexA(colors.accent, 0.025));
          g.addColorStop(1, hexA(colors.accent, 0));
          ctx.fillStyle = g;
          ctx.fillRect(0, 0, w, h);
        });
        ctx.globalCompositeOperation = "source-over";
        // Particles
        for (const p of parts) {
          p.x += p.vx * 16; p.y += p.vy * 16;
          if (p.x < -0.05) p.x = 1.05; if (p.x > 1.05) p.x = -0.05;
          if (p.y < -0.05) p.y = 1.05; if (p.y > 1.05) p.y = -0.05;
          ctx.globalAlpha = p.a * 0.6;
          ctx.fillStyle = colors.accent;
          ctx.beginPath(); ctx.arc(p.x * w, p.y * h, p.r, 0, Math.PI * 2); ctx.fill();
        }
        ctx.globalAlpha = 1;
      }

      if (variant === "grid") {
        // Animated soft grid lines
        const step = 80;
        const offset = (e * 12) % step;
        ctx.strokeStyle = hexA(colors.ink, 0.04);
        ctx.lineWidth = 1;
        for (let x = -step + offset; x < w + step; x += step) {
          ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.stroke();
        }
        for (let y = -step + offset; y < h + step; y += step) {
          ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y); ctx.stroke();
        }
        // Spotlight on grid
        const cx = (0.3 + Math.sin(e * 0.1) * 0.15) * w;
        const cy = (0.5 + Math.cos(e * 0.08) * 0.2) * h;
        const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, Math.max(w, h) * 0.5);
        g.addColorStop(0, hexA(colors.accent, 0.12));
        g.addColorStop(0.4, hexA(colors.accent, 0.03));
        g.addColorStop(1, hexA(colors.accent, 0));
        ctx.fillStyle = g; ctx.fillRect(0, 0, w, h);
      }

      if (variant === "waves") {
        // Stacked sine waves — a few visible lines, soft glow
        const layers = 4;
        for (let i = 0; i < layers; i++) {
          const yBase = h * (0.28 + i * 0.16);
          const amp = 32 + i * 14;
          const freq = 0.0032 + i * 0.0007;
          const speed = e * (0.4 + i * 0.12);
          ctx.beginPath();
          for (let x = 0; x <= w; x += 5) {
            const y = yBase + Math.sin(x * freq + speed) * amp;
            if (x === 0) ctx.moveTo(x, y);
            else ctx.lineTo(x, y);
          }
          ctx.strokeStyle = hexA(colors.accent, 0.18 + i * 0.02);
          ctx.lineWidth = 1.1;
          ctx.shadowColor = hexA(colors.accent, 0.25);
          ctx.shadowBlur = 4;
          ctx.stroke();
        }
        ctx.shadowBlur = 0;
      }

      if (variant === "rays") {
        // Soft conic rays from a moving origin — gentle glow, fewer beams
        const cx = (0.5 + Math.sin(e * 0.08) * 0.12) * w;
        const cy = h * 1.05;
        // Core glow at origin
        const coreR = Math.max(w, h) * 0.55;
        const core = ctx.createRadialGradient(cx, cy, 0, cx, cy, coreR);
        core.addColorStop(0, hexA(colors.accent, 0.22));
        core.addColorStop(0.35, hexA(colors.accent, 0.07));
        core.addColorStop(1, hexA(colors.accent, 0));
        ctx.fillStyle = core;
        ctx.fillRect(0, 0, w, h);
        // Sweeping beams
        const rays = 10;
        for (let i = 0; i < rays; i++) {
          const a = -Math.PI + (i / rays) * Math.PI + e * 0.04;
          const len = Math.max(w, h) * 1.4;
          const x2 = cx + Math.cos(a) * len;
          const y2 = cy + Math.sin(a) * len;
          const grad = ctx.createLinearGradient(cx, cy, x2, y2);
          const pulse = 0.07 + 0.04 * Math.sin(e * 0.5 + i * 0.7);
          grad.addColorStop(0, hexA(colors.accent, pulse));
          grad.addColorStop(0.6, hexA(colors.accent, pulse * 0.3));
          grad.addColorStop(1, hexA(colors.accent, 0));
          ctx.strokeStyle = grad;
          ctx.lineWidth = 70;
          ctx.beginPath(); ctx.moveTo(cx, cy); ctx.lineTo(x2, y2); ctx.stroke();
        }
      }

      raf = requestAnimationFrame(frame);
    }
    raf = requestAnimationFrame(frame);
    return () => { cancelAnimationFrame(raf); ro.disconnect(); io.disconnect(); };
  }, [variant]);

  return React.createElement("canvas", { ref, className: "section-bg" });
};

// ============================================================
// useScrollY — current window.scrollY as state, ticked on rAF
// ============================================================
window.useScrollY = function useScrollY() {
  const [y, setY] = useState(0);
  useEffect(() => {
    let raf = 0, ticking = false;
    function onScroll() {
      if (ticking) return;
      ticking = true;
      raf = requestAnimationFrame(() => {
        setY(window.scrollY);
        ticking = false;
      });
    }
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => { window.removeEventListener("scroll", onScroll); cancelAnimationFrame(raf); };
  }, []);
  return y;
};

Object.assign(window, { Cursor: window.Cursor, SectionBg: window.SectionBg, useScrollY: window.useScrollY });

// ============================================================
// ScrollWorld — fixed "signal archipelago" that shifts with page scroll.
// It gives the site a sense of traveling between work islands without
// stealing attention from the portfolio content.
// ============================================================
window.ScrollWorld = function ScrollWorld() {
  const ref = useRef(null);

  useEffect(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    const reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;

    let w = 0, h = 0, raf = 0, scrollY = window.scrollY || 0, ticking = false;
    const islands = [
      { x: 0.72, y: 0.12, r: 150, label: "START", seed: 0.2 },
      { x: 0.24, y: 0.32, r: 118, label: "BUILD", seed: 1.1 },
      { x: 0.78, y: 0.52, r: 132, label: "SHIP", seed: 2.4 },
      { x: 0.30, y: 0.72, r: 108, label: "PROFILE", seed: 3.7 },
      { x: 0.68, y: 0.90, r: 126, label: "CONTACT", seed: 4.6 },
    ];

    function resize() {
      w = window.innerWidth;
      h = window.innerHeight;
      canvas.width = Math.floor(w * dpr);
      canvas.height = Math.floor(h * dpr);
      canvas.style.width = w + "px";
      canvas.style.height = h + "px";
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    }

    function colors() {
      const cs = getComputedStyle(document.documentElement);
      return {
        accent: cs.getPropertyValue("--accent").trim() || "#c97a4a",
        ink: cs.getPropertyValue("--ink").trim() || "#f4ece2",
        bg: cs.getPropertyValue("--bg").trim() || "#0e0c0a",
        line: cs.getPropertyValue("--line").trim() || "rgba(255,255,255,.12)",
      };
    }

    function onScroll() {
      scrollY = window.scrollY || 0;
      if (ticking) return;
      ticking = true;
      requestAnimationFrame(() => {
        draw(performance.now());
        ticking = false;
      });
    }

    function draw(now) {
      const c = colors();
      const maxScroll = Math.max(1, document.documentElement.scrollHeight - h);
      const p = scrollY / maxScroll;
      const drift = reduce ? 0 : now * 0.00012;
      ctx.clearRect(0, 0, w, h);

      const mist = ctx.createRadialGradient(w * 0.5, h * 0.52, 0, w * 0.5, h * 0.52, Math.max(w, h) * 0.75);
      mist.addColorStop(0, hexA(c.accent, 0.08));
      mist.addColorStop(0.45, hexA(c.accent, 0.025));
      mist.addColorStop(1, hexA(c.accent, 0));
      ctx.fillStyle = mist;
      ctx.fillRect(0, 0, w, h);

      const positions = islands.map((island, i) => {
        const travel = (island.y - p) * h * 2.35 + h * 0.44;
        const sway = Math.sin(drift + island.seed) * 26;
        return {
          ...island,
          sx: island.x * w + sway,
          sy: travel,
          sr: Math.min(island.r, Math.max(82, w * 0.11)),
          active: Math.max(0, 1 - Math.abs(travel - h * 0.5) / (h * 0.55)),
        };
      });

      ctx.lineWidth = 1;
      for (let i = 0; i < positions.length - 1; i++) {
        const a = positions[i], b = positions[i + 1];
        const grad = ctx.createLinearGradient(a.sx, a.sy, b.sx, b.sy);
        grad.addColorStop(0, hexA(c.accent, 0.02 + a.active * 0.18));
        grad.addColorStop(0.5, hexA(c.ink, 0.035));
        grad.addColorStop(1, hexA(c.accent, 0.02 + b.active * 0.18));
        ctx.strokeStyle = grad;
        ctx.setLineDash([6, 16]);
        ctx.beginPath();
        ctx.moveTo(a.sx, a.sy);
        ctx.bezierCurveTo(a.sx, (a.sy + b.sy) * 0.5, b.sx, (a.sy + b.sy) * 0.5, b.sx, b.sy);
        ctx.stroke();
      }
      ctx.setLineDash([]);

      positions.forEach((island) => drawIsland(ctx, island, c, drift));
      if (!reduce) raf = requestAnimationFrame(draw);
    }

    resize();
    window.addEventListener("resize", resize);
    window.addEventListener("scroll", onScroll, { passive: true });
    draw(performance.now());
    if (!reduce) raf = requestAnimationFrame(draw);

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("resize", resize);
      window.removeEventListener("scroll", onScroll);
    };
  }, []);

  return React.createElement("canvas", { ref, className: "scroll-world", "aria-hidden": "true" });
};

function drawIsland(ctx, island, c, drift) {
  const { sx, sy, sr, active, seed, label } = island;
  if (sy < -sr * 2 || sy > window.innerHeight + sr * 2) return;

  const glow = ctx.createRadialGradient(sx, sy, 0, sx, sy, sr * 2.2);
  glow.addColorStop(0, hexA(c.accent, 0.04 + active * 0.12));
  glow.addColorStop(0.55, hexA(c.accent, 0.015 + active * 0.04));
  glow.addColorStop(1, hexA(c.accent, 0));
  ctx.fillStyle = glow;
  ctx.fillRect(sx - sr * 2.2, sy - sr * 2.2, sr * 4.4, sr * 4.4);

  for (let ring = 0; ring < 3; ring++) {
    const radius = sr * (1 - ring * 0.22);
    ctx.beginPath();
    for (let a = 0; a <= Math.PI * 2 + 0.08; a += 0.16) {
      const wobble = 1 + Math.sin(a * 3 + seed + drift * 2) * 0.07 + Math.cos(a * 5 - seed) * 0.035;
      const x = sx + Math.cos(a) * radius * wobble * (1.16 - ring * 0.07);
      const y = sy + Math.sin(a) * radius * wobble * (0.58 + ring * 0.03);
      if (a === 0) ctx.moveTo(x, y);
      else ctx.lineTo(x, y);
    }
    ctx.closePath();
    ctx.strokeStyle = ring === 0 ? hexA(c.accent, 0.18 + active * 0.28) : hexA(c.ink, 0.035 + active * 0.06);
    ctx.lineWidth = ring === 0 ? 1.4 : 1;
    ctx.stroke();
  }

  ctx.fillStyle = hexA(c.accent, 0.20 + active * 0.36);
  ctx.beginPath();
  ctx.arc(sx, sy, 3 + active * 3, 0, Math.PI * 2);
  ctx.fill();

  ctx.font = "10px monospace";
  ctx.textAlign = "center";
  ctx.letterSpacing = "2px";
  ctx.fillStyle = hexA(c.ink, 0.18 + active * 0.38);
  ctx.fillText(label, sx, sy + sr * 0.72);
}

Object.assign(window, { ScrollWorld: window.ScrollWorld });
