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

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

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

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

/**
 * Ступенчатая пиксельная текстура свечения: концентрические круги с дискретной
 * альфой (никаких гладких градиентов) + дизер-кольцо по внешнему радиусу.
 * resolution 1 + nearest — пиксельные ступени.
 */
export function makeGlowTexture(renderer: Renderer, steps = 4): 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) });
    }
    // Дизер-кольцо: редкие точки по внешнему радиусу — смягчают резкую ступень.
    for (let i = 0; i < 32; i++) {
        const a = ((i * 2) / 64) * Math.PI * 2;
        g.rect(Math.cos(a) * GLOW_BASE_PX - 0.5, Math.sin(a) * GLOW_BASE_PX - 0.5, 1, 1).fill({
            color: 0xffffff,
            alpha: 1 / (steps + 1)
        });
    }
    const tex = renderer.app.renderer.generateTexture({
        target: g,
        resolution: 1,
        antialias: false,
        textureSourceOptions: { scaleMode: 'nearest' }
    });
    g.destroy();
    glowCache.set(steps, tex);
    return tex;
}

/** Кэш текстур виньетки по размеру сцены. */
const vignetteCache = new Map<string, Texture>();

/**
 * Ступенчатая виньетка: белый центр → серые кольца к тёмным углам (multiply).
 * Интенсивность задаётся alpha спрайта, текстура постоянна.
 */
export function makeVignetteTexture(renderer: Renderer, width: number, height: number): Texture {
    const key = `${width}x${height}`;
    const cached = vignetteCache.get(key);
    if (cached) return cached;
    const g = new Graphics();
    const r = Math.hypot(width, height) / 2; // дистанция центра до угла
    g.rect(0, 0, width, height).fill({ color: 0x6a6a74 });
    g.circle(width / 2, height / 2, r * 0.72).fill({ color: 0xa8a8b0 });
    g.circle(width / 2, height / 2, r * 0.42).fill({ color: 0xffffff });
    const tex = renderer.app.renderer.generateTexture({
        target: g,
        resolution: 1,
        antialias: false,
        textureSourceOptions: { scaleMode: 'nearest' }
    });
    g.destroy();
    vignetteCache.set(key, tex);
    return tex;
}

/** Тёмное пятно: локальная аура опасности/тени (multiply). */
export interface DarkSpotDef {
    id: string;
    /** Позиция центра в экранных px. */
    x: number;
    y: number;
    /** Радиус в px. */
    radius: number;
    /** Сила затемнения 0..1. */
    alpha: number;
}

/** Цвет тени тёмного пятна (умножается на сцену). */
const DARK_SPOT_TINT = 0x3e3e48;

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

interface PulseEntry {
    def: LightDef;
    /** Базовая интенсивность до огибающей. */
    base: number;
    spec: PulseSpec;
    elapsed: number;
}

interface AmbientPulse {
    color: number;
    peak: number;
    spec: PulseSpec;
    elapsed: number;
}

interface DarkEntry {
    def: DarkSpotDef;
    sprite: Sprite;
}

export class Lighting extends Container {
    private readonly ambient: Sprite;
    private readonly vignette: Sprite;
    private readonly darkLayer: Container;
    private readonly pulseAmb: Sprite;
    private readonly free: Sprite[] = [];
    private readonly darkFree: Sprite[] = [];
    private readonly lights = new Map<string, LightEntry>();
    private readonly darks = new Map<string, DarkEntry>();
    private readonly pulses = new Map<string, PulseEntry>();
    private readonly glow: Texture;
    private readonly maxLights: number;
    private readonly maxDarkSpots: number;
    private pulseSeq = 0;

    private time = 0;
    private ambientFrom = 0xffffff;
    private ambientTo = 0xffffff;
    private fadeLeft = 0;
    private fadeTotal = 0;
    private vignetteFrom = 0;
    private vignetteTo = 0;
    private vignetteLeft = 0;
    private vignetteTotal = 0;
    private ambPulse: AmbientPulse | null = null;

    constructor(options: LightingOptions) {
        super();
        this.maxLights = options.maxLights ?? 16;
        this.maxDarkSpots = options.maxDarkSpots ?? 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);

        // Виньетка: Texture.WHITE без рендерера (multiply белого = не трогает).
        this.vignette = new Sprite(
            options.renderer ? makeVignetteTexture(options.renderer, options.width, options.height) : Texture.WHITE
        );
        this.vignette.width = options.width;
        this.vignette.height = options.height;
        this.vignette.blendMode = 'multiply';
        this.vignette.alpha = 0;
        this.addChild(this.vignette);

        // Тёмные пятна — под источниками: свет пробивает локальную тень.
        this.darkLayer = new Container();
        this.addChild(this.darkLayer);

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

        // Ambient-вспышка: Texture.WHITE, аддитивная, alpha по огибающей.
        this.pulseAmb = new Sprite(Texture.WHITE);
        this.pulseAmb.width = options.width;
        this.pulseAmb.height = options.height;
        this.pulseAmb.blendMode = 'add';
        this.pulseAmb.alpha = 0;
        this.addChild(this.pulseAmb);
    }

    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;
    }

    private makeDarkSprite(): Sprite {
        const s = new Sprite(this.glow);
        s.anchor.set(0.5);
        s.blendMode = 'multiply';
        s.tint = DARK_SPOT_TINT;
        s.visible = false;
        this.darkLayer.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;
    }

    // --- виньетка (край экрана темнеет: низкий hp, опасная зона) ---

    /** Виньетка 0..1 (0 — не трогает); fadeSec > 0 — плавный лерп. */
    setVignette(intensity: number, fadeSec = 0): void {
        this.vignetteFrom = this.currentVignette;
        this.vignetteTo = Math.max(0, Math.min(1, intensity));
        this.vignetteTotal = Math.max(0, fadeSec);
        this.vignetteLeft = this.vignetteTotal;
        if (this.vignetteTotal === 0) this.vignette.alpha = this.vignetteTo;
    }

    /** Целевая интенсивность виньетки. */
    get vignetteLevel(): number {
        return this.vignetteTo;
    }

    private get currentVignette(): number {
        return this.vignetteLeft > 0 ? this.vignetteFrom : this.vignette.alpha;
    }

    // --- источники: 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;
    }

    // --- импульсы: короткие вспышки в пуле источников и на весь экран ---

    /**
     * Импульс света в точке: занимает спрайт общего пула, снимается по концу
     * огибающей. Возвращает id (или пустую строку, если пул исчерпан).
     */
    pulseLight(args: {
        x: number;
        y: number;
        color: number;
        intensity?: number;
        radius?: number;
        spec: PulseSpec;
    }): string {
        const id = `pulse#${this.pulseSeq++}`;
        const sprite = this.free.pop();
        if (!sprite) return '';
        const base = args.intensity ?? 1;
        const def: LightDef = {
            id,
            x: args.x,
            y: args.y,
            color: args.color,
            intensity: base * pulseEnvelope(0, args.spec),
            radius: args.radius,
            flicker: 0,
            seed: 0
        };
        this.lights.set(id, { def, sprite });
        sprite.visible = true;
        this.pulses.set(id, { def, base, spec: args.spec, elapsed: 0 });
        return id;
    }

    /** Вспышка на весь экран (аддитивная); один слот — новая побеждает старую. */
    pulseAmbient(args: { color: number; peak: number; spec: PulseSpec }): void {
        this.pulseAmb.tint = args.color;
        this.ambPulse = { ...args, elapsed: 0 };
        this.pulseAmb.alpha = 0;
    }

    /** Текущая альфа ambient-вспышки (0..1) — для тестов и снапшота. */
    get pulseAmbientAlpha(): number {
        return this.pulseAmb.alpha;
    }

    // --- тёмные пятна: локальные ауры опасности (multiply) ---

    /** Добавить тёмное пятно или обновить его параметры. */
    upsertDarkSpot(def: DarkSpotDef): void {
        let entry = this.darks.get(def.id);
        if (!entry) {
            const sprite = this.darkFree.pop();
            if (!sprite) return; // пул исчерпан — лишние пятна игнорируются
            entry = { def: { ...def }, sprite };
            this.darks.set(def.id, entry);
            sprite.visible = true;
        }
        entry.def = { ...def };
        const s = entry.sprite;
        s.position.set(def.x, def.y);
        s.alpha = Math.max(0, Math.min(1, def.alpha));
        s.scale.set(def.radius / GLOW_BASE_PX);
    }

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

    hasDarkSpot(id: string): boolean {
        return this.darks.has(id);
    }

    // --- тик ---

    /** Тик: время, лерп 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;
        }
        if (this.vignetteLeft > 0) {
            this.vignetteLeft = Math.max(0, this.vignetteLeft - dt);
            const k = this.vignetteTotal > 0 ? 1 - this.vignetteLeft / this.vignetteTotal : 1;
            this.vignette.alpha = this.vignetteFrom + (this.vignetteTo - this.vignetteFrom) * k;
            if (this.vignetteLeft === 0) this.vignette.alpha = this.vignetteTo;
        }
        for (const [id, p] of [...this.pulses]) {
            p.elapsed += dt;
            if (p.elapsed >= pulseTotal(p.spec)) {
                this.removeLight(id);
                this.pulses.delete(id);
                continue;
            }
            p.def.intensity = p.base * pulseEnvelope(p.elapsed, p.spec);
        }
        for (const { def, sprite } of this.lights.values()) this.applyFrame(def, sprite);
        if (this.ambPulse) {
            this.ambPulse.elapsed += dt;
            if (this.ambPulse.elapsed >= pulseTotal(this.ambPulse.spec)) {
                this.ambPulse = null;
                this.pulseAmb.alpha = 0;
            } else {
                const e = pulseEnvelope(this.ambPulse.elapsed, this.ambPulse.spec);
                this.pulseAmb.alpha = Math.min(1, this.ambPulse.peak * e);
            }
        }
    }

    private applyFrame(def: LightDef, sprite: Sprite): void {
        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(options?: Parameters<Container['destroy']>[0]): void {
        this.pulses.clear();
        for (const id of [...this.lights.keys()]) this.removeLight(id);
        for (const s of this.free) s.destroy();
        this.free.length = 0;
        this.darks.clear();
        this.darkFree.length = 0;
        this.darkLayer.destroy({ children: true });
        this.ambient.destroy();
        this.vignette.destroy();
        this.pulseAmb.destroy();
        super.destroy(options);
    }
}