// WebGL Particle System with Custom Shaders
import * as THREE from 'three';
import { randomInRange, lerp } from '../utils/math.js';

export class ParticleSystem {
  constructor(count = 800) {
    this.count = count;
    this.positions = null;
    this.velocities = null;
    this.colors = null;
    this.sizes = null;
    this.geometry = null;
    this.material = null;
    this.mesh = null;

    this.baseOpacity = 1.0;
    this.targetOpacity = 1.0;

    this.init();
  }

  init() {
    // Initialize arrays
    this.positions = new Float32Array(this.count * 3);
    this.velocities = new Float32Array(this.count * 3);
    this.colors = new Float32Array(this.count * 3);
    this.sizes = new Float32Array(this.count);

    // Initialize particles in organic cluster
    this.initializeParticles();

    // Create geometry
    this.geometry = new THREE.BufferGeometry();
    this.geometry.setAttribute('position', new THREE.BufferAttribute(this.positions, 3));
    this.geometry.setAttribute('color', new THREE.BufferAttribute(this.colors, 3));
    this.geometry.setAttribute('size', new THREE.BufferAttribute(this.sizes, 1));

    // Create shader material
    this.material = this.createMaterial();

    // Create points mesh
    this.mesh = new THREE.Points(this.geometry, this.material);
  }

  initializeParticles() {
    const primaryColor = new THREE.Color(0x00d4aa); // Teal
    const secondaryColor = new THREE.Color(0x7c3aed); // Purple

    for (let i = 0; i < this.count; i++) {
      const i3 = i * 3;

      // Position in organic cluster (gaussian-like distribution)
      const radius = Math.pow(Math.random(), 0.5) * 0.5;
      const theta = Math.random() * Math.PI * 2;
      const phi = Math.acos(2 * Math.random() - 1);

      this.positions[i3] = radius * Math.sin(phi) * Math.cos(theta);
      this.positions[i3 + 1] = radius * Math.sin(phi) * Math.sin(theta);
      this.positions[i3 + 2] = 0; // Keep 2D for now

      // Random velocities
      this.velocities[i3] = randomInRange(-0.001, 0.001);
      this.velocities[i3 + 1] = randomInRange(-0.001, 0.001);
      this.velocities[i3 + 2] = 0;

      // Colors - mix between primary and secondary
      const mixRatio = Math.random();
      const color = primaryColor.clone().lerp(secondaryColor, mixRatio);
      this.colors[i3] = color.r;
      this.colors[i3 + 1] = color.g;
      this.colors[i3 + 2] = color.b;

      // Sizes - varied for organic look
      this.sizes[i] = randomInRange(0.005, 0.02);
    }
  }

  createMaterial() {
    const vertexShader = `
      attribute float size;
      attribute vec3 color;

      uniform float uTime;
      uniform vec2 uMouse;
      uniform float uOpacity;

      varying vec3 vColor;
      varying float vOpacity;

      void main() {
        vColor = color;
        vOpacity = uOpacity;

        vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);

        // Size attenuation
        gl_PointSize = size * (300.0 / -mvPosition.z);
        gl_Position = projectionMatrix * mvPosition;
      }
    `;

    const fragmentShader = `
      varying vec3 vColor;
      varying float vOpacity;

      void main() {
        // Circular particle with soft edges
        float dist = length(gl_PointCoord - vec2(0.5));
        if (dist > 0.5) discard;

        // Soft glow falloff
        float alpha = 1.0 - smoothstep(0.0, 0.5, dist);
        alpha *= vOpacity;

        // Add inner glow
        float innerGlow = 1.0 - smoothstep(0.0, 0.3, dist);
        vec3 glowColor = vColor + vec3(0.2) * innerGlow;

        gl_FragColor = vec4(glowColor, alpha * 0.8);
      }
    `;

    return new THREE.ShaderMaterial({
      uniforms: {
        uTime: { value: 0 },
        uMouse: { value: new THREE.Vector2(0.5, 0.5) },
        uOpacity: { value: 1.0 }
      },
      vertexShader,
      fragmentShader,
      transparent: true,
      depthWrite: false,
      blending: THREE.AdditiveBlending
    });
  }

  update(deltaTime) {
    // Smooth opacity transition
    this.baseOpacity = lerp(this.baseOpacity, this.targetOpacity, 0.05);
    this.material.uniforms.uOpacity.value = this.baseOpacity;

    // Update positions based on velocities
    for (let i = 0; i < this.count; i++) {
      const i3 = i * 3;

      this.positions[i3] += this.velocities[i3];
      this.positions[i3 + 1] += this.velocities[i3 + 1];
      this.positions[i3 + 2] += this.velocities[i3 + 2];

      // Apply friction
      this.velocities[i3] *= 0.99;
      this.velocities[i3 + 1] *= 0.99;
      this.velocities[i3 + 2] *= 0.99;
    }

    // Mark geometry for update
    this.geometry.attributes.position.needsUpdate = true;
  }

  setOpacity(opacity) {
    this.targetOpacity = opacity;
  }

  applyForce(index, forceX, forceY, forceZ = 0) {
    const i3 = index * 3;
    this.velocities[i3] += forceX;
    this.velocities[i3 + 1] += forceY;
    this.velocities[i3 + 2] += forceZ;
  }

  getPosition(index) {
    const i3 = index * 3;
    return {
      x: this.positions[i3],
      y: this.positions[i3 + 1],
      z: this.positions[i3 + 2]
    };
  }

  setPosition(index, x, y, z = 0) {
    const i3 = index * 3;
    this.positions[i3] = x;
    this.positions[i3 + 1] = y;
    this.positions[i3 + 2] = z;
  }

  dispose() {
    this.geometry.dispose();
    this.material.dispose();
  }
}

export default ParticleSystem;
