import * as THREE from 'three';

export class ParticleSystem {
    constructor(game) {
        this.game = game;
        this.systems = [];
    }

    createFire(position, opts = {}) {
        const count = opts.count || 60;
        const geo = new THREE.BufferGeometry();
        const positions = new Float32Array(count * 3);
        const velocities = new Float32Array(count * 3);
        const lifetimes = new Float32Array(count);
        const sizes = new Float32Array(count);

        for (let i = 0; i < count; i++) {
            this.resetFireParticle(positions, velocities, lifetimes, sizes, i, position);
        }

        geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        geo.setAttribute('size', new THREE.BufferAttribute(sizes, 1));

        const mat = new THREE.PointsMaterial({
            color: 0xff6622,
            size: 0.25,
            transparent: true,
            opacity: 0.8,
            blending: THREE.AdditiveBlending,
            depthWrite: false,
            sizeAttenuation: true
        });

        const mesh = new THREE.Points(geo, mat);
        this.game.scene.add(mesh);

        this.systems.push({
            type: 'fire',
            mesh, geo, positions, velocities, lifetimes, sizes,
            count, origin: position.clone(),
            resetFn: (i) => this.resetFireParticle(positions, velocities, lifetimes, sizes, i, position)
        });

        return mesh;
    }

    resetFireParticle(positions, velocities, lifetimes, sizes, i, origin) {
        const i3 = i * 3;
        positions[i3]     = origin.x + (Math.random() - 0.5) * 0.4;
        positions[i3 + 1] = origin.y + Math.random() * 0.2;
        positions[i3 + 2] = origin.z + (Math.random() - 0.5) * 0.4;
        velocities[i3]     = (Math.random() - 0.5) * 0.3;
        velocities[i3 + 1] = 1.0 + Math.random() * 2.0;
        velocities[i3 + 2] = (Math.random() - 0.5) * 0.3;
        lifetimes[i] = Math.random() * 1.5;
        sizes[i] = 0.15 + Math.random() * 0.2;
    }

    createRain() {
        const count = 3000;
        const geo = new THREE.BufferGeometry();
        const positions = new Float32Array(count * 3);
        const velocities = new Float32Array(count * 3);

        for (let i = 0; i < count; i++) {
            const i3 = i * 3;
            positions[i3]     = (Math.random() - 0.5) * 120;
            positions[i3 + 1] = Math.random() * 40;
            positions[i3 + 2] = (Math.random() - 0.5) * 120;
            velocities[i3]     = -0.5;
            velocities[i3 + 1] = -15 - Math.random() * 10;
            velocities[i3 + 2] = -1;
        }

        geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));

        const mat = new THREE.PointsMaterial({
            color: 0xaaccff,
            size: 0.08,
            transparent: true,
            opacity: 0.5,
            depthWrite: false
        });

        const mesh = new THREE.Points(geo, mat);
        mesh.visible = false;
        this.game.scene.add(mesh);

        this.systems.push({
            type: 'rain',
            mesh, geo, positions, velocities, count
        });

        return mesh;
    }

    createSnow() {
        const count = 2000;
        const geo = new THREE.BufferGeometry();
        const positions = new Float32Array(count * 3);
        const velocities = new Float32Array(count * 3);
        const phases = new Float32Array(count);

        for (let i = 0; i < count; i++) {
            const i3 = i * 3;
            positions[i3]     = (Math.random() - 0.5) * 120;
            positions[i3 + 1] = Math.random() * 40;
            positions[i3 + 2] = (Math.random() - 0.5) * 120;
            velocities[i3]     = 0;
            velocities[i3 + 1] = -1.5 - Math.random() * 1.5;
            velocities[i3 + 2] = 0;
            phases[i] = Math.random() * Math.PI * 2;
        }

        geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));

        const mat = new THREE.PointsMaterial({
            color: 0xffffff,
            size: 0.15,
            transparent: true,
            opacity: 0.8,
            depthWrite: false
        });

        const mesh = new THREE.Points(geo, mat);
        mesh.visible = false;
        this.game.scene.add(mesh);

        this.systems.push({
            type: 'snow',
            mesh, geo, positions, velocities, count, phases,
            time: 0
        });

        return mesh;
    }

    createSparks(position) {
        const count = 20;
        const geo = new THREE.BufferGeometry();
        const positions = new Float32Array(count * 3);
        const velocities = new Float32Array(count * 3);
        const lifetimes = new Float32Array(count);

        for (let i = 0; i < count; i++) {
            const i3 = i * 3;
            positions[i3]     = position.x;
            positions[i3 + 1] = position.y + 0.5;
            positions[i3 + 2] = position.z;
            const angle = Math.random() * Math.PI * 2;
            const speed = 1 + Math.random() * 3;
            velocities[i3]     = Math.cos(angle) * speed * 0.3;
            velocities[i3 + 1] = 2 + Math.random() * 4;
            velocities[i3 + 2] = Math.sin(angle) * speed * 0.3;
            lifetimes[i] = 0.5 + Math.random() * 1.0;
        }

        geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));

        const mat = new THREE.PointsMaterial({
            color: 0xffaa00,
            size: 0.08,
            transparent: true,
            opacity: 1,
            blending: THREE.AdditiveBlending,
            depthWrite: false
        });

        const mesh = new THREE.Points(geo, mat);
        this.game.scene.add(mesh);

        this.systems.push({
            type: 'sparks',
            mesh, geo, positions, velocities, lifetimes, count,
            origin: position.clone(), age: 0, maxAge: 1.5
        });
    }

    update(dt) {
        const playerPos = this.game.player.position;

        for (let s = this.systems.length - 1; s >= 0; s--) {
            const sys = this.systems[s];

            if (sys.type === 'fire') {
                this.updateFire(sys, dt);
            } else if (sys.type === 'rain') {
                this.updateWeatherParticles(sys, dt, playerPos);
            } else if (sys.type === 'snow') {
                this.updateSnow(sys, dt, playerPos);
            } else if (sys.type === 'sparks') {
                sys.age += dt;
                if (sys.age > sys.maxAge) {
                    this.game.scene.remove(sys.mesh);
                    sys.geo.dispose();
                    sys.mesh.material.dispose();
                    this.systems.splice(s, 1);
                    continue;
                }
                this.updateSparks(sys, dt);
            }
        }
    }

    updateFire(sys, dt) {
        const pos = sys.positions;
        const vel = sys.velocities;
        const life = sys.lifetimes;

        for (let i = 0; i < sys.count; i++) {
            const i3 = i * 3;
            life[i] -= dt;

            if (life[i] <= 0) {
                sys.resetFn(i);
                continue;
            }

            pos[i3]     += vel[i3] * dt;
            pos[i3 + 1] += vel[i3 + 1] * dt;
            pos[i3 + 2] += vel[i3 + 2] * dt;

            vel[i3 + 1] += dt * 0.5; // подъём
        }

        sys.geo.attributes.position.needsUpdate = true;
        sys.mesh.material.opacity = 0.5 + Math.sin(Date.now() * 0.01) * 0.3;
    }

    updateWeatherParticles(sys, dt, playerPos) {
        const pos = sys.positions;
        const vel = sys.velocities;

        for (let i = 0; i < sys.count; i++) {
            const i3 = i * 3;
            pos[i3]     += vel[i3] * dt;
            pos[i3 + 1] += vel[i3 + 1] * dt;
            pos[i3 + 2] += vel[i3 + 2] * dt;

            if (pos[i3 + 1] < 0) {
                pos[i3]     = playerPos.x + (Math.random() - 0.5) * 120;
                pos[i3 + 1] = 30 + Math.random() * 10;
                pos[i3 + 2] = playerPos.z + (Math.random() - 0.5) * 120;
            }
        }

        sys.geo.attributes.position.needsUpdate = true;
    }

    updateSnow(sys, dt, playerPos) {
        const pos = sys.positions;
        const vel = sys.velocities;
        sys.time += dt;

        for (let i = 0; i < sys.count; i++) {
            const i3 = i * 3;
            pos[i3]     += vel[i3] * dt + Math.sin(sys.time * 2 + sys.phases[i]) * 0.3 * dt;
            pos[i3 + 1] += vel[i3 + 1] * dt;
            pos[i3 + 2] += vel[i3 + 2] * dt + Math.cos(sys.time * 1.5 + sys.phases[i]) * 0.3 * dt;

            if (pos[i3 + 1] < 0) {
                pos[i3]     = playerPos.x + (Math.random() - 0.5) * 120;
                pos[i3 + 1] = 30 + Math.random() * 10;
                pos[i3 + 2] = playerPos.z + (Math.random() - 0.5) * 120;
            }
        }

        sys.geo.attributes.position.needsUpdate = true;
    }

    updateSparks(sys, dt) {
        const pos = sys.positions;
        const vel = sys.velocities;

        for (let i = 0; i < sys.count; i++) {
            const i3 = i * 3;
            pos[i3]     += vel[i3] * dt;
            pos[i3 + 1] += vel[i3 + 1] * dt;
            pos[i3 + 2] += vel[i3 + 2] * dt;
            vel[i3 + 1] -= 9.8 * dt; // гравитация
        }

        sys.geo.attributes.position.needsUpdate = true;
        sys.mesh.material.opacity = 1 - (sys.age / sys.maxAge);
    }

    setWeather(type) {
        this.systems.forEach(sys => {
            if (sys.type === 'rain') sys.mesh.visible = (type === 'rain');
            if (sys.type === 'snow') sys.mesh.visible = (type === 'snow');
        });
    }

    clear() {
        this.systems.forEach(sys => {
            this.game.scene.remove(sys.mesh);
            sys.geo.dispose();
            sys.mesh.material.dispose();
        });
        this.systems = [];
    }
}
