// Volumetric 3D network lobe — pure SVG/CSS, no WebGL
// Projects 3D points to 2D screen space with depth-based sizing/opacity.
// Supports orbit animation and live connection lines.

// Deterministic pseudo-random for stable layouts
function prand(i, salt = 0) {
  const x = Math.sin(i * 12.9898 + salt * 78.233) * 43758.5453;
  return x - Math.floor(x);
}

// Spherical → cartesian (radius R around origin)
function sphPoint(i, n, R, jitter = 0.15) {
  // Fibonacci sphere for even distribution
  const goldenAngle = Math.PI * (3 - Math.sqrt(5));
  const y = 1 - (i / (n - 1)) * 2; // -1..1
  const r = Math.sqrt(1 - y * y);
  const theta = goldenAngle * i;
  const jx = (prand(i, 1) - 0.5) * jitter;
  const jy = (prand(i, 2) - 0.5) * jitter;
  const jz = (prand(i, 3) - 0.5) * jitter;
  return {
    x: (Math.cos(theta) * r + jx) * R,
    y: (y + jy) * R,
    z: (Math.sin(theta) * r + jz) * R,
  };
}

// Rotate point around Y then X axes
function rotate(p, rotY, rotX) {
  const cy = Math.cos(rotY), sy = Math.sin(rotY);
  const x1 = p.x * cy + p.z * sy;
  const z1 = -p.x * sy + p.z * cy;
  const cx = Math.cos(rotX), sx = Math.sin(rotX);
  const y2 = p.y * cx - z1 * sx;
  const z2 = p.y * sx + z1 * cx;
  return { x: x1, y: y2, z: z2 };
}

// Perspective project
function project(p, cx, cy, focal = 900) {
  const z = p.z + focal;
  const s = focal / Math.max(0.1, z);
  return {
    x: cx + p.x * s,
    y: cy + p.y * s,
    scale: s,
    z: p.z, // for sort
  };
}

// Build a volumetric cloud of nodes classified into 3 tiers:
//   - institutions (large cubes) ~ 6
//   - trainers (diamonds) ~ 14
//   - enthusiasts (dots) ~ 60
function buildNodes(total = 80, radius = 280) {
  const nodes = [];
  for (let i = 0; i < total; i++) {
    const p = sphPoint(i, total, radius);
    let kind, size, color;
    const r = prand(i, 99);
    if (i < 6) {
      kind = 'institution';
      size = 14;
      color = '#CCFF00';
    } else if (i < 20) {
      kind = 'trainer';
      size = 9;
      color = '#D9FF33';
    } else {
      kind = 'enthusiast';
      size = 4 + prand(i, 7) * 2;
      color = r > 0.7 ? '#CCFF00' : '#ffffff';
    }
    nodes.push({ i, pos: p, kind, size, color });
  }
  return nodes;
}

// Pre-compute connections: each trainer connects to 1-2 institutions and 3-5 enthusiasts
function buildConnections(nodes) {
  const insts = nodes.filter(n => n.kind === 'institution');
  const trainers = nodes.filter(n => n.kind === 'trainer');
  const enths = nodes.filter(n => n.kind === 'enthusiast');
  const cons = [];
  trainers.forEach((tr, ti) => {
    // connect to 1-2 nearest institutions
    const sortedInsts = insts.map(ins => ({
      ins, d: dist3(ins.pos, tr.pos)
    })).sort((a,b) => a.d - b.d);
    cons.push({ a: tr, b: sortedInsts[0].ins, kind: 't-i' });
    if (prand(ti, 11) > 0.5) cons.push({ a: tr, b: sortedInsts[1].ins, kind: 't-i' });
    // connect to 3-5 nearest enthusiasts
    const sortedEnths = enths.map(e => ({
      e, d: dist3(e.pos, tr.pos)
    })).sort((a,b) => a.d - b.d);
    const nE = 3 + Math.floor(prand(ti, 22) * 3);
    for (let k = 0; k < nE; k++) {
      cons.push({ a: tr, b: sortedEnths[k].e, kind: 't-e' });
    }
  });
  // Institution ↔ institution hub lines (sparse)
  for (let i = 0; i < insts.length; i++) {
    for (let j = i + 1; j < insts.length; j++) {
      if (prand(i * 10 + j, 33) > 0.75) {
        cons.push({ a: insts[i], b: insts[j], kind: 'i-i' });
      }
    }
  }
  return cons;
}

function dist3(a, b) {
  const dx = a.x-b.x, dy = a.y-b.y, dz = a.z-b.z;
  return Math.sqrt(dx*dx+dy*dy+dz*dz);
}

// ──────────────────────────────────────────────────────────────────
// <NetworkLobe /> — renders a volumetric 3D network
//
// Props:
//   cx, cy         — screen center
//   radius         — sphere radius
//   rotY, rotX     — current rotation (radians)
//   t              — global time for pulsing
//   activeBeats    — array of {fromIdx, toIdx, progress 0..1, hue} data packets
//   buildUp        — 0..1 how many nodes/edges to show (0 = none, 1 = full)
// ──────────────────────────────────────────────────────────────────
function NetworkLobe({
  cx = 960, cy = 540, radius = 300,
  rotY = 0, rotX = 0, t = 0,
  buildUp = 1,
  showConnections = true,
  focalBoost = 0,
}) {
  // Build once and memoize via ref-pattern (simple: keep module-level cache)
  if (!NetworkLobe._cache) {
    const nodes = buildNodes(80, 300);
    const cons = buildConnections(nodes);
    NetworkLobe._cache = { nodes, cons };
  }
  const { nodes, cons } = NetworkLobe._cache;

  // Rotate every node
  const projected = nodes.map(n => {
    const rot = rotate(n.pos, rotY, rotX);
    // scale by radius ratio
    const scaled = { x: rot.x * radius/300, y: rot.y * radius/300, z: rot.z * radius/300 };
    const pr = project(scaled, cx, cy, 900 + focalBoost);
    return { ...n, rot: scaled, pr };
  });

  // How many nodes to show
  const visible = projected.slice(0, Math.floor(projected.length * buildUp));
  const visibleSet = new Set(visible.map(n => n.i));

  // Sort by z for painter's algorithm
  const sorted = [...visible].sort((a, b) => a.pr.z - b.pr.z);

  // Connections visible only if both endpoints visible
  const visCons = showConnections
    ? cons.filter(c => visibleSet.has(c.a.i) && visibleSet.has(c.b.i))
    : [];

  return (
    <svg
      width="100%" height="100%"
      viewBox="0 0 1920 1080"
      style={{ position: 'absolute', inset: 0, overflow: 'visible' }}
    >
      {/* Glow defs */}
      <defs>
        <radialGradient id="nodeGlow" cx="50%" cy="50%" r="50%">
          <stop offset="0%" stopColor="#CCFF00" stopOpacity="0.9"/>
          <stop offset="100%" stopColor="#CCFF00" stopOpacity="0"/>
        </radialGradient>
        <filter id="blurGlow">
          <feGaussianBlur stdDeviation="3"/>
        </filter>
      </defs>

      {/* Connections (behind nodes) */}
      <g>
        {visCons.map((c, ci) => {
          const aRot = rotate(c.a.pos, rotY, rotX);
          const bRot = rotate(c.b.pos, rotY, rotX);
          const aScaled = { x: aRot.x * radius/300, y: aRot.y * radius/300, z: aRot.z * radius/300 };
          const bScaled = { x: bRot.x * radius/300, y: bRot.y * radius/300, z: bRot.z * radius/300 };
          const aP = project(aScaled, cx, cy, 900 + focalBoost);
          const bP = project(bScaled, cx, cy, 900 + focalBoost);
          const avgZ = (aScaled.z + bScaled.z) / 2;
          const depthT = (avgZ + 300) / 600; // 0 back .. 1 front
          const opacity = 0.08 + depthT * 0.35;
          const strokeW = c.kind === 'i-i' ? 1.2 : 0.7;
          return (
            <line
              key={ci}
              x1={aP.x} y1={aP.y} x2={bP.x} y2={bP.y}
              stroke="#CCFF00"
              strokeOpacity={opacity}
              strokeWidth={strokeW}
            />
          );
        })}
      </g>

      {/* Data packets traveling along edges */}
      <g>
        {visCons.map((c, ci) => {
          // Only pulse some edges to keep signal readable
          if (prand(ci, 5) < 0.65) return null;
          const speed = 0.35 + prand(ci, 17) * 0.4;
          const phase = prand(ci, 29) * Math.PI * 2;
          const p = ((t * speed + phase / (2*Math.PI)) % 1);

          const aRot = rotate(c.a.pos, rotY, rotX);
          const bRot = rotate(c.b.pos, rotY, rotX);
          const aScaled = { x: aRot.x * radius/300, y: aRot.y * radius/300, z: aRot.z * radius/300 };
          const bScaled = { x: bRot.x * radius/300, y: bRot.y * radius/300, z: bRot.z * radius/300 };
          const mid = {
            x: aScaled.x + (bScaled.x - aScaled.x) * p,
            y: aScaled.y + (bScaled.y - aScaled.y) * p,
            z: aScaled.z + (bScaled.z - aScaled.z) * p,
          };
          const pr = project(mid, cx, cy, 900 + focalBoost);
          const depthT = (mid.z + 300) / 600;
          const size = 1.5 + depthT * 2;
          const opacity = 0.5 + depthT * 0.5;
          return (
            <circle
              key={'pkt' + ci}
              cx={pr.x} cy={pr.y} r={size}
              fill="#CCFF00"
              opacity={opacity}
              style={{ filter: 'drop-shadow(0 0 4px #CCFF00)' }}
            />
          );
        })}
      </g>

      {/* Nodes */}
      <g>
        {sorted.map((n, idx) => {
          const depthT = (n.rot.z + 300) / 600;
          const nodeScale = 0.4 + depthT * 1.0; // shrink far nodes
          const opacity = 0.25 + depthT * 0.75;
          const pulse = Math.sin(t * 2 + n.i * 0.7) * 0.5 + 0.5;
          const size = n.size * nodeScale;

          if (n.kind === 'institution') {
            // Tilted square (diamond when rotated)
            const s = size * 1.6;
            return (
              <g key={n.i} transform={`translate(${n.pr.x} ${n.pr.y}) rotate(45)`}>
                <rect
                  x={-s/2} y={-s/2} width={s} height={s}
                  fill="#CCFF00"
                  fillOpacity={opacity}
                  stroke="#CCFF00"
                  strokeWidth={1}
                  style={{ filter: `drop-shadow(0 0 ${6 + pulse*6}px #CCFF00)` }}
                />
                <rect
                  x={-s/4} y={-s/4} width={s/2} height={s/2}
                  fill="#0a0a0a"
                />
              </g>
            );
          } else if (n.kind === 'trainer') {
            const s = size * 1.3;
            return (
              <g key={n.i}>
                <circle
                  cx={n.pr.x} cy={n.pr.y} r={s}
                  fill="none"
                  stroke="#CCFF00"
                  strokeWidth={1.5}
                  strokeOpacity={opacity}
                  style={{ filter: `drop-shadow(0 0 4px #CCFF00)` }}
                />
                <circle
                  cx={n.pr.x} cy={n.pr.y} r={s * 0.4}
                  fill="#CCFF00"
                  fillOpacity={opacity}
                />
              </g>
            );
          } else {
            return (
              <circle
                key={n.i}
                cx={n.pr.x} cy={n.pr.y} r={size}
                fill={n.color}
                fillOpacity={opacity * (0.5 + pulse * 0.5)}
                style={{ filter: n.color === '#CCFF00' ? 'drop-shadow(0 0 3px #CCFF00)' : 'none' }}
              />
            );
          }
        })}
      </g>
    </svg>
  );
}

Object.assign(window, { NetworkLobe, buildNodes, buildConnections, sphPoint, rotate, project, prand });
