/**
 * Игровая обвязка освещения (перенос v1 Lighting.ts на v2-рендер): ночная
 * кривая ambient (setNight), точечные источники области по флагам и времени
 * суток (three.js PointLight через renderer.addPointLight) и лампа героя
 * в тёмных областях/ночью. Источники — diff по id каждый тик; мерцание —
 * детерминированный flickerFactor от времени цикла.
 */
import { dayNightAmbient, dayNightFactor, flickerFactor } from '@rpg/engine';
import type { PointLightHandle, VoxelRenderer } from '@rpg/engine';
import { UNIT, GROUND_Y, tileCenter } from './areas';
import type { LightingDef, LightSourceDef } from './data/lighting';

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

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

/** Порог ночи для ночных источников и лампы (≈ 19:00–07:00). */
const NIGHT_SOURCE_FACTOR = 0.5;

/** Считается ли область тёмной для лампы (та же эвристика, что v1). */
function isDarkAmbient(ambient: number | undefined): boolean {
    if (ambient === undefined) return false;
    const r = (ambient >> 16) & 0xff;
    const g = (ambient >> 8) & 0xff;
    const b = ambient & 0xff;
    return (r * 0.3 + g * 0.6 + b * 0.1) / 255 < 0.75;
}

/** Детерминированная фаза мерцания по 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 interface GameLightingDeps {
    renderer: VoxelRenderer;
    /** Освещение области (нет — нейтральный свет по умолчанию). */
    lighting: LightingDef | undefined;
    /** Позиции тайлов по id раскладки (источники `tiles`; один проход по карте). */
    layerTilePos: Map<number, { x: number; y: number }[]>;
    /** Позиция героя в вокселях (для лампы). */
    heroPos: () => { x: number; z: number };
    hasFlag: (flag: string) => boolean;
    /** Время суток в часах (GameClock.hours) — вход кривой дня. */
    timeHours: () => number;
    /** Время цикла в секундах (тик/60) — детерминизм мерцания в гейтах. */
    timeSeconds: () => number;
    /** Лампа героя (v1 HERO_LAMP); null — без лампы. */
    lamp?: HeroLampDef | null;
}

/** Снапшот для моста: фактор ночи + активные источники (пост-мерцание). */
export interface LightingSnapshot {
    nightFactor: number;
    sources: { id: string; x: number; y: number; intensity: number }[];
}

export class GameLighting {
    private readonly deps: GameLightingDeps;
    /** Активные источники прошлого тика (diff: снять погасшие по флагам). */
    private readonly handles = new Map<string, { handle: PointLightHandle; intensity: number }>();

    constructor(deps: GameLightingDeps) {
        this.deps = deps;
        this.applyDayAmbient();
        this.update(); // вход в область сразу при нужном свете (без мелькания дня)
    }

    /** Дневной базовый тон области: тёмный интерьер гасит hemisphere и солнце. */
    private applyDayAmbient(): void {
        const ambient = this.deps.lighting?.ambient;
        this.deps.renderer.setDayAmbient(
            ambient !== undefined && isDarkAmbient(ambient) ? ambient : 0x94949e,
        );
    }

    /** Текущий фактор ночи 0..1 (округлён — для снапшота). */
    get nightFactor(): number {
        return Math.round(dayNightFactor(this.deps.timeHours()) * 1000) / 1000;
    }

    /** Тик: ночная цель ambient + источники (diff по id, мерцание). */
    update(): void {
        const light = this.deps.lighting;
        const k = this.nightFactor;
        // цель ambient: микс день → золотой час → ночь (рассвет тем же тёплым)
        const tint =
            light?.ambient !== undefined && light?.nightAmbient !== undefined
                ? dayNightAmbient(light.ambient, light.nightAmbient, k, light.duskColor)
                : light?.ambient ?? 0x94949e;
        this.deps.renderer.setNight(k, tint);

        const next = new Set<string>();
        for (const def of this.activeDefs(k)) {
            for (const pos of this.positions(def)) {
                if (next.size >= MAX_LIGHTS) break;
                const key = def.tiles ? `${def.id}@${pos.x},${pos.y}` : def.id;
                next.add(key);
                const c = tileCenter(pos.x, pos.y);
                const flicker = def.flicker ?? 0;
                const intensity =
                    (def.intensity ?? 0.9) * flickerFactor(this.deps.timeSeconds(), seedOf(key), flicker, 0.9);
                const existing = this.handles.get(key);
                if (existing) {
                    existing.handle.setIntensity(intensity);
                    existing.handle.setPos([c.x, GROUND_Y + 2, c.z]); // лампа героя едет
                    existing.intensity = intensity;
                } else {
                    this.handles.set(key, {
                        handle: this.deps.renderer.addPointLight({
                            pos: [c.x, GROUND_Y + 2, c.z],
                            color: def.color ?? 0xf2b45a,
                            intensity,
                            radius: (def.radius ?? 3) * UNIT,
                        }),
                        intensity,
                    });
                }
            }
        }
        for (const [key, entry] of this.handles) {
            if (!next.has(key)) {
                entry.handle.dispose();
                this.handles.delete(key);
            }
        }
    }

    /** Состояние для агентного снапшота (интенсивность — пост-мерцание). */
    snapshot(): LightingSnapshot {
        return {
            nightFactor: this.nightFactor,
            sources: [...this.handles.entries()].map(([key, entry]) => {
                const [id, at] = key.split('@');
                const [tx, ty] = at ? at.split(',').map(Number) : [null, null];
                return {
                    id: id!,
                    x: tx ?? -1,
                    y: ty ?? -1,
                    intensity: Math.round(entry.intensity * 1000) / 1000,
                };
            }),
        };
    }

    destroy(): void {
        for (const h of this.handles.values()) h.handle.dispose();
        this.handles.clear();
    }

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

    /** Активные def области (фильтры флагов/ночи) + лампа героя. */
    private activeDefs(night: number): LightSourceDef[] {
        const out: LightSourceDef[] = [];
        for (const def of this.deps.lighting?.sources ?? []) {
            if (def.whenFlag && !this.deps.hasFlag(def.whenFlag)) continue;
            if (def.notFlag && this.deps.hasFlag(def.notFlag)) continue;
            if (def.whenNight && night < NIGHT_SOURCE_FACTOR) continue;
            out.push(def);
        }
        const lamp = this.deps.lamp;
        if (lamp && (isDarkAmbient(this.deps.lighting?.ambient) || night >= NIGHT_SOURCE_FACTOR)) {
            const hero = this.deps.heroPos();
            out.push({
                id: 'hero_lamp',
                // позиция в тайлах (tileCenter конвертирует обратно): центр героя
                at: { x: (hero.x - UNIT / 2) / UNIT, y: (hero.z - UNIT / 2) / UNIT },
                radius: lamp.radius,
                color: lamp.color,
                intensity: lamp.intensity,
                flicker: lamp.flicker,
            });
        }
        return out;
    }

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