/**
 * Слой освещения: fullscreen ambient (multiply-цвет) + аддитивные источники.
 * Тонкий Pixi-адаптер над lightSim (чистая математика — там).
 *
 * Живёт в renderer.lightRoot — экранное пространство между миром и UI:
 * ambient затемняет сцену целиком, источники рисуются поверх затемнения.
 */

import { Container, Graphics, Sprite, Texture } from 'pixi.js';
import type { Renderer } from './Renderer';
import { GLOW_BASE_PX, lightFrame, lerpAmbient, type LightDef, type LightFrame } from './lightSim';

export interface LightingOptions {
    /** Виртуальные размеры сцены (480×270). */
    width: number;
    height: number;
    /** Живой рендерер — для генерации текстуры свечения; без него Texture.WHITE. */
    renderer?: Renderer;
    /** Размер пула спрайтов источников (по умолчанию 16). */
    maxLights?: number;
}

/** Кэш текстур свечения по числу ступеней — генерируется один раз на рендерер. */
const glowCache = new Map<number, Texture>();

/**
 * Ступенчатая пиксельная текстура свечения: концентрические круги с дискретной
 * альфой (никаких гладких градиентов). resolution 1 + nearest — пиксельные ступени.
 */
export function makeGlowTexture(renderer: Renderer, steps = 3): Texture {
    const cached = glowCache.get(steps);
    if (cached) return cached;
    const g = new Graphics();
    for (let i = steps; i >= 1; i--) {
        const r = (GLOW_BASE_PX * i) / steps;
        g.circle(0, 0, r).fill({ color: 0xffffff, alpha: 1 / (i + 1) });
    }
    const tex = renderer.app.renderer.generateTexture({
        target: g,
        resolution: 1,
        antialias: false,
        textureSourceOptions: { scaleMode: 'nearest' }
    });
    g.destroy();
    glowCache.set(steps, tex);
    return tex;
}

interface LightEntry {
    def: LightDef;
    sprite: Sprite;
}

export class Lighting extends Container {
    private readonly ambient: Sprite;
    private readonly free: Sprite[] = [];
    private readonly lights = new Map<string, LightEntry>();
    private readonly glow: Texture;
    private readonly maxLights: number;

    private time = 0;
    private ambientFrom = 0xffffff;
    private ambientTo = 0xffffff;
    private fadeLeft = 0;
    private fadeTotal = 0;

    constructor(options: LightingOptions) {
        super();
        this.maxLights = options.maxLights ?? 16;
        // Ambient: Texture.WHITE, растянутый на сцену; цвет — яркость (0xffffff = не трогает).
        this.ambient = new Sprite(Texture.WHITE);
        this.ambient.width = options.width;
        this.ambient.height = options.height;
        this.ambient.blendMode = 'multiply';
        this.ambient.tint = 0xffffff;
        this.addChild(this.ambient);

        this.glow = options.renderer ? makeGlowTexture(options.renderer) : Texture.WHITE;
        for (let i = 0; i < this.maxLights; i++) this.free.push(this.makeLightSprite());
    }

    private makeLightSprite(): Sprite {
        const s = new Sprite(this.glow);
        s.anchor.set(0.5);
        s.blendMode = 'add';
        s.visible = false;
        this.addChild(s);
        return s;
    }

    // --- ambient (фундамент смены времени дня) ---

    /** Задать ambient-цвет; fadeSec > 0 — плавный лерп (день/ночь, закат). */
    setAmbient(color: number, fadeSec = 0): void {
        this.ambientFrom = this.currentAmbient;
        this.ambientTo = color;
        this.fadeTotal = Math.max(0, fadeSec);
        this.fadeLeft = this.fadeTotal;
        if (this.fadeTotal === 0) this.ambient.tint = color;
    }

    /** Целевой (заданный) ambient-цвет. */
    get ambientColor(): number {
        return this.ambientTo;
    }

    private get currentAmbient(): number {
        return this.fadeLeft > 0 ? this.ambientFrom : this.ambient.tint;
    }

    // --- источники: runtime add/remove/move/color/enable ---

    /** Добавить источник или обновить его параметры целиком. */
    upsertLight(def: LightDef): void {
        let entry = this.lights.get(def.id);
        if (!entry) {
            const sprite = this.free.pop();
            if (!sprite) return; // пул исчерпан — лишние источники игнорируются
            entry = { def: { ...def }, sprite };
            this.lights.set(def.id, entry);
            sprite.visible = true;
        }
        entry.def = { ...def };
    }

    removeLight(id: string): void {
        const entry = this.lights.get(id);
        if (!entry) return;
        entry.sprite.visible = false;
        entry.sprite.alpha = 0;
        this.free.push(entry.sprite);
        this.lights.delete(id);
    }

    hasLight(id: string): boolean {
        return this.lights.has(id);
    }

    setLightPos(id: string, x: number, y: number): void {
        const entry = this.lights.get(id);
        if (entry) {
            entry.def.x = x;
            entry.def.y = y;
        }
    }

    setLightColor(id: string, color: number): void {
        const entry = this.lights.get(id);
        if (entry) entry.def.color = color;
    }

    setLightEnabled(id: string, enabled: boolean): void {
        const entry = this.lights.get(id);
        if (entry) entry.def.enabled = enabled;
    }

    /** Тик: время, лерп ambient, пересчёт кадров источников. */
    update(dt: number): void {
        this.time += dt;
        if (this.fadeLeft > 0) {
            this.fadeLeft = Math.max(0, this.fadeLeft - dt);
            const k = this.fadeTotal > 0 ? 1 - this.fadeLeft / this.fadeTotal : 1;
            this.ambient.tint = lerpAmbient(this.ambientFrom, this.ambientTo, k);
            if (this.fadeLeft === 0) this.ambient.tint = this.ambientTo;
        }
        for (const { def, sprite } of this.lights.values()) {
            const f = lightFrame(def, this.time);
            sprite.position.set(f.x, f.y);
            sprite.tint = f.tint;
            sprite.alpha = Math.min(1, f.alpha);
            sprite.scale.set(f.scale);
        }
    }

    /** Видимые кадры источников (пост-мерцание) — для снапшота агента. */
    frames(): readonly LightFrame[] {
        const out: LightFrame[] = [];
        for (const { def } of this.lights.values()) out.push(lightFrame(def, this.time));
        return out;
    }

    override destroy(): void {
        for (const id of [...this.lights.keys()]) this.removeLight(id);
        for (const s of this.free) s.destroy();
        this.free.length = 0;
        this.ambient.destroy();
        super.destroy();
    }
}