import { describe, it, expect } from 'vitest';
import { stepParticle, sampleSpawn, sampleBurst } from '../particleSim';
import { createRng } from '../../math/rng';
import type { EmitterOptions } from '../Particles';

const opts: EmitterOptions = {
    color: 0xffffff,
    rate: 10,
    lifetime: [1, 2],
    velocity: { x: [-10, 10], y: [-10, 10] }
};

describe('stepParticle', () => {
    it('позиция после шагов (полу-неявный Эйлер)', () => {
        const p = { x: 0, y: 0, vx: 10, vy: -5, age: 0, lifetime: 10 };
        stepParticle(p, 0.5, undefined, false);
        expect(p.x).toBeCloseTo(5);
        expect(p.y).toBeCloseTo(-2.5);
        expect(p.age).toBeCloseTo(0.5);
    });

    it('ускорение влияет на скорость', () => {
        const p = { x: 0, y: 0, vx: 0, vy: 0, age: 0, lifetime: 10 };
        stepParticle(p, 1, { y: 100 }, false);
        expect(p.vy).toBeCloseTo(100);
        expect(p.y).toBeCloseTo(100); // скорость применилась в том же шаге
    });

    it('fadeOut уменьшает alpha, без fadeOut alpha = 1', () => {
        const a = { x: 0, y: 0, vx: 0, vy: 0, age: 0, lifetime: 2 };
        expect(stepParticle(a, 1, undefined, true)).toBeCloseTo(0.5);
        const b = { x: 0, y: 0, vx: 0, vy: 0, age: 0, lifetime: 2 };
        expect(stepParticle(b, 1, undefined, false)).toBe(1);
    });
});

describe('sampleSpawn', () => {
    it('учитывает spawnArea и диапазоны', () => {
        const rng = createRng(1);
        for (let i = 0; i < 50; i++) {
            const s = sampleSpawn({ ...opts, spawnArea: { width: 20, height: 10 } }, rng);
            expect(s.x).toBeGreaterThanOrEqual(-10);
            expect(s.x).toBeLessThanOrEqual(10);
            expect(s.y).toBeGreaterThanOrEqual(-5);
            expect(s.y).toBeLessThanOrEqual(5);
            expect(s.vx).toBeGreaterThanOrEqual(-10);
            expect(s.vx).toBeLessThanOrEqual(10);
            expect(s.lifetime).toBeGreaterThanOrEqual(1);
            expect(s.lifetime).toBeLessThanOrEqual(2);
            expect(s.age).toBe(0);
        }
    });

    it('без области — старт из точки', () => {
        const s = sampleSpawn(opts, createRng(1));
        expect(s.x).toBe(0);
        expect(s.y).toBe(0);
    });
});

describe('sampleBurst', () => {
    it('нужное количество, скорость в диапазоне', () => {
        const rng = createRng(5);
        const parts = sampleBurst(12, [30, 80], [0.2, 0.5], rng);
        expect(parts).toHaveLength(12);
        for (const p of parts) {
            const v = Math.hypot(p.vx, p.vy);
            expect(v).toBeGreaterThanOrEqual(30 - 1e-9);
            expect(v).toBeLessThanOrEqual(80 + 1e-9);
            expect(p.lifetime).toBeGreaterThanOrEqual(0.2);
            expect(p.lifetime).toBeLessThanOrEqual(0.5);
        }
    });

    it('детерминирован по seed', () => {
        const a = sampleBurst(5, [10, 20], [0.1, 0.2], createRng(99));
        const b = sampleBurst(5, [10, 20], [0.1, 0.2], createRng(99));
        expect(a).toEqual(b);
    });
});