/**
 * ZombieSpawnerManager — спавн зомби с лимитом и периодом.
 *
 * Визуал: вместо character-d показываем светящийся диск (цилиндр) с
 * пульсацией. Сама character-d модель скрывается.
 */
import { MeshBuilder, StandardMaterial, Color3 } from '@babylonjs/core';

const DEFAULTS = {
    // Снижено с 10 → 3: меньше живых зомби на спавнер = меньше нагрузки и
    // меньше шанс нарваться на невидимых из-за бага высоты на GLB-террейне.
    maxAlive: 3,
    spawnInterval: 10,
    radius: 6,
    zombieModelType: 'character-c',
};

let _spawnerIdSeq = 1;

export class ZombieSpawnerManager {
    constructor(scene3d, zombieManager) {
        this.scene3d = scene3d;
        this.zombieManager = zombieManager;
        /** @type {Map<number, object>} id → spawner state */
        this.spawners = new Map();
        this._lastTick = performance.now() / 1000;
        this._renderHook = null;

        // Подписываемся на смерть зомби — уменьшаем счётчик у спавнера
        this.zombieManager.setOnDeath((zombie) => this.onZombieDied(zombie));
    }

    start() {
        if (this._renderHook) return;
        this._renderHook = () => this._tick();
        this.scene3d.scene.registerBeforeRender(this._renderHook);
        this._lastTick = performance.now() / 1000;
    }

    stop() {
        if (this._renderHook) {
            this.scene3d.scene.unregisterBeforeRender(this._renderHook);
            this._renderHook = null;
        }
        // Удаляем всех зомби, которые были СПАВНУТЫ нашими спавнерами
        // (но не трогаем зомби размещённых вручную — они переживают Play→Stop).
        for (const sp of this.spawners.values()) {
            for (const instId of (sp.spawnedZombieIds || [])) {
                this.scene3d.modelManager?.removeInstance(instId);
            }
        }
        this.spawners.clear();
    }

    /** Колбэк когда зомби умер — обновляем счётчик спавнера. */
    onZombieDied(zombie) {
        const sid = zombie.spawnerId;
        if (sid == null) return;
        const sp = this.spawners.get(sid);
        if (!sp) return;
        sp.aliveZombies = Math.max(0, sp.aliveZombies - 1);
        // Также убираем из списка spawnedZombieIds (чтобы не пытаться удалить уже мёртвого)
        const idx = sp.spawnedZombieIds.indexOf(zombie.instanceId);
        if (idx >= 0) sp.spawnedZombieIds.splice(idx, 1);
    }

    /**
     * Зарегистрировать модель сцены как спавнер.
     * modelInstanceId — id из ModelManager.instances.
     */
    register(modelInstanceId, options = {}) {
        const data = this.scene3d.modelManager?.instances?.get(modelInstanceId);
        if (!data) return null;
        const id = _spawnerIdSeq++;
        const opts = { ...DEFAULTS, ...options };
        this.spawners.set(id, {
            id,
            instanceId: modelInstanceId,
            data,
            opts,
            spawnedZombieIds: [],   // ids зомби которых мы создали — для cleanup при stop
            aliveZombies: 0,
            lastSpawnTime: 0,
        });
        return id;
    }

    _tick() {
        const now = performance.now() / 1000;
        this._lastTick = now;
        // Глобальный лимит зомби на тач-устройствах (low-perf): если суммарно
        // живых зомби > MOBILE_TOTAL_LIMIT — не спавним больше. Это критично
        // для мобилки на «зомби-острове»: иначе FPS просаживается до 5-10.
        const lowPerf = !!this.scene3d?._lowPerfApplied;
        const MOBILE_TOTAL_LIMIT = 6;
        if (lowPerf) {
            const total = this.zombieManager?.zombies?.size || 0;
            if (total >= MOBILE_TOTAL_LIMIT) return;
        }
        for (const sp of this.spawners.values()) {
            // На мобиле каждый спавнер ограничиваем 3 зомби (вне зависимости от opts).
            const cap = lowPerf ? Math.min(3, sp.opts.maxAlive) : sp.opts.maxAlive;
            if (sp.aliveZombies >= cap) continue;
            if (now - sp.lastSpawnTime < sp.opts.spawnInterval) continue;
            sp.lastSpawnTime = now;
            this._spawnAt(sp);
        }
    }

    async _spawnAt(sp) {
        const { x, z } = sp.data;
        const a = Math.random() * Math.PI * 2;
        const r = Math.random() * sp.opts.radius;
        let sx = x + Math.cos(a) * r;
        let sz = z + Math.sin(a) * r;
        // Не спавнить зомби за пределами карты (worldHalf по X и Z, отступ 1.5м).
        const half = (this.scene3d?._worldHalf ?? 40) - 1.5;
        if (sx >  half) sx =  half;
        if (sx < -half) sx = -half;
        if (sz >  half) sz =  half;
        if (sz < -half) sz = -half;
        // Y берём из ПОВЕРХНОСТИ под (sx, sz), а не из y спавнера. Иначе зомби
        // спавнится на y=0 (под рельефом), его видимый меш под полом и
        // невидимый хитбокс наносит урон сквозь землю.
        const sy = this._surfaceHeightAt(sx, sz);
        const mm = this.scene3d.modelManager;
        if (!mm) return;
        const modelType = sp.opts.zombieModelType || 'zombie';
        const instId = await mm.addInstance(modelType, sx, sy, sz, Math.random() * Math.PI * 2);
        if (instId == null) return;
        // Помечаем data модели чтобы при сериализации НЕ сохранять этих зомби
        const data = mm.instances.get(instId);
        if (data) data._spawnedAtRuntime = true;
        const zombieParams = {
            spawnerId: sp.id,
            hp: sp.opts.zombieHp ?? 50,
            speed: sp.opts.zombieSpeed ?? 2.2,
            attackDamage: sp.opts.zombieDamage ?? 10,
        };
        this.zombieManager.registerExisting(instId, zombieParams);
        sp.aliveZombies += 1;
        sp.spawnedZombieIds.push(instId);
        // Новый меш в сцене — frozen active-meshes устарел
        try { this.scene3d?.setActiveMeshesDirty?.(); } catch (e) {}
    }

    /** Вернуть y-поверхности (top самого высокого блока) под (x, z).
     *  Без блоков — fallback 0. Та же логика что в ZombieManager и worker'е. */
    _surfaceHeightAt(x, z) {
        const bm = this.scene3d?.blockManager;
        if (!bm || !bm.blocks) return 0;
        const gx = Math.round(x);
        const gz = Math.round(z);
        for (let y = 20; y >= -3; y--) {
            const key = `${gx},${y},${gz}`;
            const mesh = bm.blocks.get(key);
            if (mesh && !mesh.metadata?.isWater) return y + 1;
        }
        return 0;
    }
}
