// Premium AI particle orb — production-grade.
//
// Layers (back to front):
//   1. Outer atmospheric bloom (huge soft purple)
//   2. Connection lines between near-neighbour particles (only on front hemisphere)
//   3. Back-hemisphere particles (dim, blurred, smaller)
//   4. Inner core glow
//   5. Front-hemisphere particles (sharp, bright, twinkling)
//   6. Drifting "energy" sparks — handful of bright travelling points
//   7. Lens flare highlight (top-right specular)
//
// All on canvas 2D with depth sort, ~60fps.

const { useRef, useEffect } = React;

function ParticleOrb({ count = 2200, radius = 240, mouseRepel = true }) {
  const canvasRef = useRef(null);
  const wrapRef = useRef(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    const wrap = wrapRef.current;
    if (!canvas || !wrap) return;

    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    let W = 0, H = 0;

    const resize = () => {
      const r = wrap.getBoundingClientRect();
      W = r.width; H = r.height;
      canvas.width = W * dpr;
      canvas.height = H * dpr;
      canvas.style.width = W + 'px';
      canvas.style.height = H + 'px';
    };
    resize();
    window.addEventListener('resize', resize);

    // ── Particles on Fibonacci sphere ──
    const particles = [];
    const golden = Math.PI * (3 - Math.sqrt(5));
    for (let i = 0; i < count; i++) {
      const y = 1 - (i / (count - 1)) * 2;
      const r = Math.sqrt(1 - y * y);
      const theta = golden * i;
      const x = Math.cos(theta) * r;
      const z = Math.sin(theta) * r;
      const jitter = 0.93 + Math.random() * 0.14;
      particles.push({
        bx: x * jitter, by: y * jitter, bz: z * jitter,
        phase: Math.random() * Math.PI * 2,
        speed: 0.35 + Math.random() * 0.7,
        twinkle: Math.random(),
        sizeBias: 0.7 + Math.random() * 0.7,
        // Each particle keeps its index for stable neighbour lookup
        idx: i,
      });
    }

    // ── Energy sparks: bright travelling points ──
    const sparks = [];
    for (let i = 0; i < 14; i++) {
      sparks.push({
        // Random unit vector
        ...randomSparkSeed(),
        speed: 0.6 + Math.random() * 0.8,
        life: Math.random(),
        hue: Math.random() < 0.5 ? 'violet' : 'cyan',
      });
    }
    function randomSparkSeed() {
      // Pick two random points, will arc between them via great-circle interp
      const a = randUnitVec();
      const b = randUnitVec();
      return { ax: a[0], ay: a[1], az: a[2], bx_: b[0], by_: b[1], bz_: b[2] };
    }
    function randUnitVec() {
      const u = Math.random() * 2 - 1;
      const t = Math.random() * Math.PI * 2;
      const s = Math.sqrt(1 - u*u);
      return [Math.cos(t)*s, u, Math.sin(t)*s];
    }
    function slerp(a, b, t) {
      const dot = Math.max(-1, Math.min(1, a[0]*b[0] + a[1]*b[1] + a[2]*b[2]));
      const omega = Math.acos(dot);
      if (omega < 0.001) return a;
      const so = Math.sin(omega);
      const wa = Math.sin((1-t)*omega)/so;
      const wb = Math.sin(t*omega)/so;
      return [a[0]*wa + b[0]*wb, a[1]*wa + b[1]*wb, a[2]*wa + b[2]*wb];
    }

    const state = { rotY: 0, rotX: -0.20, mouse: { x: 0, y: 0, active: false } };

    const onMove = (e) => {
      const r = wrap.getBoundingClientRect();
      state.mouse.x = (e.clientX - r.left - W/2);
      state.mouse.y = (e.clientY - r.top - H/2);
      state.mouse.active = true;
    };
    const onLeave = () => { state.mouse.active = false; };
    wrap.addEventListener('mousemove', onMove);
    wrap.addEventListener('mouseleave', onLeave);

    const ctx = canvas.getContext('2d');
    let raf;
    let last = performance.now();

    // ── Find sparse "constellation" connections: pre-computed neighbour pairs.
    // For each particle, pick 1-2 random nearby particles (in 3D) once at startup.
    // We'll only draw the line if both endpoints are on the front hemisphere.
    const connections = [];
    {
      // Use a coarse spatial bucket on (x,y,z) for quick neighbour search.
      const buckets = new Map();
      const cellSize = 0.18;
      const key = (x,y,z) => `${Math.floor(x/cellSize)},${Math.floor(y/cellSize)},${Math.floor(z/cellSize)}`;
      for (const p of particles) {
        const k = key(p.bx, p.by, p.bz);
        if (!buckets.has(k)) buckets.set(k, []);
        buckets.get(k).push(p);
      }
      const seen = new Set();
      for (const p of particles) {
        // Only ~25% of particles get connections, to keep it sparse
        if (Math.random() > 0.25) continue;
        // Look in own cell + neighbours
        const cx = Math.floor(p.bx/cellSize), cy = Math.floor(p.by/cellSize), cz = Math.floor(p.bz/cellSize);
        const candidates = [];
        for (let dx=-1; dx<=1; dx++) for (let dy=-1; dy<=1; dy++) for (let dz=-1; dz<=1; dz++) {
          const arr = buckets.get(`${cx+dx},${cy+dy},${cz+dz}`);
          if (arr) candidates.push(...arr);
        }
        // Sort by distance, take 1-2 nearest (excluding self)
        candidates.sort((a,b) => {
          const da = (a.bx-p.bx)**2 + (a.by-p.by)**2 + (a.bz-p.bz)**2;
          const db = (b.bx-p.bx)**2 + (b.by-p.by)**2 + (b.bz-p.bz)**2;
          return da - db;
        });
        const wanted = 1 + (Math.random() < 0.4 ? 1 : 0);
        let added = 0;
        for (const q of candidates) {
          if (q === p) continue;
          if (added >= wanted) break;
          const k = p.idx < q.idx ? `${p.idx}-${q.idx}` : `${q.idx}-${p.idx}`;
          if (seen.has(k)) continue;
          seen.add(k);
          connections.push([p, q]);
          added++;
        }
      }
    }

    const tick = (now) => {
      const dt = Math.min((now - last) / 1000, 0.05);
      last = now;
      state.rotY += dt * 0.10;

      const cx = W/2, cy = H/2;
      // Slight perspective so front particles get bigger
      const persp = 800;

      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.save();
      ctx.scale(dpr, dpr);

      // ── 1. Outer atmospheric bloom ──
      const aura = ctx.createRadialGradient(cx, cy + 20, 0, cx, cy + 20, radius * 2.2);
      aura.addColorStop(0, 'rgba(120, 90, 220, 0.10)');
      aura.addColorStop(0.4, 'rgba(80, 60, 180, 0.05)');
      aura.addColorStop(1, 'rgba(0,0,0,0)');
      ctx.fillStyle = aura;
      ctx.fillRect(0, 0, W, H);

      const sinY = Math.sin(state.rotY), cosY = Math.cos(state.rotY);
      const sinX = Math.sin(state.rotX), cosX = Math.cos(state.rotX);

      // Project all particles
      const projected = new Array(particles.length);
      for (let i = 0; i < particles.length; i++) {
        const p = particles[i];
        p.phase += dt * p.speed * 0.5;
        const drift = 0.022;
        const ox = Math.sin(p.phase) * drift;
        const oy = Math.cos(p.phase * 0.7) * drift;
        const oz = Math.sin(p.phase * 1.3) * drift;

        const x0 = (p.bx + ox) * radius;
        const y0 = (p.by + oy) * radius;
        const z0 = (p.bz + oz) * radius;

        // rot Y
        const x1 = x0 * cosY + z0 * sinY;
        const z1 = -x0 * sinY + z0 * cosY;
        // rot X
        const y2 = y0 * cosX - z1 * sinX;
        const z2 = y0 * sinX + z1 * cosX;

        // Perspective scale
        const ps = persp / (persp - z2);

        // Mouse repulsion in screen space
        let sx = cx + x1 * ps;
        let sy = cy + y2 * ps;
        if (mouseRepel && state.mouse.active) {
          const dx = sx - (cx + state.mouse.x);
          const dy = sy - (cy + state.mouse.y);
          const d2 = dx*dx + dy*dy;
          const R = 110;
          if (d2 < R*R) {
            const d = Math.sqrt(d2) || 0.0001;
            const force = (1 - d/R) * 35;
            sx += (dx/d) * force;
            sy += (dy/d) * force;
          }
        }

        projected[i] = { p, sx, sy, z: z2, ps };
      }

      // ── 2. Connection lines (front hemisphere only) ──
      ctx.lineWidth = 0.5;
      for (let i = 0; i < connections.length; i++) {
        const [a, b] = connections[i];
        const pa = projected[a.idx], pb = projected[b.idx];
        // Only draw if both are on the front hemisphere
        if (pa.z < 0 || pb.z < 0) continue;
        const avgZ = (pa.z + pb.z) / 2;
        const front = (avgZ / radius + 1) / 2;
        // Line opacity falls off with depth, and pulses subtly
        const pulse = 0.7 + Math.sin((a.phase + b.phase) * 0.5) * 0.3;
        const alpha = front * 0.18 * pulse;
        if (alpha < 0.01) continue;
        ctx.strokeStyle = `rgba(180,170,230,${alpha.toFixed(3)})`;
        ctx.beginPath();
        ctx.moveTo(pa.sx, pa.sy);
        ctx.lineTo(pb.sx, pb.sy);
        ctx.stroke();
      }

      // Sort projected by z
      projected.sort((a, b) => a.z - b.z);

      // ── 3+5. Particles, two-pass: back hemisphere blurred, front sharp ──
      // Back pass first (z < 0)
      for (let i = 0; i < projected.length; i++) {
        const pp = projected[i];
        if (pp.z >= 0) continue;
        const depth = pp.z / radius;
        const front = (depth + 1) / 2; // 0 at far back, 0.5 at equator
        const sb = pp.p.sizeBias;
        const size = (0.4 + front * 0.5) * sb;
        // Back: tinted purple, dim
        const r = Math.round(120 + front * 60);
        const g = Math.round(110 + front * 70);
        const b = Math.round(180 + front * 50);
        const alpha = 0.15 + front * 0.35;
        // Slight glow halo
        ctx.beginPath();
        ctx.arc(pp.sx, pp.sy, size * 1.6, 0, Math.PI * 2);
        ctx.fillStyle = `rgba(${r},${g},${b},${(alpha*0.25).toFixed(3)})`;
        ctx.fill();
        ctx.beginPath();
        ctx.arc(pp.sx, pp.sy, size, 0, Math.PI * 2);
        ctx.fillStyle = `rgba(${r},${g},${b},${alpha.toFixed(3)})`;
        ctx.fill();
      }

      // ── 4. Inner core glow ──
      const core = ctx.createRadialGradient(cx, cy, 0, cx, cy, radius * 0.55);
      core.addColorStop(0, 'rgba(220, 200, 255, 0.14)');
      core.addColorStop(0.5, 'rgba(140, 110, 220, 0.06)');
      core.addColorStop(1, 'rgba(0,0,0,0)');
      ctx.fillStyle = core;
      ctx.beginPath();
      ctx.arc(cx, cy, radius * 0.55, 0, Math.PI * 2);
      ctx.fill();

      // Front pass (z >= 0), with twinkle
      for (let i = 0; i < projected.length; i++) {
        const pp = projected[i];
        if (pp.z < 0) continue;
        const depth = pp.z / radius;
        const front = (depth + 1) / 2; // 0.5 to 1
        const sb = pp.p.sizeBias;
        const size = (0.5 + front * 1.1) * sb * pp.ps;
        // Front: bright, near-white with slight cool tint
        const r = Math.round(200 + front * 55);
        const g = Math.round(195 + front * 60);
        const b = Math.round(230 + front * 25);
        const twinkle = 0.7 + Math.sin(pp.p.phase * 2 + pp.p.twinkle * 6) * 0.3;
        const alpha = (0.35 + front * 0.6) * twinkle;
        // Soft halo
        ctx.beginPath();
        ctx.arc(pp.sx, pp.sy, size * 2.2, 0, Math.PI * 2);
        ctx.fillStyle = `rgba(${r},${g},${b},${(alpha*0.18).toFixed(3)})`;
        ctx.fill();
        // Core dot
        ctx.beginPath();
        ctx.arc(pp.sx, pp.sy, size, 0, Math.PI * 2);
        ctx.fillStyle = `rgba(${r},${g},${b},${alpha.toFixed(3)})`;
        ctx.fill();
      }

      // ── 6. Energy sparks travelling on great circles ──
      for (let i = 0; i < sparks.length; i++) {
        const s = sparks[i];
        s.life += dt * 0.15 * s.speed;
        if (s.life >= 1) {
          // Reset to a new arc
          const seed = randomSparkSeed();
          s.ax = seed.ax; s.ay = seed.ay; s.az = seed.az;
          s.bx_ = seed.bx_; s.by_ = seed.by_; s.bz_ = seed.bz_;
          s.life = 0;
        }
        const v = slerp([s.ax, s.ay, s.az], [s.bx_, s.by_, s.bz_], s.life);
        const x0 = v[0] * radius * 1.02;
        const y0 = v[1] * radius * 1.02;
        const z0 = v[2] * radius * 1.02;
        const x1 = x0 * cosY + z0 * sinY;
        const z1 = -x0 * sinY + z0 * cosY;
        const y2 = y0 * cosX - z1 * sinX;
        const z2 = y0 * sinX + z1 * cosX;
        if (z2 < -radius * 0.1) continue;
        const ps = persp / (persp - z2);
        const sx = cx + x1 * ps;
        const sy = cy + y2 * ps;
        const front = (z2/radius + 1) / 2;
        // Fade in/out at start/end
        const fade = Math.sin(s.life * Math.PI);
        const tint = s.hue === 'violet' ? [200, 160, 255] : [170, 220, 255];
        const a = fade * front * 0.95;
        // Big bloom
        ctx.beginPath();
        ctx.arc(sx, sy, 9, 0, Math.PI * 2);
        ctx.fillStyle = `rgba(${tint[0]},${tint[1]},${tint[2]},${(a*0.18).toFixed(3)})`;
        ctx.fill();
        ctx.beginPath();
        ctx.arc(sx, sy, 4, 0, Math.PI * 2);
        ctx.fillStyle = `rgba(${tint[0]},${tint[1]},${tint[2]},${(a*0.5).toFixed(3)})`;
        ctx.fill();
        ctx.beginPath();
        ctx.arc(sx, sy, 1.6, 0, Math.PI * 2);
        ctx.fillStyle = `rgba(255,255,255,${a.toFixed(3)})`;
        ctx.fill();
      }

      // ── 7. Top-right specular highlight (lens flare) ──
      const flareX = cx + radius * 0.5;
      const flareY = cy - radius * 0.55;
      const flare = ctx.createRadialGradient(flareX, flareY, 0, flareX, flareY, radius * 0.6);
      flare.addColorStop(0, 'rgba(255,255,255,0.10)');
      flare.addColorStop(0.4, 'rgba(200,180,255,0.04)');
      flare.addColorStop(1, 'rgba(0,0,0,0)');
      ctx.fillStyle = flare;
      ctx.beginPath();
      ctx.arc(flareX, flareY, radius * 0.6, 0, Math.PI * 2);
      ctx.fill();

      ctx.restore();
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('resize', resize);
      wrap.removeEventListener('mousemove', onMove);
      wrap.removeEventListener('mouseleave', onLeave);
    };
  }, [count, radius, mouseRepel]);

  return (
    <div
      ref={wrapRef}
      style={{
        width: '100%',
        height: '100%',
        position: 'relative',
        cursor: 'crosshair',
      }}
    >
      <canvas ref={canvasRef} style={{ display: 'block', width: '100%', height: '100%' }} />
    </div>
  );
}

window.ParticleOrb = ParticleOrb;
