"use client";

import { useMemo, useRef } from "react";
import { Canvas, useFrame, useThree } from "@react-three/fiber";
import * as THREE from "three";
import { Preload } from "@react-three/drei";

// CONFIGURATION
const PARTICLE_COUNT = 80;
const CONNECT_DISTANCE = 3.5;
const MOUSE_INFLUENCE_RADIUS = 6; // Radius of the mouse wave
const WAVE_AMPLITUDE = 1.5; // How high the wave ripples (Z-axis)

function NeuralMesh() {
  const groupRef = useRef<THREE.Group>(null);
  const particlesRef = useRef<THREE.Points>(null);
  const linesRef = useRef<THREE.LineSegments>(null);
  
  // Get viewport to map mouse coordinates correctly to world space
  const { viewport } = useThree();

  // 1. Generate Initial Random Positions & Velocities
  // We store "original positions" to calculate the wave offset from
  const [positions, velocities, originalPositions] = useMemo(() => {
    const pos = new Float32Array(PARTICLE_COUNT * 3);
    const origPos = new Float32Array(PARTICLE_COUNT * 3);
    const vel = [];
    
    for (let i = 0; i < PARTICLE_COUNT; i++) {
      const x = (Math.random() - 0.5) * 18;
      const y = (Math.random() - 0.5) * 18;
      const z = (Math.random() - 0.5) * 10;

      pos[i * 3] = x;
      pos[i * 3 + 1] = y;
      pos[i * 3 + 2] = z;

      origPos[i * 3] = x;
      origPos[i * 3 + 1] = y;
      origPos[i * 3 + 2] = z;

      vel.push({
        x: (Math.random() - 0.5) * 0.05, // Slower natural drift
        y: (Math.random() - 0.5) * 0.05,
        z: (Math.random() - 0.5) * 0.05,
      });
    }
    return [pos, vel, origPos];
  }, []);

  useFrame((state, delta) => {
    // 2. Map Mouse to World Coordinates
    // state.pointer is normalized (-1 to 1). Convert to world units using viewport.
    const mouseX = (state.pointer.x * viewport.width) / 2;
    const mouseY = (state.pointer.y * viewport.height) / 2;

    if (particlesRef.current && linesRef.current) {
      const positionsAttr = particlesRef.current.geometry.attributes.position;
      const currentPositions = positionsAttr.array as Float32Array;

      // 3. Update Particles
      for (let i = 0; i < PARTICLE_COUNT; i++) {
        const i3 = i * 3;

        // A. Natural Drift (Gravity)
        // We use the "original" position as an anchor to prevent them from flying away too far
        // but we add the velocity to keep them moving organically.
        originalPositions[i3]     += velocities[i].x * delta * 2;
        originalPositions[i3 + 1] += velocities[i].y * delta * 2;
        
        // Bounce logic for the anchor points
        if (Math.abs(originalPositions[i3]) > 10) velocities[i].x *= -1;
        if (Math.abs(originalPositions[i3 + 1]) > 10) velocities[i].y *= -1;

        // B. Mouse Interaction (The Wave)
        // Calculate distance from particle to mouse
        const dx = mouseX - originalPositions[i3];
        const dy = mouseY - originalPositions[i3 + 1];
        const dist = Math.sqrt(dx * dx + dy * dy);

        // Apply Wave Effect
        // If the mouse is close, we disturb the position
        let xOffset = 0;
        let yOffset = 0;
        let zOffset = 0;

        if (dist < MOUSE_INFLUENCE_RADIUS) {
          // 1. Repulsion force (XY Plane) - pushes them slightly aside
          const force = (MOUSE_INFLUENCE_RADIUS - dist) / MOUSE_INFLUENCE_RADIUS;
          const angle = Math.atan2(dy, dx);
          
          xOffset = -Math.cos(angle) * force * 2; // Push away X
          yOffset = -Math.sin(angle) * force * 2; // Push away Y
          
          // 2. Wave Ripple (Z Plane) - Sine wave based on distance and time
          // This creates the "water ripple" effect in 3D depth
          zOffset = Math.sin(dist * 1.5 - state.clock.elapsedTime * 3) * WAVE_AMPLITUDE * force;
        }

        // Apply calculated positions
        currentPositions[i3]     = originalPositions[i3] + xOffset;
        currentPositions[i3 + 1] = originalPositions[i3 + 1] + yOffset;
        currentPositions[i3 + 2] = originalPositions[i3 + 2] + zOffset;
      }
      positionsAttr.needsUpdate = true;

      // 4. Update Connections (Plexus)
      // Re-calculate lines based on the NEW modified positions
      const linePositions: number[] = [];
      
      for (let i = 0; i < PARTICLE_COUNT; i++) {
        for (let j = i + 1; j < PARTICLE_COUNT; j++) {
          const x1 = currentPositions[i * 3];
          const y1 = currentPositions[i * 3 + 1];
          const z1 = currentPositions[i * 3 + 2];

          const x2 = currentPositions[j * 3];
          const y2 = currentPositions[j * 3 + 1];
          const z2 = currentPositions[j * 3 + 2];

          const dist = Math.sqrt(
            Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2) + Math.pow(z2 - z1, 2)
          );

          if (dist < CONNECT_DISTANCE) {
            linePositions.push(x1, y1, z1, x2, y2, z2);
          }
        }
      }

      linesRef.current.geometry.setAttribute(
        "position",
        new THREE.Float32BufferAttribute(linePositions, 3)
      );
    }
  });

  return (
    <group ref={groupRef}>
      <points ref={particlesRef}>
        <bufferGeometry>
          <bufferAttribute
            attach="attributes-position"
            count={PARTICLE_COUNT}
            array={positions}
            itemSize={3}
          />
        </bufferGeometry>
        <pointsMaterial
          size={0.15}
          color="#22d3ee" // Cyan
          sizeAttenuation={true}
          transparent
          opacity={0.8}
        />
      </points>

      <lineSegments ref={linesRef}>
        <bufferGeometry />
        <lineBasicMaterial
          color="#0891b2" // Cyan-600
          transparent
          opacity={0.15}
        />
      </lineSegments>
    </group>
  );
}

export default function NeuronsGravity() {
  return (
    <div className="absolute inset-0 z-0 h-full w-full">
      <Canvas
        camera={{ position: [0, 0, 12], fov: 50 }}
        gl={{ alpha: true, antialias: true }}
        dpr={[1, 2]}
      >
        <NeuralMesh />
        <Preload all />
      </Canvas>
    </div>
  );
}