// Hero scene: three.js wireframe globe + neural network + particles
// Loaded via <script type="text/babel"> AFTER three.js UMD.

(function initHero() {
  const canvas = document.getElementById('hero-canvas');
  if (!canvas || typeof THREE === 'undefined') return;

  const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  const isMobile = window.innerWidth < 768;

  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(45, canvas.clientWidth / canvas.clientHeight, 0.1, 2000);
  camera.position.set(0, 0, 38);

  const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  renderer.setSize(canvas.clientWidth, canvas.clientHeight, false);
  renderer.setClearColor(0x000000, 0);

  const COLOR_CYAN = new THREE.Color(0x00F0FF);
  const COLOR_MAGENTA = new THREE.Color(0xFF2E9A);
  const COLOR_VIOLET = new THREE.Color(0x8B5CF6);

  // ================================
  // Group to hold everything for parallax / scroll effects
  // ================================
  const sceneRoot = new THREE.Group();
  scene.add(sceneRoot);

  // ================================
  // Wireframe globe
  // ================================
  const globeGroup = new THREE.Group();
  // Push globe right + slightly up
  globeGroup.position.set(7.5, 0.5, 0);
  sceneRoot.add(globeGroup);

  const globeRadius = 10;
  const globeGeom = new THREE.SphereGeometry(globeRadius, 48, 36);
  const wireMat = new THREE.LineBasicMaterial({ color: COLOR_CYAN, transparent: true, opacity: 0.18 });
  const wireframe = new THREE.LineSegments(new THREE.WireframeGeometry(globeGeom), wireMat);
  globeGroup.add(wireframe);

  // Inner translucent glow sphere
  const glowGeom = new THREE.SphereGeometry(globeRadius * 0.98, 32, 24);
  const glowMat = new THREE.MeshBasicMaterial({
    color: COLOR_CYAN,
    transparent: true, opacity: 0.04, side: THREE.BackSide,
  });
  globeGroup.add(new THREE.Mesh(glowGeom, glowMat));

  // Outer atmosphere ring
  const ringGeom = new THREE.RingGeometry(globeRadius * 1.18, globeRadius * 1.2, 128);
  const ringMat = new THREE.MeshBasicMaterial({ color: COLOR_CYAN, transparent: true, opacity: 0.35, side: THREE.DoubleSide });
  const ring = new THREE.Mesh(ringGeom, ringMat);
  ring.rotation.x = Math.PI / 2.2;
  globeGroup.add(ring);

  // Equator latitude circle (stronger line)
  const latSegments = 128;
  const latPts = [];
  for (let i = 0; i <= latSegments; i++) {
    const a = (i / latSegments) * Math.PI * 2;
    latPts.push(new THREE.Vector3(Math.cos(a) * globeRadius, 0, Math.sin(a) * globeRadius));
  }
  const equator = new THREE.Line(
    new THREE.BufferGeometry().setFromPoints(latPts),
    new THREE.LineBasicMaterial({ color: COLOR_CYAN, transparent: true, opacity: 0.5 })
  );
  globeGroup.add(equator);

  // ================================
  // City nodes on globe surface
  // ================================
  const cities = [
    { name: 'Tokyo',     lat:  35.68, lon: 139.76 },
    { name: 'New York',  lat:  40.71, lon: -74.00 },
    { name: 'London',    lat:  51.50, lon:  -0.12 },
    { name: 'San Francisco', lat: 37.77, lon: -122.41 },
    { name: 'Singapore', lat:   1.35, lon: 103.81 },
    { name: 'Seoul',     lat:  37.56, lon: 126.97 },
    { name: 'Mumbai',    lat:  19.07, lon:  72.87 },
    { name: 'Sydney',    lat: -33.86, lon: 151.20 },
    { name: 'Berlin',    lat:  52.52, lon:  13.40 },
    { name: 'Dubai',     lat:  25.20, lon:  55.27 },
  ];

  function latLonToVec3(lat, lon, r) {
    const phi = (90 - lat) * (Math.PI / 180);
    const theta = (lon + 180) * (Math.PI / 180);
    return new THREE.Vector3(
      -r * Math.sin(phi) * Math.cos(theta),
       r * Math.cos(phi),
       r * Math.sin(phi) * Math.sin(theta)
    );
  }

  const cityGroup = new THREE.Group();
  globeGroup.add(cityGroup);

  cities.forEach(c => {
    c.pos = latLonToVec3(c.lat, c.lon, globeRadius);
    const dot = new THREE.Mesh(
      new THREE.SphereGeometry(0.12, 10, 10),
      new THREE.MeshBasicMaterial({ color: COLOR_CYAN })
    );
    dot.position.copy(c.pos);
    cityGroup.add(dot);

    // outer glow halo
    const halo = new THREE.Mesh(
      new THREE.SphereGeometry(0.28, 12, 12),
      new THREE.MeshBasicMaterial({ color: COLOR_CYAN, transparent: true, opacity: 0.2 })
    );
    halo.position.copy(c.pos);
    cityGroup.add(halo);
  });

  // ================================
  // Bezier arcs between cities + animated pulses
  // ================================
  const arcs = [];
  const pulseCount = isMobile ? 6 : 14;

  function buildArc(a, b) {
    const mid = a.clone().add(b).multiplyScalar(0.5);
    const dist = a.distanceTo(b);
    const height = globeRadius + dist * 0.45;
    mid.normalize().multiplyScalar(height);
    const curve = new THREE.QuadraticBezierCurve3(a, mid, b);
    const pts = curve.getPoints(60);
    const geom = new THREE.BufferGeometry().setFromPoints(pts);
    const mat = new THREE.LineBasicMaterial({ color: COLOR_CYAN, transparent: true, opacity: 0.35 });
    const line = new THREE.Line(geom, mat);
    globeGroup.add(line);
    return { curve, line };
  }

  // Pick connections from Tokyo hub + some extras
  const tokyo = cities[0];
  cities.slice(1).forEach(c => arcs.push(buildArc(tokyo.pos, c.pos)));
  // extras
  arcs.push(buildArc(cities[1].pos, cities[2].pos)); // NY-London
  arcs.push(buildArc(cities[3].pos, cities[5].pos)); // SF-Seoul
  arcs.push(buildArc(cities[4].pos, cities[8].pos)); // SG-Berlin
  arcs.push(buildArc(cities[6].pos, cities[9].pos)); // Mumbai-Dubai

  // Pulses traveling along random arcs
  const pulses = [];
  for (let i = 0; i < pulseCount; i++) {
    const arc = arcs[Math.floor(Math.random() * arcs.length)];
    const mesh = new THREE.Mesh(
      new THREE.SphereGeometry(0.17, 10, 10),
      new THREE.MeshBasicMaterial({ color: COLOR_MAGENTA })
    );
    // Halo
    const halo = new THREE.Mesh(
      new THREE.SphereGeometry(0.42, 10, 10),
      new THREE.MeshBasicMaterial({ color: COLOR_MAGENTA, transparent: true, opacity: 0.35 })
    );
    mesh.add(halo);
    globeGroup.add(mesh);
    pulses.push({
      mesh,
      arc,
      t: Math.random(),
      speed: 0.12 + Math.random() * 0.1,
    });
  }

  // ================================
  // Neural network layers (floating around globe)
  // ================================
  const neuralGroup = new THREE.Group();
  sceneRoot.add(neuralGroup);

  // Layers are arranged to the LEFT of the globe, forming a kind of column
  const layerCount = isMobile ? 2 : 3;
  const nodesPerLayer = isMobile ? [5, 6] : [4, 6, 5];
  const layerX = -13;
  const layerSpacing = 5;
  const layers = [];

  for (let L = 0; L < layerCount; L++) {
    const n = nodesPerLayer[L] || 5;
    const layer = [];
    for (let i = 0; i < n; i++) {
      const node = new THREE.Mesh(
        new THREE.SphereGeometry(0.2, 12, 12),
        new THREE.MeshBasicMaterial({ color: COLOR_CYAN, transparent: true, opacity: 0.7 })
      );
      const y = (i - (n - 1) / 2) * 2.2;
      const x = layerX + L * layerSpacing + (Math.random() - 0.5) * 0.6;
      const z = (Math.random() - 0.5) * 3;
      node.position.set(x, y, z);
      node.userData.base = node.position.clone();
      node.userData.fireT = Math.random() * 5;
      neuralGroup.add(node);
      layer.push(node);
    }
    layers.push(layer);
  }

  // Connections between neural layers
  const neuralLineMat = new THREE.LineBasicMaterial({ color: COLOR_CYAN, transparent: true, opacity: 0.12 });
  for (let L = 0; L < layers.length - 1; L++) {
    const a = layers[L], b = layers[L + 1];
    a.forEach(na => {
      b.forEach(nb => {
        if (Math.random() > 0.35) {
          const geom = new THREE.BufferGeometry().setFromPoints([na.position, nb.position]);
          neuralGroup.add(new THREE.Line(geom, neuralLineMat));
        }
      });
    });
  }

  // Thin lines connecting last neural layer to a couple of globe cities
  const neuralToGlobeMat = new THREE.LineBasicMaterial({ color: COLOR_VIOLET, transparent: true, opacity: 0.18 });
  const lastLayer = layers[layers.length - 1];
  lastLayer.forEach((node, i) => {
    if (i % 2 === 0) {
      const city = cities[i % cities.length];
      const globePos = city.pos.clone().add(globeGroup.position);
      const geom = new THREE.BufferGeometry().setFromPoints([node.position, globePos]);
      neuralGroup.add(new THREE.Line(geom, neuralToGlobeMat));
    }
  });

  // ================================
  // Background particles
  // ================================
  if (!isMobile) {
    const pCount = 1800;
    const pGeom = new THREE.BufferGeometry();
    const positions = new Float32Array(pCount * 3);
    for (let i = 0; i < pCount; i++) {
      positions[i * 3]     = (Math.random() - 0.5) * 140;
      positions[i * 3 + 1] = (Math.random() - 0.5) * 80;
      positions[i * 3 + 2] = (Math.random() - 0.5) * 100 - 20;
    }
    pGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
    const pMat = new THREE.PointsMaterial({
      color: COLOR_CYAN,
      size: 0.06,
      transparent: true,
      opacity: 0.6,
    });
    sceneRoot.add(new THREE.Points(pGeom, pMat));
  }

  // ================================
  // Mouse parallax
  // ================================
  let targetRX = 0, targetRY = 0;
  let curRX = 0, curRY = 0;
  window.addEventListener('mousemove', (e) => {
    const nx = (e.clientX / window.innerWidth) * 2 - 1;
    const ny = (e.clientY / window.innerHeight) * 2 - 1;
    targetRY = nx * (Math.PI / 60); // ~3 degrees
    targetRX = -ny * (Math.PI / 60);
  });

  // Scroll scale/fade
  let scrollFactor = 0;
  window.addEventListener('scroll', () => {
    const h = window.innerHeight;
    scrollFactor = Math.min(1, Math.max(0, window.scrollY / h));
  }, { passive: true });

  // ================================
  // Resize
  // ================================
  function resize() {
    const w = canvas.clientWidth, h = canvas.clientHeight;
    renderer.setSize(w, h, false);
    camera.aspect = w / h;
    camera.updateProjectionMatrix();
  }
  window.addEventListener('resize', resize);

  // ================================
  // Animation loop
  // ================================
  const clock = new THREE.Clock();

  function animate() {
    const dt = clock.getDelta();
    const t  = clock.getElapsedTime();

    if (!reducedMotion) {
      // 90s rotation = 2pi / 90 rad/s
      globeGroup.rotation.y += (Math.PI * 2 / 90) * dt;
      cityGroup.rotation.y = 0; // already a child of globeGroup, no-op

      // Pulses along arcs
      pulses.forEach(p => {
        p.t += p.speed * dt;
        if (p.t > 1) {
          p.t = 0;
          p.arc = arcs[Math.floor(Math.random() * arcs.length)];
        }
        const pt = p.arc.curve.getPoint(p.t);
        p.mesh.position.copy(pt);
        const scale = 0.6 + Math.sin(p.t * Math.PI) * 0.6;
        p.mesh.scale.setScalar(scale);
      });

      // Neural node firing
      layers.flat().forEach(node => {
        node.userData.fireT -= dt;
        if (node.userData.fireT < 0) {
          node.userData.fireT = 3 + Math.random() * 4;
          node.userData.firing = 1;
        }
        if (node.userData.firing > 0) {
          node.userData.firing -= dt * 1.5;
          const f = Math.max(0, node.userData.firing);
          node.scale.setScalar(1 + f * 1.2);
          node.material.color.lerpColors(COLOR_CYAN, COLOR_MAGENTA, f * 0.6);
          node.material.opacity = 0.7 + f * 0.3;
        } else {
          node.scale.setScalar(1);
          node.material.color.copy(COLOR_CYAN);
        }
        // subtle float
        node.position.y = node.userData.base.y + Math.sin(t * 0.6 + node.userData.base.x) * 0.15;
      });

      // Equator shimmer
      equator.material.opacity = 0.35 + Math.sin(t * 2) * 0.1;
    }

    // Parallax ease
    curRX += (targetRX - curRX) * 0.05;
    curRY += (targetRY - curRY) * 0.05;
    sceneRoot.rotation.x = curRX;
    sceneRoot.rotation.y = curRY;

    // Scroll scale/fade
    const s = 1 - scrollFactor * 0.2;
    sceneRoot.scale.setScalar(s);
    renderer.domElement.style.opacity = 1 - scrollFactor * 0.9;

    renderer.render(scene, camera);
    requestAnimationFrame(animate);
  }
  animate();

  // If reduced-motion, render one frame then stop; already handled (animate still runs but no changes)
})();
