/**
 * Игровая обвязка освещения: статика из LightingDef области + лампа героя,
 * проекция в экранные px каждый тик (camera.toScreen), условные источники
 * по флагам. Runtime API (setAmbient/addLight) — база day/night и эффектов.
 */

import { Camera, Lighting, tileToWorld, unitsToPx, type Vec2 } from '@rpg/engine';
import type { AreaDef, LightSourceDef } from '../data/locations';

/** Параметры лампы героя (следует за героем в тёмных областях). */
export interface HeroLampDef {
    color: number;
    radius: number; // юниты
    intensity: number;
    flicker: number;
}

/** Кап активных источников — защита от опечатки в данных (tiles одного id). */
export const MAX_LIGHTS = 24;

export interface GameLightingDeps {
    area: AreaDef;
    /** Движковый адаптер (уже добавлен в renderer.lightRoot). */
    lighting: Lighting;
    /** Позиции тайлов по id (один проход по карте, см. LocationScene). */
    layerTilePos: Map<number, Vec2[]>;
    camera: Camera;
    /** Позиция героя в мировых юнитах. */
    heroPos: () => Vec2;
    hasFlag: (flag: string) => boolean;
    lamp?: HeroLampDef | null;
}

export interface LightingSnapshot {
    ambient: number;
    sources: { id: string; x: number; y: number; color: number; intensity: number }[];
}

/** Детерминированная фаза мерцания по id: снапшот воспроизводим между запусками. */
function seedOf(id: string): number {
    let h = 2166136261;
    for (let i = 0; i < id.length; i++) {
        h ^= id.charCodeAt(i);
        h = Math.imul(h, 16777619);
    }
    return ((h >>> 0) % 1000) / 1000;
}

export class GameLighting {
    private readonly deps: GameLightingDeps;
    /** Runtime-источники поверх статики области (эффекты, скрипты). */
    private readonly extra: LightSourceDef[] = [];
    /** Активные id на прошлом тике — чтобы снимать погасшие (условия по флагам). */
    private activeIds = new Set<string>();
    private destroyed = false;

    constructor(deps: GameLightingDeps) {
        this.deps = deps;
        if (deps.area.lighting?.ambient !== undefined) {
            deps.lighting.setAmbient(deps.area.lighting.ambient);
        }
    }

    // --- runtime поверх статики области (фундамент day/night и эффектов) ---

    setAmbient(color: number, fadeSec = 0): void {
        this.deps.lighting.setAmbient(color, fadeSec);
    }

    /** Динамически добавить источник (позиция обязательна — в тайлах). */
    addLight(def: LightSourceDef & { at: { x: number; y: number } }): void {
        this.extra.push(def);
    }

    removeLight(id: string): void {
        const i = this.extra.findIndex((d) => d.id === id);
        if (i >= 0) this.extra.splice(i, 1);
    }

    /** Тик: пересобрать активные источники и протолкнуть в движковый слой. */
    update(): void {
        if (this.destroyed) return;
        const next = new Set<string>();
        for (const def of this.activeDefs()) {
            for (const pos of this.sourcePositions(def)) {
                if (next.size >= MAX_LIGHTS) break;
                const id = def.tiles ? `${def.id}@${pos.x},${pos.y}` : def.id;
                next.add(id);
                const world = tileToWorld(pos.x + 0.5, pos.y + 0.5);
                const s = this.deps.camera.toScreen(world.x, world.y);
                this.deps.lighting.upsertLight({
                    id,
                    x: s.x,
                    y: s.y,
                    color: def.color ?? 0xf2b45a,
                    intensity: def.intensity ?? 0.9,
                    radius: unitsToPx(def.radius ?? 3),
                    flicker: def.flicker ?? 0,
                    seed: seedOf(id)
                });
            }
        }
        for (const id of this.activeIds) {
            if (!next.has(id)) this.deps.lighting.removeLight(id);
        }
        this.activeIds = next;
    }

    /** Состояние для агентного снапшота (интенсивность — пост-мерцание). */
    snapshot(): LightingSnapshot {
        const frames = this.deps.lighting.frames();
        return {
            ambient: this.deps.lighting.ambientColor,
            sources: frames.map((f) => ({
                id: f.id,
                x: Math.round(f.x * 10) / 10,
                y: Math.round(f.y * 10) / 10,
                color: f.tint,
                intensity: Math.round(f.alpha * 1000) / 1000
            }))
        };
    }

    destroy(): void {
        for (const id of this.activeIds) this.deps.lighting.removeLight(id);
        this.activeIds.clear();
        this.destroyed = true;
    }

    // --- внутреннее ---

    private activeDefs(): LightSourceDef[] {
        const out: LightSourceDef[] = [];
        const all = [...(this.deps.area.lighting?.sources ?? []), ...this.extra];
        for (const def of all) {
            if (def.whenFlag && !this.deps.hasFlag(def.whenFlag)) continue;
            if (def.notFlag && this.deps.hasFlag(def.notFlag)) continue;
            out.push(def);
        }
        const lamp = this.deps.lamp;
        if (lamp) {
            // Позиция героя уже в мировых юнитах: центры тайлов = tileToWorld(x+0.5),
            // поэтому отнимаем 0.5 — источник ляжет ровно на героя.
            const hero = this.deps.heroPos();
            out.push({
                id: 'lamp',
                at: { x: hero.x - 0.5, y: hero.y - 0.5 },
                radius: lamp.radius,
                color: lamp.color,
                intensity: lamp.intensity,
                flicker: lamp.flicker
            });
        }
        return out;
    }

    /** Позиции источника в тайлах: точка at или все тайлы заданных id. */
    private sourcePositions(def: LightSourceDef): { x: number; y: number }[] {
        if (def.at) return [def.at];
        const out: { x: number; y: number }[] = [];
        for (const id of def.tiles ?? []) out.push(...(this.deps.layerTilePos.get(id) ?? []));
        return out;
    }
}