import { describe, expect, it } from 'vitest';
import { Lighting } from '../Lighting';
import { GLOW_BASE_PX, type LightFrame } from '../lightSim';

function makeLighting(maxLights = 16): Lighting {
    // Без renderer: glow-текстура = Texture.WHITE, пул и логика тестируются в node.
    return new Lighting({ width: 480, height: 270, maxLights });
}

describe('Lighting: ambient', () => {
    it('ambient-спрайт: multiply, белый, на всю сцену', () => {
        const l = makeLighting();
        const ambient = l.children[0];
        expect(ambient.blendMode).toBe('multiply');
        expect(ambient.tint).toBe(0xffffff);
        expect(ambient.width).toBe(480);
        expect(ambient.height).toBe(270);
        l.destroy();
    });

    it('setAmbient без fade применяется мгновенно', () => {
        const l = makeLighting();
        l.setAmbient(0x54586a);
        expect(l.ambientColor).toBe(0x54586a);
        expect(l.children[0].tint).toBe(0x54586a);
        l.destroy();
    });

    it('setAmbient с fade — лерп к целевому цвету', () => {
        const l = makeLighting();
        l.setAmbient(0x000000);
        l.setAmbient(0xffffff, 1);
        l.update(0.5);
        const mid = l.children[0].tint;
        expect(mid).not.toBe(0x000000);
        expect(mid).not.toBe(0xffffff);
        l.update(0.5);
        expect(l.children[0].tint).toBe(0xffffff);
        expect(l.ambientColor).toBe(0xffffff);
        l.destroy();
    });
});

describe('Lighting: источники', () => {
    it('upsertLight добавляет источник, кадр читается после update', () => {
        const l = makeLighting();
        l.upsertLight({ id: 'hearth', x: 100, y: 50, color: 0xf2b45a });
        l.update(1 / 60);
        const frames = l.frames();
        expect(frames.length).toBe(1);
        expect(frames[0].tint).toBe(0xf2b45a);
        expect(frames[0].alpha).toBeGreaterThan(0);
        l.destroy();
    });

    it('лишние источники сверх пула игнорируются', () => {
        const l = makeLighting(2);
        l.upsertLight({ id: 'a', x: 0, y: 0, color: 0xffffff });
        l.upsertLight({ id: 'b', x: 0, y: 0, color: 0xffffff });
        l.upsertLight({ id: 'c', x: 0, y: 0, color: 0xffffff });
        expect(l.frames().length).toBe(2);
        expect(l.hasLight('c')).toBe(false);
        l.destroy();
    });

    it('removeLight возвращает спрайт в пул: число детей стабильно', () => {
        const l = makeLighting();
        const before = l.children.length;
        l.upsertLight({ id: 'a', x: 0, y: 0, color: 0xffffff });
        l.upsertLight({ id: 'b', x: 0, y: 0, color: 0xffffff });
        l.removeLight('a');
        l.upsertLight({ id: 'c', x: 0, y: 0, color: 0xffffff });
        expect(l.children.length).toBe(before);
        expect(l.hasLight('a')).toBe(false);
        expect(l.hasLight('c')).toBe(true);
        l.destroy();
    });

    it('setLightPos/setLightEnabled отражаются в кадрах', () => {
        const l = makeLighting();
        l.upsertLight({ id: 'a', x: 0, y: 0, color: 0xffffff, radius: 2 * GLOW_BASE_PX });
        l.setLightPos('a', 123, 45);
        l.setLightEnabled('a', false);
        l.update(1 / 60);
        const f: LightFrame = l.frames()[0];
        expect(f.x).toBe(123);
        expect(f.y).toBe(45);
        expect(f.alpha).toBe(0);
        l.destroy();
    });

    it('upsertLight обновляет параметры существующего', () => {
        const l = makeLighting();
        l.upsertLight({ id: 'a', x: 0, y: 0, color: 0xffffff, intensity: 0.5 });
        l.upsertLight({ id: 'a', x: 7, y: 8, color: 0x112233, intensity: 1, flicker: 0 });
        l.update(1 / 60);
        const f = l.frames()[0];
        expect(f.x).toBe(7);
        expect(f.tint).toBe(0x112233);
        expect(l.children.length).toBe(l.children.length); // пул не рос
        l.destroy();
    });

    it('мерцание меняет alpha между апдейтами', () => {
        const l = makeLighting();
        l.upsertLight({ id: 'a', x: 0, y: 0, color: 0xffffff, flicker: 0.5 });
        l.update(0);
        const a1 = l.frames()[0].alpha;
        l.update(0.37);
        const a2 = l.frames()[0].alpha;
        expect(a1).not.toBe(a2);
        l.destroy();
    });
});

describe('Lighting: импульсы', () => {
    it('pulseLight появляется в кадрах, модулируется огибающей и снимается', () => {
        const l = makeLighting(2); // дети: ambient + виньетка + darkLayer + pulseAmb + 2 спрайта
        const id = l.pulseLight({ x: 10, y: 20, color: 0xf2b45a, intensity: 0.5, spec: { attack: 0.05, decay: 0.1 } });
        expect(id).toMatch(/^pulse#/);
        l.update(0.01);
        let f = l.frames().find((fr) => fr.id === id);
        expect(f).toBeTruthy();
        expect(f!.tint).toBe(0xf2b45a);
        expect(f!.alpha).toBeCloseTo(0.5 * 0.2, 5); // elapsed 0.01 из attack 0.05
        l.update(0.04); // elapsed 0.05 — пик огибающей
        f = l.frames().find((fr) => fr.id === id);
        expect(f!.alpha).toBeCloseTo(0.5, 5);
        l.update(0.1); // elapsed 0.15 = total — снят
        expect(l.frames().length).toBe(0);
        expect(l.hasLight(id)).toBe(false);
        expect(l.children.length).toBe(6); // ambient+виньетка+darkLayer+pulseAmb+2 спрайта
        l.destroy();
    });

    it('pulseLight при исчерпании пула не рисуется (пустой id)', () => {
        const l = makeLighting(1);
        l.upsertLight({ id: 'hearth', x: 0, y: 0, color: 0xffffff });
        const id = l.pulseLight({ x: 0, y: 0, color: 0xffffff, spec: { attack: 0.05, decay: 0.1 } });
        expect(id).toBe('');
        expect(l.hasLight('pulse#1')).toBe(false);
        expect(l.frames().length).toBe(1);
        l.destroy();
    });

    it('pulseAmbient: alpha по огибающей, новая вспышка побеждает', () => {
        const l = makeLighting();
        l.pulseAmbient({ color: 0xd99a32, peak: 0.2, spec: { attack: 0.1, decay: 0.1 } });
        l.update(0.05);
        expect(l.pulseAmbientAlpha).toBeCloseTo(0.1, 5); // 0.2 * 0.5
        l.pulseAmbient({ color: 0xb0453f, peak: 0.3, spec: { attack: 0, decay: 0.2 } });
        l.update(0.1);
        expect(l.pulseAmbientAlpha).toBeCloseTo(0.15, 5); // 0.3 * 0.5
        l.update(0.2); // конец второй вспышки
        expect(l.pulseAmbientAlpha).toBe(0);
        l.destroy();
    });
});

describe('Lighting: виньетка', () => {
    it('setVignette мгновенно; значение клампится в 0..1', () => {
        const l = makeLighting();
        l.setVignette(1.5);
        expect(l.vignetteLevel).toBe(1);
        expect(l.children[1].alpha).toBe(1); // спрайт виньетки — второй ребёнок
        l.setVignette(-0.5);
        expect(l.vignetteLevel).toBe(0);
        expect(l.children[1].alpha).toBe(0);
        l.destroy();
    });

    it('setVignette с fade — лерп к целевой интенсивности', () => {
        const l = makeLighting();
        l.setVignette(1);
        l.setVignette(0, 1);
        expect(l.vignetteLevel).toBe(0);
        l.update(0.5);
        const mid = l.children[1].alpha;
        expect(mid).toBeGreaterThan(0);
        expect(mid).toBeLessThan(1);
        l.update(0.5);
        expect(l.children[1].alpha).toBe(0);
        l.destroy();
    });
});

describe('Lighting: тёмные пятна', () => {
    it('upsert/remove/has; спрайт multiply с нужным масштабом и альфой', () => {
        const l = makeLighting(2);
        l.upsertDarkSpot({ id: 'hazard@3,4', x: 10, y: 10, radius: 51, alpha: 0.22 });
        expect(l.hasDarkSpot('hazard@3,4')).toBe(true);
        const layer = l.children[2]; // darkLayer — третий ребёнок
        expect(layer.children.filter((c) => c.visible).length).toBe(1);
        const spot = layer.children.find((c) => c.visible)!;
        expect(spot.blendMode).toBe('multiply');
        expect(spot.alpha).toBeCloseTo(0.22, 5);
        expect(spot.scale.x).toBeCloseTo(51 / GLOW_BASE_PX, 5);
        l.upsertDarkSpot({ id: 'hazard@3,4', x: 12, y: 14, radius: 51, alpha: 0.3 });
        expect(layer.children.filter((c) => c.visible).length).toBe(1); // обновление без нового спрайта
        expect(spot.x).toBe(12);
        expect(spot.y).toBe(14);
        l.removeDarkSpot('hazard@3,4');
        expect(l.hasDarkSpot('hazard@3,4')).toBe(false);
        expect(l.children.length).toBe(6); // слои на месте, спрайт в пуле
        l.destroy();
    });

    it('пятна сверх пула игнорируются', () => {
        const l = new Lighting({ width: 480, height: 270, maxDarkSpots: 2 });
        l.upsertDarkSpot({ id: 'a', x: 0, y: 0, radius: 32, alpha: 0.2 });
        l.upsertDarkSpot({ id: 'b', x: 0, y: 0, radius: 32, alpha: 0.2 });
        l.upsertDarkSpot({ id: 'c', x: 0, y: 0, radius: 32, alpha: 0.2 });
        expect(l.hasDarkSpot('a')).toBe(true);
        expect(l.hasDarkSpot('b')).toBe(true);
        expect(l.hasDarkSpot('c')).toBe(false);
        l.destroy();
    });
});

describe('Lighting: destroy', () => {
    it('не бросает и повторный вызов безопасен', () => {
        const l = makeLighting();
        l.upsertLight({ id: 'a', x: 0, y: 0, color: 0xffffff });
        l.setAmbient(0x334455, 0.5);
        l.destroy();
        expect(() => l.destroy()).not.toThrow();
    });
});