/**
 * GdLevelManager — авто-обработка всех GD-объектов в Play-режиме.
 *
 * Находит примитивы по их kind ('gd_portal', 'gd_spike', 'gd_finish', 'gd_coin')
 * и при пересечении с игроком автоматически выполняет нужное действие:
 *  - gd_portal  → переключение гейммода (Cube/Ship/Ball/UFO/Wave/Robot)
 *  - gd_spike   → смерть и respawn (telepor на spawnPoint)
 *  - gd_finish  → событие 'gdFinish' (скрипт показывает win-screen)
 *  - gd_coin    → +1 монета (скрытие меша, событие 'gdCoinCollected')
 *
 * Цель: дать пользователю возможность ставить GD-объекты в редакторе из палитры,
 * и всё работает автоматически без скрипта-обвязки.
 *
 * События пробрасываются в скрипт через `gameRuntime.routeGlobalEvent`,
 * чтобы скрипт мог показывать UI и проигрывать SFX.
 */

import { getPrimitiveType } from './PrimitiveTypes';

const PORTAL_RADIUS = 1.0;
const SPIKE_RADIUS  = 0.7;
const FINISH_RADIUS = 1.5;
const COIN_RADIUS   = 1.4;
const PORTAL_COOLDOWN = 1.0;
const DEATH_COOLDOWN = 0.5;     // после смерти не убиваем 0.5с (даём respawn-окно)

export class GdLevelManager {
    constructor(scene3d) {
        this.scene3d = scene3d;
        this._observer = null;
        this._portals = [];   // [{ x, y, z, gdMode, primId }]
        this._spikes = [];    // [{ x, y, z, primId }]
        this._finishes = [];  // [{ x, y, z, primId }]
        this._coins = [];     // [{ x, y, z, primId, collected }]
        this._portalCooldown = 0;
        this._deathCooldown = 0;
        this._winSent = false;
        this._onPortalEnter = null;
        this._onDeath = null;
        this._onFinish = null;
        this._onCoinCollected = null;
        this._lastPmCount = -1;   // для авто-refresh когда primitiveManager наполнится
    }

    setOnPortalEnter(cb)    { this._onPortalEnter = cb; }
    setOnDeath(cb)          { this._onDeath = cb; }
    setOnFinish(cb)         { this._onFinish = cb; }
    setOnCoinCollected(cb)  { this._onCoinCollected = cb; }

    start() {
        this._refreshObjectList();
        const scene = this.scene3d?.scene;
        if (!scene) return;
        this._winSent = false;
        this._deathCooldown = 0;
        this._portalCooldown = 0;
        this._observer = scene.onBeforeRenderObservable.add(() => this._tick());
    }

    stop() {
        const scene = this.scene3d?.scene;
        if (scene && this._observer) {
            scene.onBeforeRenderObservable.remove(this._observer);
        }
        this._observer = null;
        const p = this.scene3d?.player;
        if (p) {
            p._shipMode = false;
            p._ufoMode = false;
            p._waveMode = false;
            p._robotMode = false;
            p._gravityDir = 1;
            p._robotBoostLeft = 0;
        }
    }

    /** Собрать все GD-объекты из текущего набора примитивов. */
    _refreshObjectList() {
        this._portals = [];
        this._spikes = [];
        this._finishes = [];
        this._coins = [];
        const pm = this.scene3d?.primitiveManager;
        if (!pm) return;
        for (const data of pm.instances.values()) {
            const typeDef = getPrimitiveType(data.type);
            if (!typeDef) continue;
            const base = { x: data.x, y: data.y, z: data.z, primId: data.id };
            if (typeDef.kind === 'gd_portal') {
                this._portals.push({ ...base, gdMode: typeDef.gdMode });
            } else if (typeDef.kind === 'gd_spike') {
                this._spikes.push(base);
            } else if (typeDef.kind === 'gd_finish') {
                this._finishes.push(base);
            } else if (typeDef.kind === 'gd_coin') {
                this._coins.push({ ...base, collected: false });
            }
        }
    }

    /** Очистка состояния (вызывается при respawn). Монеты сохраняются — собранные собраны. */
    resetForRespawn() {
        this._deathCooldown = DEATH_COOLDOWN;
        this._portalCooldown = 0;
        // Режим возвращаем к Cube
        const p = this.scene3d?.player;
        if (p) {
            p._shipMode = false;
            p._ufoMode = false;
            p._waveMode = false;
            p._robotMode = false;
            p._gravityDir = 1;
            p._robotBoostLeft = 0;
            p._ballHintActive = false;
        }
    }

    /** Сбросить ВСЁ (включая монеты) — для рестарта уровня. */
    resetForNewRun() {
        this.resetForRespawn();
        this._winSent = false;
        for (const coin of this._coins) {
            coin.collected = false;
            try {
                const data = this.scene3d?.primitiveManager?.instances?.get(coin.primId);
                if (data?.mesh) data.mesh.setEnabled(true);
            } catch (e) {}
        }
    }

    _currentMode() {
        const p = this.scene3d?.player;
        if (!p) return 'cube';
        if (p._shipMode) return 'ship';
        if (p._ufoMode) return 'ufo';
        if (p._waveMode) return 'wave';
        if (p._robotMode) return 'robot';
        if ((p._gravityDir || 1) < 0 || p._ballHintActive) return 'ball';
        return 'cube';
    }

    _setMode(gdMode) {
        const p = this.scene3d?.player;
        if (!p) return;
        console.log('[GdLevelManager] _setMode →', gdMode);
        p._shipMode = false;
        p._ufoMode = false;
        p._waveMode = false;
        p._robotMode = false;
        p._ballHintActive = (gdMode === 'ball');
        if (gdMode === 'ship')  p._shipMode  = true;
        if (gdMode === 'ufo')   p._ufoMode   = true;
        if (gdMode === 'wave')  p._waveMode  = true;
        if (gdMode === 'robot') p._robotMode = true;
        if (gdMode === 'cube') {
            p._gravityDir = 1;
            p._robotBoostLeft = 0;
        }
    }

    _tick() {
        const p = this.scene3d?.player;
        if (!p) return;
        const dt = (this.scene3d.scene.getEngine().getDeltaTime() / 1000) || 0.016;
        this._portalCooldown = Math.max(0, this._portalCooldown - dt);
        this._deathCooldown = Math.max(0, this._deathCooldown - dt);

        // Автообновление: primitives загружаются асинхронно после start().
        // Считаем кол-во в primitiveManager и при изменении — refresh всех списков.
        const pmCount = this.scene3d?.primitiveManager?.instances?.size || 0;
        if (pmCount !== this._lastPmCount) {
            this._lastPmCount = pmCount;
            this._refreshObjectList();
            console.log(`[GdLevelManager] refresh: pm=${pmCount}, portals=${this._portals.length}, spikes=${this._spikes.length}, finishes=${this._finishes.length}, coins=${this._coins.length}`);
        }

        // PlayerController хранит позицию в _pos (не в .position — там нет геттера).
        const ppos = p._pos || p.position;
        const px = ppos?.x ?? 0;
        const py = ppos?.y ?? 0;

        // === Порталы (приоритет: они перекрывают всё) ===
        // Триггер — пересечение по X на любой высоте 0..12 (как у финиша).
        // Чтобы в ship-режиме игрок не пролетел над cube-порталом сверху.
        if (this._portalCooldown <= 0) {
            for (const portal of this._portals) {
                const dx = px - portal.x;
                if (Math.abs(dx) < PORTAL_RADIUS * 1.5 && py >= 0 && py <= 12) {
                    const cur = this._currentMode();
                    if (cur === portal.gdMode) continue;
                    const prevMode = cur;
                    this._setMode(portal.gdMode);
                    this._portalCooldown = PORTAL_COOLDOWN;
                    if (this._onPortalEnter) {
                        try { this._onPortalEnter(portal.gdMode, prevMode); } catch (e) {}
                    }
                    break;
                }
            }
        }

        // === Финиш (приоритет 2: победа важнее смерти) ===
        // Финиш = вертикальный столб (cylinder neon sy=8). Триггер — пересечение по X
        // на любой допустимой высоте (от пола y=0 до y=8). Это покрывает все режимы
        // (cube/ship/ball/ufo/wave/robot — игрок может быть на разной высоте).
        if (!this._winSent) {
            for (const fin of this._finishes) {
                const dx = px - fin.x;
                // По X — узко (1.5м), по Y — широко (от 0 до 10)
                if (Math.abs(dx) < FINISH_RADIUS && py >= -1 && py <= 10) {
                    this._winSent = true;
                    if (this._onFinish) {
                        try { this._onFinish({ x: fin.x, y: fin.y }); } catch (e) {}
                    }
                    return;
                }
            }
        }

        // === Шипы (смерть) ===
        if (this._deathCooldown <= 0) {
            for (const spike of this._spikes) {
                const dx = px - spike.x;
                const dy = py - spike.y;
                if (dx*dx + dy*dy < SPIKE_RADIUS * SPIKE_RADIUS) {
                    this.resetForRespawn();
                    if (this._onDeath) {
                        try { this._onDeath({ x: spike.x, y: spike.y, cause: 'spike' }); } catch (e) {}
                    }
                    return;
                }
            }
            // Также «упал в бездну» — y < -2
            if (py < -2) {
                this.resetForRespawn();
                if (this._onDeath) {
                    try { this._onDeath({ x: px, y: py, cause: 'fall' }); } catch (e) {}
                }
                return;
            }
        }

        // === Монеты (сбор) ===
        for (const coin of this._coins) {
            if (coin.collected) continue;
            const dx = px - coin.x;
            const dy = py - coin.y;
            if (dx*dx + dy*dy < COIN_RADIUS * COIN_RADIUS) {
                coin.collected = true;
                try {
                    const data = this.scene3d?.primitiveManager?.instances?.get(coin.primId);
                    if (data?.mesh) data.mesh.setEnabled(false);
                } catch (e) {}
                if (this._onCoinCollected) {
                    try { this._onCoinCollected({ primId: coin.primId, index: this._coins.indexOf(coin) }); } catch (e) {}
                }
            }
        }
    }

    /** Сколько собрано монет / всего. Для HUD. */
    getCoinsStats() {
        const total = this._coins.length;
        const collected = this._coins.filter(c => c.collected).length;
        return { collected, total };
    }
}
