/**
 * ModalManager — модальные сцены (затемнение + GUI поверх + блок ввода).
 *
 * Задача 04 из «1 - Неделя 4/ЗАДАЧИ РУБЛОКС/04_modal_cutscene.md».
 *
 * Типовой кейс: boss-fight intro / открытие лутбокса / диалог с NPC / получил
 * питомца. Скрипт зовёт `game.modal.open(opts)` → весь 3D-мир затемняется
 * (но HUD остаётся), управление блокируется, поверх показывается контент.
 *
 * Координирует:
 *   - DOM overlay (рендерится в KubikonEditor/KubikonPlayer)
 *   - PlayerController.setInputBlocked / setCameraFrozen
 *   - HighlightLayer Babylon (spotlight-объекты светятся)
 *   - GameRuntime.paused (опционально, через pauseSimulation)
 *   - AudioManager.duck (опционально, через muteWorld)
 *   - GuiManager (временные элементы создаются/удаляются с модалом)
 *
 * Не зависит от React — просто состояние и колбэки.
 *
 * Архитектура:
 *   _state = {
 *     id, opts,
 *     fadePhase: 'in'|'visible'|'out'|'closed',
 *     fadeStart: ms, fadeFrom: 0..1, fadeTo: 0..1,
 *     currentAlpha: 0..1,
 *     tempGuiIds: [...],         — id-шники созданных временных GUI-элементов
 *     spotlightScreens: [{x,y,r}], — позиции spotlight'ов в экранных координатах
 *   }
 *
 * Активен только ОДИН модал одновременно (Roblox-style). Повторный open
 * автоматически закрывает предыдущий (через close+open).
 */

let _seq = 1;

export class ModalManager {
    constructor() {
        /** @type {object|null} текущий модал, null если закрыт */
        this._state = null;
        /** @type {Function|null} вызывается когда меняется state — UI пере-рендерится */
        this._onChange = null;
        /** Babylon scene нужна для HighlightLayer и Vector3.Project */
        this._scene = null;
        /** PlayerController для блока ввода/freeze камеры */
        this._player = null;
        /** GuiManager для temp-элементов */
        this._gui = null;
        /** GameRuntime для pauseSimulation */
        this._runtime = null;
        /** AudioManager для muteWorld */
        this._audio = null;
        /** HighlightLayer Babylon — создаётся лениво при первом spotlight */
        this._highlight = null;
        /** Колбэки onClose — массив функций (modalId) => void */
        this._closeCallbacks = [];
        /** Прежний WASD-state и FOV — для восстановления */
        this._savedCameraState = null;
    }

    setOnChange(cb) { this._onChange = cb; }
    _notify() { if (this._onChange) try { this._onChange(this._state); } catch (e) {} }

    attachScene(scene) { this._scene = scene; }
    attachPlayer(player) { this._player = player; }
    attachGui(gui) { this._gui = gui; }
    attachRuntime(runtime) { this._runtime = runtime; }
    attachAudio(audio) { this._audio = audio; }

    /** Открыт ли сейчас модал. */
    isOpen() {
        return !!this._state && this._state.fadePhase !== 'closed';
    }

    /** Получить текущий state (для UI-overlay). */
    getState() { return this._state; }

    /**
     * Открыть модал. opts — см. doc по задаче 04.
     * Возвращает modalId (число).
     */
    open(opts) {
        opts = opts || {};
        console.log('[ModalManager] open called, opts:', opts);
        // Если уже открыт — мгновенно закрываем (без fadeOut, чтобы не плодить
        // одновременных модалов).
        if (this.isOpen()) this._instantClose();

        const id = ++_seq;
        const norm = {
            darken: Number.isFinite(opts.darken) ? Math.max(0, Math.min(1, opts.darken)) : 0.5,
            darkenColor: typeof opts.darkenColor === 'string' ? opts.darkenColor : '#000000',
            target: opts.target === 'screen' ? 'screen' : 'scene',
            blockInput: opts.blockInput !== false,  // по умолчанию true
            freezeCamera: !!opts.freezeCamera,
            cameraOverride: opts.cameraOverride || null,
            fadeIn: Number.isFinite(opts.fadeIn) ? Math.max(0, opts.fadeIn) : 0.3,
            fadeOut: Number.isFinite(opts.fadeOut) ? Math.max(0, opts.fadeOut) : 0.3,
            spotlights: Array.isArray(opts.spotlights) ? opts.spotlights.slice() : [],
            spotlightRadius: Number.isFinite(opts.spotlightRadius) ? opts.spotlightRadius : 120,
            spotlightSoftEdge: Number.isFinite(opts.spotlightSoftEdge) ? opts.spotlightSoftEdge : 40,
            pauseSimulation: !!opts.pauseSimulation,
            muteWorld: !!opts.muteWorld,
            content: opts.content || null,
        };

        this._state = {
            id,
            opts: norm,
            fadePhase: 'in',
            fadeStart: this._now(),
            fadeFrom: 0,
            fadeTo: norm.darken,
            currentAlpha: 0,
            tempGuiIds: [],
            spotlightScreens: [],
        };

        // 1) Block input
        if (norm.blockInput) {
            try { this._player?.setInputBlocked?.(true); } catch (e) {}
        }
        // 2) Freeze camera (сохраняем текущее состояние для восстановления)
        if (norm.freezeCamera) {
            try {
                this._savedCameraState = this._player?.captureCameraState?.() || null;
                this._player?.setCameraFrozen?.(true);
            } catch (e) {}
        }
        // 3) Camera override — переключение на focusOn
        if (norm.cameraOverride && this._scene) {
            this._applyCameraOverride(norm.cameraOverride);
        }
        // 4) Pause simulation
        if (norm.pauseSimulation && this._runtime) {
            try { this._runtime.paused = true; } catch (e) {}
        }
        // 5) Mute world audio
        if (norm.muteWorld && this._audio) {
            try { this._audio.duck?.(0.3); } catch (e) {}
        }
        // 6) Highlight spotlight-объектов в Babylon
        if (norm.spotlights.length && norm.target === 'scene' && this._scene) {
            this._applyHighlight(norm.spotlights);
        }
        // 7) content.elements — создать временные GUI-элементы
        if (norm.content?.elements && this._gui) {
            this._createTempGui(norm.content.elements);
        }

        this._notify();
        return id;
    }

    /** Закрыть модал. Если modalId передан и не совпадает — игнор. */
    close(modalId) {
        if (!this._state) return;
        if (modalId != null && this._state.id !== modalId) return;
        if (this._state.fadePhase === 'out' || this._state.fadePhase === 'closed') return;
        this._state.fadePhase = 'out';
        this._state.fadeStart = this._now();
        this._state.fadeFrom = this._state.currentAlpha;
        this._state.fadeTo = 0;
        this._notify();
    }

    /** Поменять параметры на лету. */
    update(modalId, patch) {
        if (!this._state) return;
        if (modalId != null && this._state.id !== modalId) return;
        if (!patch || typeof patch !== 'object') return;
        Object.assign(this._state.opts, patch);
        // Если поменяли darken — плавно tween-им currentAlpha к новому значению
        if (Number.isFinite(patch.darken) && this._state.fadePhase !== 'out') {
            this._state.fadeFrom = this._state.currentAlpha;
            this._state.fadeTo = patch.darken;
            this._state.fadeStart = this._now();
            this._state.fadePhase = 'in';
        }
        this._notify();
    }

    /** Подписаться на закрытие. fn получает modalId. */
    onClose(fn) {
        if (typeof fn === 'function') this._closeCallbacks.push(fn);
    }

    /** Обновление за кадр — двигает fade-phase и spotlight-screens. dt в секундах. */
    tick(dt) {
        if (!this._state) return;
        const st = this._state;
        if (!this._tickLogged) {
            this._tickLogged = true;
            console.log('[ModalManager] first tick, phase:', st.fadePhase, 'alpha:', st.currentAlpha);
        }

        // 1) Fade-tween
        if (st.fadePhase === 'in' || st.fadePhase === 'out') {
            const dur = st.fadePhase === 'in' ? st.opts.fadeIn : st.opts.fadeOut;
            const elapsed = (this._now() - st.fadeStart) / 1000;
            const t = dur > 0 ? Math.min(1, elapsed / dur) : 1;
            // ease-out cubic
            const k = 1 - Math.pow(1 - t, 3);
            st.currentAlpha = st.fadeFrom + (st.fadeTo - st.fadeFrom) * k;
            if (t >= 1) {
                if (st.fadePhase === 'in') {
                    st.fadePhase = 'visible';
                } else {
                    // close завершился — финальная уборка
                    st.fadePhase = 'closed';
                    this._teardown();
                }
            }
        }

        // 2) Обновить экранные координаты spotlight'ов (объекты могут двигаться)
        if (st.fadePhase !== 'closed' && st.opts.spotlights.length && st.opts.target === 'scene') {
            st.spotlightScreens = this._computeSpotlightScreens(st.opts.spotlights);
        }

        this._notify();
    }

    // ===== private =====

    _now() {
        return (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
    }

    _instantClose() {
        if (!this._state) return;
        this._teardown();
        this._state = null;
    }

    _teardown() {
        const st = this._state;
        if (!st) return;
        // 1) Unblock input
        if (st.opts.blockInput) {
            try { this._player?.setInputBlocked?.(false); } catch (e) {}
        }
        // 2) Unfreeze camera
        if (st.opts.freezeCamera) {
            try { this._player?.setCameraFrozen?.(false); } catch (e) {}
        }
        // 3) Camera reset — только если был cameraOverride
        if (st.opts.cameraOverride && this._savedCameraState) {
            try { this._player?.restoreCameraState?.(this._savedCameraState); } catch (e) {}
            this._savedCameraState = null;
        }
        // 4) Unpause
        if (st.opts.pauseSimulation && this._runtime) {
            try { this._runtime.paused = false; } catch (e) {}
        }
        // 5) Unmute
        if (st.opts.muteWorld && this._audio) {
            try { this._audio.unduck?.(); } catch (e) {}
        }
        // 6) Снять highlight
        if (this._highlight) {
            try { this._highlight.removeAllMeshes(); } catch (e) {}
        }
        // 7) Удалить temp GUI
        if (st.tempGuiIds.length && this._gui) {
            for (const id of st.tempGuiIds) {
                try { this._gui.remove(id); } catch (e) {}
            }
        }
        // 8) Колбэки onClose
        for (const cb of this._closeCallbacks) {
            try { cb(st.id); } catch (e) {}
        }
    }

    _applyCameraOverride(co) {
        // Используем существующий camera.focusOn механизм из BabylonScene/PlayerController
        try {
            const ref = co.target;
            const distance = Number.isFinite(co.distance) ? co.distance : 8;
            const height = Number.isFinite(co.height) ? co.height : 3;
            const fov = Number.isFinite(co.fov) ? co.fov : null;
            const duration = Number.isFinite(co.duration) ? co.duration : 0.5;
            if (this._player?.focusOnTarget) {
                this._player.focusOnTarget(ref, { distance, height, fov, duration });
            } else if (this._scene?._gameRuntime?._handleCommand) {
                // fallback через runtime — отправляем camera.focus
                this._scene._gameRuntime._handleCommand(null, 'camera.focus', {
                    ref, distance, height, fov, duration,
                });
            }
        } catch (e) {}
    }

    _applyHighlight(refs) {
        if (!this._scene) return;
        // Лениво создаём HighlightLayer
        if (!this._highlight) {
            try {
                const BABYLON = window.BABYLON || (typeof globalThis !== 'undefined' ? globalThis.BABYLON : null);
                if (BABYLON?.HighlightLayer && this._scene.scene) {
                    this._highlight = new BABYLON.HighlightLayer('modal-spotlight', this._scene.scene);
                    this._highlight.innerGlow = false;
                    this._highlight.outerGlow = true;
                }
            } catch (e) {}
        }
        if (!this._highlight) return;
        try { this._highlight.removeAllMeshes(); } catch (e) {}
        const BABYLON = window.BABYLON;
        const glowColor = (BABYLON && BABYLON.Color3)
            ? new BABYLON.Color3(1, 1, 0.6)
            : null;
        for (const ref of refs) {
            const meshes = this._resolveMeshes(ref);
            for (const m of meshes) {
                try {
                    if (glowColor) this._highlight.addMesh(m, glowColor);
                } catch (e) {}
            }
        }
    }

    /** Резолв ref → массив Babylon-мешей.
     *  ref может быть: строка-id, объект ref-обёртка ({kind, id}), либо сам Mesh. */
    _resolveMeshes(ref) {
        if (!ref || !this._scene) return [];
        // Уже Mesh-инстанс
        if (ref.getScene && typeof ref.getScene === 'function') return [ref];

        const sc = this._scene;
        const idStr = (typeof ref === 'string') ? ref : (ref.id || ref.refId || null);
        if (!idStr) return [];

        // Пробуем разные менеджеры
        const tryGetters = [
            () => sc.primitiveManager?.getMesh?.(idStr),
            () => sc.modelManager?.getInstanceMeshes?.(idStr),
            () => sc.scene?.getMeshByName?.(idStr),
            () => sc.npcManager?.getMeshes?.(idStr),
            () => sc.zombieManager?.getMeshes?.(idStr),
        ];
        for (const g of tryGetters) {
            try {
                const r = g();
                if (!r) continue;
                if (Array.isArray(r)) return r;
                return [r];
            } catch (e) {}
        }
        return [];
    }

    /** Проектируем 3D-позиции spotlight-refs в экранные координаты для CSS-mask. */
    _computeSpotlightScreens(refs) {
        if (!this._scene?.scene) return [];
        const out = [];
        const BABYLON = window.BABYLON;
        if (!BABYLON) return [];
        const engine = this._scene.scene.getEngine();
        const camera = this._scene.scene.activeCamera;
        if (!camera || !engine) return [];
        const w = engine.getRenderWidth();
        const h = engine.getRenderHeight();
        const matrix = camera.getTransformationMatrix();
        const viewport = camera.viewport.toGlobal(w, h);
        for (const ref of refs) {
            const meshes = this._resolveMeshes(ref);
            if (!meshes.length) continue;
            const m = meshes[0];
            try {
                const pos = m.getAbsolutePosition?.() || m.position;
                if (!pos) continue;
                // Center проектируем
                const proj = BABYLON.Vector3.Project(pos, BABYLON.Matrix.Identity(), matrix, viewport);
                // Если за камерой — скип (z вне 0..1)
                if (proj.z < 0 || proj.z > 1) continue;
                // Радиус — фиксированный из opts (можно потом масштабировать по distance/size)
                out.push({ x: proj.x, y: proj.y, r: this._state.opts.spotlightRadius });
            } catch (e) {}
        }
        return out;
    }

    _createTempGui(elements) {
        if (!Array.isArray(elements) || !this._gui) return;
        for (const el of elements) {
            if (!el || typeof el !== 'object') continue;
            const kind = el.kind || el.type || 'frame';
            const opts = { ...el };
            delete opts.kind;
            delete opts.type;
            try {
                const id = this._gui.create(kind, opts);
                if (id) this._state.tempGuiIds.push(id);
            } catch (e) {}
        }
    }
}
