/**
 * Тесты света частиц: блок юниформ (дефолты), упаковка снапшота источников
 * (цвет × яркость, кап), фонарик (нормировка направления, конус), сброс.
 */
import { describe, expect, it } from 'vitest';
import { makeParticleLightUniforms, packParticleLights, PARTICLE_LIGHT_MAX } from '../particles';
import type { ParticleLight, ParticleSpot } from '../particles';

describe('свет частиц (packParticleLights)', () => {
    it('блок юниформ: по умолчанию источников нет, фонарик погашен', () => {
        const u = makeParticleLightUniforms();
        expect(u.uLightCount!.value).toBe(0);
        expect(u.uSpotOn!.value).toBe(0);
        expect((u.uLightPos!.value as Float32Array).length).toBe(PARTICLE_LIGHT_MAX * 3);
    });

    it('упаковка: цвет × яркость, радиус и позиция по местам', () => {
        const u = makeParticleLightUniforms();
        const points: ParticleLight[] = [
            { pos: [10, 2, 20], color: 0xff8040, intensity: 0.5, radius: 30 },
            { pos: [0, 2, 0], color: 0x202020, intensity: 2, radius: 5 },
        ];
        packParticleLights(u, points, null);
        expect(u.uLightCount!.value).toBe(2);
        const col = u.uLightColor!.value as Float32Array;
        // 0xff8040 × 0.5 → (0.5, 0.25, 0.125)
        expect(col[0]).toBeCloseTo(0.5);
        expect(col[1]).toBeCloseTo(0.251, 2);
        expect(col[2]).toBeCloseTo(0.125, 2);
        // второй источник: 0x20 × 2 → 0.25
        expect(col[5]).toBeCloseTo(0.25);
        const pos = u.uLightPos!.value as Float32Array;
        expect([pos[0], pos[1], pos[2]]).toEqual([10, 2, 20]);
        expect((u.uLightRadius!.value as Float32Array)[1]).toBe(5);
        expect(u.uSpotOn!.value).toBe(0);
    });

    it('кап: лишние источники обрезаются до PARTICLE_LIGHT_MAX', () => {
        const u = makeParticleLightUniforms();
        const many: ParticleLight[] = Array.from({ length: 40 }, (_, i) => ({
            pos: [i, 0, 0], color: 0xffffff, intensity: 1, radius: 1,
        }));
        packParticleLights(u, many, null);
        expect(u.uLightCount!.value).toBe(PARTICLE_LIGHT_MAX);
    });

    it('фонарик: направление нормируется, конус от угла и penumbra', () => {
        const u = makeParticleLightUniforms();
        const spot: ParticleSpot = {
            pos: [5, 7, 5], dir: [0, -3, 0], color: 0xfff2c0, intensity: 0.8,
            distance: 60, angle: Math.PI / 4, penumbra: 0.4,
        };
        packParticleLights(u, [], spot);
        expect(u.uSpotOn!.value).toBe(1);
        const dir = u.uSpotDir!.value as Float32Array;
        expect([dir[0], dir[1], dir[2]]).toEqual([0, -1, 0]); // нормировка
        expect(u.uSpotCos!.value).toBeCloseTo(Math.cos(Math.PI / 8));
        // внутренний край — уже угол, косинус больше внешнего
        expect(u.uSpotSoft!.value).toBeGreaterThan(u.uSpotCos!.value);
        const c = u.uSpotColor!.value as Float32Array;
        expect(c[0]).toBeCloseTo(0.8);
        expect(c[1]).toBeCloseTo((0xf2 / 255) * 0.8);
    });

    it('сброс: погасший фонарик гасит uSpotOn, счётчик обнуляется', () => {
        const u = makeParticleLightUniforms();
        packParticleLights(u, [], {
            pos: [0, 0, 0], dir: [1, 0, 0], color: 0xffffff, intensity: 1,
            distance: 10, angle: 1, penumbra: 0.5,
        });
        expect(u.uSpotOn!.value).toBe(1);
        packParticleLights(u, [], null);
        expect(u.uSpotOn!.value).toBe(0);
        packParticleLights(u, [{ pos: [1, 1, 1], color: 0xffffff, intensity: 1, radius: 2 }], null);
        expect(u.uLightCount!.value).toBe(1);
        packParticleLights(u, [], null);
        expect(u.uLightCount!.value).toBe(0);
    });
});