// Three.js — A morphing wireframe icosphere with orbiting particles.
// Brand colors only: navy (#00577B) for the main mesh, amber (#E09839) for accents.

function NeuralSphere() {
  const mountRef = useRef(null);
  const stateRef = useRef({ mouseX: 0, mouseY: 0, scrollY: 0 });

  useEffect(() => {
    const mount = mountRef.current;
    if (!mount || !window.THREE) return;

    const THREE = window.THREE;
    const w = mount.clientWidth;
    const h = mount.clientHeight;

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(45, w / h, 0.1, 100);
    camera.position.set(0, 0, 7);

    const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.setSize(w, h);
    renderer.setClearColor(0x000000, 0);
    mount.appendChild(renderer.domElement);

    // Main wireframe icosphere
    const geo = new THREE.IcosahedronGeometry(2.2, 4);
    const positions = geo.attributes.position.array.slice(); // store originals
    const wireframeMat = new THREE.LineBasicMaterial({
      color: 0x00577B,
      transparent: true,
      opacity: 0.55,
    });
    const wireGeo = new THREE.WireframeGeometry(geo);
    const wire = new THREE.LineSegments(wireGeo, wireframeMat);
    scene.add(wire);

    // Inner solid (subtle)
    const innerMat = new THREE.MeshBasicMaterial({
      color: 0x00577B,
      transparent: true,
      opacity: 0.05,
    });
    const inner = new THREE.Mesh(geo, innerMat);
    scene.add(inner);

    // Vertex points (amber)
    const pointsGeo = new THREE.BufferGeometry();
    pointsGeo.setAttribute("position", geo.attributes.position.clone());
    const pointsMat = new THREE.PointsMaterial({
      color: 0xE09839,
      size: 0.06,
      transparent: true,
      opacity: 0.9,
      sizeAttenuation: true,
    });
    const points = new THREE.Points(pointsGeo, pointsMat);
    scene.add(points);

    // Orbiting particles
    const particleCount = 220;
    const particlesGeo = new THREE.BufferGeometry();
    const particlePos = new Float32Array(particleCount * 3);
    const particleSeed = new Float32Array(particleCount * 4);
    for (let i = 0; i < particleCount; i++) {
      const r = 3.2 + Math.random() * 1.5;
      const theta = Math.random() * Math.PI * 2;
      const phi = Math.acos(2 * Math.random() - 1);
      particlePos[i * 3 + 0] = r * Math.sin(phi) * Math.cos(theta);
      particlePos[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
      particlePos[i * 3 + 2] = r * Math.cos(phi);
      particleSeed[i * 4 + 0] = r;
      particleSeed[i * 4 + 1] = theta;
      particleSeed[i * 4 + 2] = phi;
      particleSeed[i * 4 + 3] = Math.random() * 2 - 1;
    }
    particlesGeo.setAttribute("position", new THREE.BufferAttribute(particlePos, 3));
    const particlesMat = new THREE.PointsMaterial({
      color: 0x00577B,
      size: 0.045,
      transparent: true,
      opacity: 0.6,
      sizeAttenuation: true,
    });
    const particles = new THREE.Points(particlesGeo, particlesMat);
    scene.add(particles);

    // Group for easy rotation
    const group = new THREE.Group();
    group.add(wire);
    group.add(inner);
    group.add(points);
    group.add(particles);
    scene.add(group);

    // Mouse + scroll listeners
    const onMouseMove = (e) => {
      stateRef.current.mouseX = (e.clientX / window.innerWidth) * 2 - 1;
      stateRef.current.mouseY = (e.clientY / window.innerHeight) * 2 - 1;
    };
    const onScroll = () => {
      stateRef.current.scrollY = window.scrollY;
    };
    window.addEventListener("mousemove", onMouseMove, { passive: true });
    window.addEventListener("scroll", onScroll, { passive: true });

    const onResize = () => {
      const W = mount.clientWidth, H = mount.clientHeight;
      renderer.setSize(W, H);
      camera.aspect = W / H;
      camera.updateProjectionMatrix();
    };
    window.addEventListener("resize", onResize);

    // Animation
    let raf = 0;
    const start = performance.now();
    const animate = () => {
      const t = (performance.now() - start) / 1000;
      const { mouseX, mouseY, scrollY } = stateRef.current;

      // Morph the wireframe vertices
      const wirePos = wireGeo.attributes.position.array;
      // We'll morph the source geo and rebuild wireframe? Costly.
      // Instead morph the points geo and use its positions to shape the inner mesh.
      const pPos = pointsGeo.attributes.position.array;
      for (let i = 0; i < pPos.length; i += 3) {
        const ox = positions[i];
        const oy = positions[i + 1];
        const oz = positions[i + 2];
        const len = Math.sqrt(ox * ox + oy * oy + oz * oz);
        const nx = ox / len, ny = oy / len, nz = oz / len;
        const n = Math.sin(t * 1.2 + ox * 1.3) * Math.cos(t * 0.9 + oy * 1.1) * 0.18;
        const m = Math.sin(t * 0.7 + oz * 1.6) * 0.08;
        const d = 1 + n + m;
        pPos[i + 0] = nx * len * d;
        pPos[i + 1] = ny * len * d;
        pPos[i + 2] = nz * len * d;
      }
      pointsGeo.attributes.position.needsUpdate = true;

      // Subtle morph on inner mesh too
      const innerPos = geo.attributes.position.array;
      for (let i = 0; i < innerPos.length; i += 3) {
        const ox = positions[i];
        const oy = positions[i + 1];
        const oz = positions[i + 2];
        const len = Math.sqrt(ox * ox + oy * oy + oz * oz);
        const nx = ox / len, ny = oy / len, nz = oz / len;
        const n = Math.sin(t * 1.2 + ox * 1.3) * Math.cos(t * 0.9 + oy * 1.1) * 0.18;
        const m = Math.sin(t * 0.7 + oz * 1.6) * 0.08;
        const d = 1 + n + m;
        innerPos[i + 0] = nx * len * d;
        innerPos[i + 1] = ny * len * d;
        innerPos[i + 2] = nz * len * d;
      }
      geo.attributes.position.needsUpdate = true;
      // Rebuild wireframe occasionally would be expensive; skip — wire stays based on initial geo.
      // Replace approach: rebuild wireGeo from updated geo
      wire.geometry.dispose();
      wire.geometry = new THREE.WireframeGeometry(geo);

      // Orbit particles
      const orbPos = particlesGeo.attributes.position.array;
      for (let i = 0; i < particleCount; i++) {
        const r = particleSeed[i * 4 + 0];
        const theta0 = particleSeed[i * 4 + 1];
        const phi = particleSeed[i * 4 + 2];
        const speed = 0.12 + particleSeed[i * 4 + 3] * 0.1;
        const theta = theta0 + t * speed;
        orbPos[i * 3 + 0] = r * Math.sin(phi) * Math.cos(theta);
        orbPos[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
        orbPos[i * 3 + 2] = r * Math.cos(phi);
      }
      particlesGeo.attributes.position.needsUpdate = true;

      // Group rotation: gentle auto + mouse parallax + scroll lift
      const targetX = mouseY * 0.25 + (scrollY * 0.0008);
      const targetY = mouseX * 0.45 + t * 0.12;
      group.rotation.x += (targetX - group.rotation.x) * 0.05;
      group.rotation.y += (targetY - group.rotation.y) * 0.05;

      // Camera zoom on scroll (gentle)
      camera.position.z = 7 + Math.min(scrollY * 0.002, 1.5);

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

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("mousemove", onMouseMove);
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onResize);
      mount.removeChild(renderer.domElement);
      geo.dispose();
      wire.geometry.dispose();
      wireframeMat.dispose();
      innerMat.dispose();
      pointsGeo.dispose();
      pointsMat.dispose();
      particlesGeo.dispose();
      particlesMat.dispose();
      renderer.dispose();
    };
  }, []);

  return (
    <div className="hero-canvas-wrap" ref={mountRef}>
      <div className="hero-canvas-fade" />
    </div>
  );
}

window.NeuralSphere = NeuralSphere;
