/**
 * SelectionManager — управление выделением объектов в сцене.
 *
 * Поддерживает два типа выделения:
 *   - блок: метаданные { isBlock: true, blockTypeId, gridX, gridY, gridZ }
 *   - модель: метаданные { isModel: true, instanceId } (могут быть на любом
 *     дочернем меше — модели Kenney состоят из иерархии mesh-узлов)
 *
 * Подсветка: enableEdgesRendering на основном меше (для блока) или на всех
 * меш-чилдренах (для модели). Edges-рёбра рисуются зелёным цветом и поверх
 * объекта.
 *
 * Public API:
 *   selectBlockAt(x, y, z)            — выделить блок по координатам
 *   selectModelByInstanceId(id)        — выделить модель
 *   selectByMesh(mesh)                 — универсально (после raycast)
 *   clear()                            — снять выделение
 *   getSelection()                     — { type, ... } | null
 *   setOnSelectionChange(cb)           — подписка для UI
 *   focusOnSelection(camera)           — двинуть редактор-камеру к объекту
 */
import { Color3, Color4, MeshBuilder, Vector3, StandardMaterial } from '@babylonjs/core';
import { getModelType } from './ModelTypes';

const EDGE_COLOR = new Color4(0.4, 1.0, 0.4, 1.0);   // ярко-зелёный
const EDGE_WIDTH = 4;

export class SelectionManager {
    constructor(scene, blockManager, modelManager) {
        this.scene = scene;
        this.blockManager = blockManager;
        this.modelManager = modelManager;
        this.primitiveManager = null;
        // Этап 5: пользовательские воксельные модели (UserModelManager).
        this.userModelManager = null;
        this._selection = null;
        // Multi-select: массив {kind: 'block'|'model'|'primitive'|'userModel', ref}
        this._multi = [];
        this._highlightedMeshes = [];
        this._onChange = null;
        this._snapStep = 0;
        this._scene3d = null;
    }

    setPrimitiveManager(pm) {
        this.primitiveManager = pm;
    }

    setUserModelManager(um) {
        this.userModelManager = um;
    }

    /** Установить ссылку на BabylonScene (для доступа к spawn-маркеру и т.п.). */
    setScene3D(scene3d) {
        this._scene3d = scene3d;
    }

    /** Установить snap для координат моделей. Влияет на moveSelectedModel. */
    setSnapStep(step) {
        this._snapStep = step;
    }

    _snap(value) {
        if (!this._snapStep || this._snapStep <= 0) return value;
        return Math.round(value / this._snapStep) * this._snapStep;
    }

    setOnSelectionChange(cb) {
        this._onChange = cb;
    }

    /**
     * Выделить по mesh (полученному из raycast).
     * Различает blocks и models через metadata.
     */
    selectByMesh(mesh) {
        if (!mesh) return this.clear();
        const m = mesh.metadata;
        // Если объект лежит в папке — клик по СЦЕНЕ выделяет ВСЮ папку целиком
        // (отдельную часть можно выбрать через дерево). folderId берём из data.
        const folderIdOf = (kind, id) => {
            let d = null;
            if (kind === 'model') d = this.modelManager?.instances?.get(id);
            else if (kind === 'userModel') d = this.userModelManager?.instances?.get(id);
            else if (kind === 'primitive') d = this.primitiveManager?.instances?.get(id);
            return d ? (d.folderId ?? null) : null;
        };
        if (m?.isBlock) {
            return this.selectBlockAt(m.gridX, m.gridY, m.gridZ);
        }
        if (m?.isModel) {
            const md = this.modelManager?.instances?.get(m.instanceId);
            if (md && md.locked) return this.clear();
            const fid = folderIdOf('model', m.instanceId);
            if (fid != null) return this.selectFolder(fid);
            return this.selectModelByInstanceId(m.instanceId);
        }
        if (m?.isUserModel) {
            const ud = this.userModelManager?.instances?.get(m.instanceId);
            if (ud && ud.locked) return this.clear();
            const fid = folderIdOf('userModel', m.instanceId);
            if (fid != null) return this.selectFolder(fid);
            return this.selectUserModelByInstanceId(m.instanceId);
        }
        if (m?.isPrimitive) {
            const pd = this.primitiveManager?.instances?.get(m.primitiveId);
            if (pd && pd.locked) return this.clear();
            const fid = folderIdOf('primitive', m.primitiveId);
            if (fid != null) return this.selectFolder(fid);
            return this.selectPrimitiveById(m.primitiveId);
        }
        if (m?.isSpawn) {
            return this.selectSpawn();
        }
        return this.clear();
    }

    selectPrimitiveById(id) {
        if (!this.primitiveManager) return this.clear();
        const data = this.primitiveManager.instances.get(id);
        if (!data) return this.clear();

        this._removeHighlight();
        this._highlightMesh(data.mesh);

        this._selection = {
            type: 'primitive',
            id,
            primitiveType: data.type,
            x: data.x, y: data.y, z: data.z,
            sx: data.sx, sy: data.sy, sz: data.sz,
            // Вращение — нужно для корректного копирования/дублирования
            // (без него копия теряла поворот, баг 2026-06-04).
            rotationX: data.rotationX || 0,
            rotationY: data.rotationY || 0,
            rotationZ: data.rotationZ || 0,
            color: data.color,
            material: data.material,
            canCollide: data.canCollide,
            visible: data.visible,
            anchored: data.anchored,
            mass: data.mass ?? 1,
            // Параметры лампы / эмиттера / текстуры — чтобы инспектор
            // показывал текущие значения при перевыборе объекта.
            brightness: data.brightness,
            range: data.range,
            effect: data.effect,
            textureAsset: data.textureAsset || null,
            studDensity: data.studDensity || 1,
            label: data.label || null,            // подпись над объектом (задача 10)
            locked: !!data.locked,
            mesh: data.mesh,
            rootMesh: data.mesh,
        };
        this._notifyChange();
    }

    /** Сменить позицию примитива. */
    moveSelectedPrimitive(newX, newY, newZ) {
        if (this._selection?.type !== 'primitive' || !this.primitiveManager) return;
        newX = this._snap(newX);
        newY = this._snap(newY);
        newZ = this._snap(newZ);
        this.primitiveManager.updateInstance(this._selection.id, { x: newX, y: newY, z: newZ });
        this._selection.x = newX;
        this._selection.y = newY;
        this._selection.z = newZ;
        this._notifyChange();
    }

    /** Изменить размер примитива. */
    resizeSelectedPrimitive(sx, sy, sz) {
        if (this._selection?.type !== 'primitive') return;
        this.primitiveManager.updateInstance(this._selection.id, { sx, sy, sz });
        this._selection.sx = sx;
        this._selection.sy = sy;
        this._selection.sz = sz;
        // mesh пересоздан — обновляем reference
        const data = this.primitiveManager.instances.get(this._selection.id);
        if (data) {
            this._removeHighlight();
            this._highlightMesh(data.mesh);
            this._selection.mesh = data.mesh;
            this._selection.rootMesh = data.mesh;
        }
        this._notifyChange();
    }

    /** Изменить свойства примитива (color/material/canCollide/visible). */
    setSelectedPrimitiveProps(patch) {
        if (this._selection?.type !== 'primitive') return;
        this.primitiveManager.updateInstance(this._selection.id, patch);
        // Применяем в локальное selection
        Object.assign(this._selection, patch);
        this._notifyChange();
    }

    /** Выделить точку спавна — показать гизмо на маркере. */
    selectSpawn() {
        if (!this._scene3d || !this._scene3d._spawnMarker) return;
        this._removeHighlight();
        // Подсвечиваем оба меша маркера
        for (const m of this._scene3d._spawnMarkerMeshes || []) {
            this._highlightMesh(m);
        }
        const sp = this._scene3d._spawnPoint;
        this._selection = {
            type: 'spawn',
            x: sp.x, y: sp.y, z: sp.z,
            rootMesh: this._scene3d._spawnMarker,
        };
        this._notifyChange();
    }

    /**
     * Выделить ПАПКУ целиком: подсветить все объекты внутри + поставить
     * selection.type='folder'. Групповой gizmo привязывается в BabylonScene.
     */
    selectFolder(folderId) {
        const fm = this._scene3d?.folderManager;
        if (!fm) return;
        const g = fm.getFolderObjects(folderId);
        this._removeHighlight();
        this._multi = [];
        for (const m of g.meshes) this._highlightMesh(m);
        this._selection = {
            type: 'folder',
            folderId,
            center: g.center,
            count: g.count,
            meshes: g.meshes,
        };
        this._notifyChange();
        // Поставить групповой gizmo на пивот папки.
        this._scene3d?._attachFolderGizmo?.(folderId, g.center);
    }

    /** Выделить псевдо-объект «Пол» — настройки grid'а пола. */
    selectFloor() {
        if (!this._scene3d) return;
        this._removeHighlight();
        this._selection = {
            type: 'floor',
            worldSize: this._scene3d.getWorldSize?.() ?? 80,
            enabled: this._scene3d.isFloorEnabled?.() !== false,
        };
        this._notifyChange();
    }

    /** Выделить пользовательский скрипт по id (этап 2.1 — только информационно). */
    selectScript(scriptId) {
        if (!this._scene3d) return;
        const all = this._scene3d.getScripts?.() || [];
        const s = all.find(x => x.id === scriptId);
        if (!s) return this.clear();
        this._removeHighlight();
        this._selection = {
            type: 'script',
            scriptId: s.id,
            code: s.code,
            target: s.target || null,
        };
        this._notifyChange();
    }

    /** Выделить псевдо-объект «Освещение» — меняем настройки света через Inspector. */
    selectLighting() {
        if (!this._scene3d) return;
        this._removeHighlight();
        const sun = this._scene3d._sunLight;
        const hemi = this._scene3d._hemiLight;
        const env = this._scene3d.environment;
        this._selection = {
            type: 'lighting',
            envPreset: env?.preset || 'day',
            dayDurationMin: env?.dayDurationMin ?? 5,
            nightDurationMin: env?.nightDurationMin ?? 3,
            sunIntensity: sun?.intensity ?? 0.8,
            hemiIntensity: hemi?.intensity ?? 0.65,
            fogEnabled: env?.fogEnabled ?? false,
            fogDensity: env?.fogDensity ?? 0.005,
            fogColor: env ? `#${[env.fogColor?.[0] ?? 0.7, env.fogColor?.[1] ?? 0.8, env.fogColor?.[2] ?? 0.9].map(c => Math.round(Math.max(0, Math.min(1, c)) * 255).toString(16).padStart(2, '0')).join('')}` : '#b0c8e6',
            shadowQuality: this._scene3d.getShadowQuality?.() || 'soft',
            ssaoEnabled: this._scene3d.getSsaoEnabled?.() || false,
            // Новые: глобальный ambient + image processing
            sceneAmbient: this._scene3d._sceneAmbient ?? 0.3,
            exposure: this._scene3d._exposure ?? 1.0,
            contrast: this._scene3d._contrast ?? 1.0,
            saturation: this._scene3d._saturation ?? 1.0,
        };
        this._notifyChange();
    }

    /** Выделить псевдо-объект «Звук» — амбиент + музыка. */
    selectSound() {
        if (!this._scene3d) return;
        this._removeHighlight();
        const audio = this._scene3d.audioManager;
        // Импортировать пресеты сложно из selectionManager — подгрузим через scene3d
        const audioState = audio?.serialize?.() || {};
        const presets = this._scene3d.getAudioPresets?.() || { ambient: [], music: [] };
        this._selection = {
            type: 'sound',
            ambientId: audioState.ambientId || 'none',
            musicId: audioState.musicId || 'none',
            ambientPresets: presets.ambient,
            musicPresets: presets.music,
        };
        this._notifyChange();
    }

    /** Выделить псевдо-объект «Скин игрока» — только список моделей. */
    selectPlayer() {
        if (!this._scene3d) return;
        this._removeHighlight();
        const playerOptions = this._scene3d.getPlayerOptions?.() || [];
        this._selection = {
            type: 'player',
            playerModelType: this._scene3d.getPlayerModelType?.() || 'character-a',
            playerOptions,
        };
        this._notifyChange();
    }

    /** Выделить GUI-элемент по id. Возвращает true если найден. */
    selectGui(id) {
        if (!this._scene3d?.guiManager) return false;
        const el = this._scene3d.guiManager.get(id);
        if (!el) return false;
        this._removeHighlight();
        this._selection = {
            type: 'gui',
            id: el.id,
            guiType: el.type,
            data: { ...el },
        };
        this._notifyChange();
        return true;
    }

    /** Выделить псевдо-объект «Свойства игрока» — прыжок, прицел и т.п. */
    selectPlayerProps() {
        if (!this._scene3d) return;
        this._removeHighlight();
        this._selection = {
            type: 'playerProps',
            jumpPower: this._scene3d.getPlayerJumpPower?.() ?? 1,
            crosshair: this._scene3d.getCrosshair?.() ?? 'none',
        };
        this._notifyChange();
    }

    /** Сменить позицию точки спавна. */
    moveSelectedSpawn(newX, newY, newZ) {
        if (this._selection?.type !== 'spawn' || !this._scene3d) return;
        // Snap
        newX = this._snap(newX);
        newY = this._snap(newY);
        newZ = this._snap(newZ);
        this._scene3d._spawnPoint = { x: newX, y: newY, z: newZ };
        this._scene3d._updateSpawnMarker();
        this._selection.x = newX;
        this._selection.y = newY;
        this._selection.z = newZ;
        this._notifyChange();
        // Триггерим dirty-tracking
        if (this._scene3d._onSceneChange) this._scene3d._onSceneChange();
        this._scene3d.history?.markChange();
    }

    selectBlockAt(x, y, z) {
        if (!this.blockManager) return;
        const key = `${x},${y},${z}`;
        const mesh = this.blockManager.blocks.get(key);
        if (!mesh) return this.clear();

        this._removeHighlight();
        // Для proxy-блоков (thin-instances) подсветка через отдельный overlay-mesh,
        // т.к. у proxy нет enableEdgesRendering.
        if (mesh._isBlockProxy || mesh.metadata?._liquidProxy) {
            this._showBlockHighlight(x, y, z);
        } else {
            this._highlightMesh(mesh);
        }

        this._selection = {
            type: 'block',
            gridX: x, gridY: y, gridZ: z,
            blockTypeId: mesh.metadata.blockTypeId,
            anchored: mesh.metadata.anchored !== false,
            canCollide: mesh.metadata.canCollide !== false,
            visible: mesh.metadata.visible !== false,
            mass: mesh.metadata.mass ?? 1,
            mesh,
        };
        this._notifyChange();
    }

    /** Показать рамку поверх блока в (x, y, z). Использует переиспользуемый overlay-mesh. */
    _showBlockHighlight(x, y, z) {
        if (!this._blockHighlightMesh) {
            const m = MeshBuilder.CreateBox('blockHighlight', { size: 1.02 }, this.scene);
            m.isPickable = false;
            const mat = new StandardMaterial('blockHighlightMat', this.scene);
            mat.diffuseColor = new Color3(0, 0, 0);
            mat.alpha = 0.001; // почти невидим, но edges рисуются
            m.material = mat;
            m.enableEdgesRendering();
            m.edgesWidth = EDGE_WIDTH;
            m.edgesColor = EDGE_COLOR;
            this._blockHighlightMesh = m;
        }
        this._blockHighlightMesh.position = new Vector3(x, y + 0.5, z);
        this._blockHighlightMesh.setEnabled(true);
    }

    _hideBlockHighlight() {
        if (this._blockHighlightMesh) this._blockHighlightMesh.setEnabled(false);
    }

    selectModelByInstanceId(instanceId) {
        if (!this.modelManager) return;
        const data = this.modelManager.instances.get(instanceId);
        if (!data) return this.clear();

        this._removeHighlight();
        // Подсвечиваем все mesh-чилдрены модели
        for (const m of data.clonedMeshes) {
            this._highlightMesh(m);
        }

        this._selection = {
            type: 'model',
            instanceId,
            modelTypeId: data.modelTypeId,
            x: data.x, y: data.y, z: data.z,
            rotationY: data.rotationY,
            scale: data.rootMesh?.scaling?.x ?? 1,
            anchored: data.anchored,
            canCollide: data.canCollide,
            visible: data.visible,
            mass: data.mass ?? 1,
            opacity: typeof data.opacity === 'number' ? data.opacity : 1,
            tint: data.tint || null,
            rootMesh: data.rootMesh,
            // Gameplay-метаданные модели (для Inspector враги/спавнеры).
            // Если у инстанса нет gameplay (например в старом проекте) —
            // дотягиваем из ModelTypes по modelTypeId.
            gameplay: data.gameplay
                || getModelType(data.modelTypeId)?.gameplay
                || null,
            gameplayParams: data.gameplayParams || null,
        };
        this._notifyChange();
    }

    /** Выделить пользовательскую voxel-модель (Этап 5 редактора моделей). */
    selectUserModelByInstanceId(instanceId) {
        if (!this.userModelManager) return this.clear();
        const data = this.userModelManager.instances.get(instanceId);
        if (!data) return this.clear();

        this._removeHighlight();
        // Подсвечиваем все меши инстанса (по одному per материал).
        for (const m of data.meshes) {
            this._highlightMesh(m);
        }

        this._selection = {
            type: 'userModel',
            instanceId,
            // Префикс 'user:<numericId>' — ID который сохраняется в project_data
            userModelTypeId: data.userModelTypeId,
            userModelId: data.userModelId,
            x: data.x, y: data.y, z: data.z,
            rotationY: data.rotationY,
            scale: data.rootNode?.scaling?.x ?? 1,
            // По умолчанию воксельные модели — anchored true (декорация),
            // но если потом понадобится менять — добавим в data.
            anchored: data.anchored !== false,
            canCollide: data.canCollide !== false,
            visible: data.visible !== false,
            mass: data.mass ?? 1,
            rootMesh: data.rootNode,
        };
        this._notifyChange();
    }

    /** Сменить позицию выделенного блока — переустанавливаем блок (block-level операция). */
    moveSelectedBlock(newX, newY, newZ) {
        if (this._selection?.type !== 'block') return;
        const s = this._selection;
        const typeId = s.blockTypeId;
        // Удаляем старый, создаём новый. Сохраняем выделение на новой позиции.
        this.blockManager.removeBlock(s.gridX, s.gridY, s.gridZ);
        const placed = this.blockManager.addBlock(newX, newY, newZ, typeId);
        if (placed) {
            this.selectBlockAt(newX, newY, newZ);
        } else {
            this.clear();
        }
    }

    /** Сменить позицию модели (без удаления-создания — двигаем rootMesh). */
    moveSelectedModel(newX, newY, newZ) {
        if (this._selection?.type !== 'model') return;
        const data = this.modelManager.instances.get(this._selection.instanceId);
        if (!data) return;
        // Snap к шагу если включён
        newX = this._snap(newX);
        newY = this._snap(newY);
        newZ = this._snap(newZ);
        data.x = newX; data.y = newY; data.z = newZ;
        if (data.rootMesh) {
            data.rootMesh.position.x = newX;
            data.rootMesh.position.y = newY;
            data.rootMesh.position.z = newZ;
        }
        this._selection.x = newX;
        this._selection.y = newY;
        this._selection.z = newZ;
        this._notifyChange();
        this.modelManager._notifyChange?.();
    }

    /** Поменять угол поворота модели по Y (радианы). */
    rotateSelectedModel(angleRad) {
        if (this._selection?.type !== 'model') return;
        const data = this.modelManager.instances.get(this._selection.instanceId);
        if (!data) return;
        data.rotationY = angleRad;
        if (data.rootMesh) {
            data.rootMesh.rotation.y = angleRad;
        }
        this._selection.rotationY = angleRad;
        this._notifyChange();
        this.modelManager._notifyChange?.();
    }

    /** Поменять масштаб модели (равномерный). */
    scaleSelectedModel(scale) {
        if (this._selection?.type !== 'model') return;
        const data = this.modelManager.instances.get(this._selection.instanceId);
        if (!data) return;
        if (data.rootMesh) {
            data.rootMesh.scaling.x = scale;
            data.rootMesh.scaling.y = scale;
            data.rootMesh.scaling.z = scale;
        }
        this._selection.scale = scale;
        this._notifyChange();
        this.modelManager._notifyChange?.();
    }

    /** Переместить выделенную user-модель. */
    moveSelectedUserModel(newX, newY, newZ) {
        if (this._selection?.type !== 'userModel') return;
        const data = this.userModelManager.instances.get(this._selection.instanceId);
        if (!data) return;
        newX = this._snap(newX);
        newY = this._snap(newY);
        newZ = this._snap(newZ);
        data.x = newX; data.y = newY; data.z = newZ;
        if (data.rootNode) {
            data.rootNode.position.x = newX;
            data.rootNode.position.y = newY;
            data.rootNode.position.z = newZ;
        }
        this._selection.x = newX;
        this._selection.y = newY;
        this._selection.z = newZ;
        // Уведомляем физику — модель сдвинулась, spatial-индекс устарел.
        this._scene3d?._syncUserModelColliders?.();
        try { this._scene3d?._onSceneChange?.(); } catch (e) {}
        this._notifyChange();
    }

    /** Поворот выделенной user-модели по Y (радианы). */
    rotateSelectedUserModel(angleRad) {
        if (this._selection?.type !== 'userModel') return;
        const data = this.userModelManager.instances.get(this._selection.instanceId);
        if (!data) return;
        data.rotationY = angleRad;
        if (data.rootNode) {
            data.rootNode.rotation.y = angleRad;
        }
        this._selection.rotationY = angleRad;
        this._scene3d?._syncUserModelColliders?.();
        try { this._scene3d?._onSceneChange?.(); } catch (e) {}
        this._notifyChange();
    }

    /** Равномерный масштаб user-модели. */
    scaleSelectedUserModel(scale) {
        if (this._selection?.type !== 'userModel') return;
        const data = this.userModelManager.instances.get(this._selection.instanceId);
        if (!data) return;
        data.scale = scale;
        if (data.rootNode) {
            data.rootNode.scaling.x = scale;
            data.rootNode.scaling.y = scale;
            data.rootNode.scaling.z = scale;
        }
        this._selection.scale = scale;
        this._scene3d?._syncUserModelColliders?.();
        try { this._scene3d?._onSceneChange?.(); } catch (e) {}
        this._notifyChange();
    }

    /** Поменять свойства user-модели (canCollide / visible / mass / anchored). */
    setSelectedUserModelProps(patch) {
        if (this._selection?.type !== 'userModel' || !this.userModelManager) return;
        const data = this.userModelManager.instances.get(this._selection.instanceId);
        if (!data) return;
        if ('canCollide' in patch) {
            data.canCollide = !!patch.canCollide;
            // Перерегистрируем коллайдер этого инстанса в физике
            if (this._scene3d?._syncUserModelColliders) {
                this._scene3d._syncUserModelColliders();
            }
        }
        if ('visible' in patch) {
            data.visible = !!patch.visible;
            // Включаем/выключаем рендер всех мешей этого инстанса + root TransformNode
            try { data.rootNode?.setEnabled(data.visible); } catch (e) {}
            for (const m of data.meshes) {
                try { m.setEnabled(data.visible); } catch (e) {}
            }
        }
        if ('mass' in patch) data.mass = patch.mass;
        if ('anchored' in patch) {
            data.anchored = !!patch.anchored;
            // Якорь и канCollide вместе — оба меняют физическую регистрацию
            if (this._scene3d?._syncUserModelColliders) {
                this._scene3d._syncUserModelColliders();
            }
        }
        Object.assign(this._selection, patch);
        // Дёрнем sceneChange чтобы markDirty сработал и проект сохранился
        try { this._scene3d?._onSceneChange?.(); } catch (e) {}
        this._notifyChange();
    }

    /** Поменять свойства блока (canCollide / visible). */
    setSelectedBlockProps(patch) {
        if (this._selection?.type !== 'block' || !this.blockManager) return;
        const { gridX, gridY, gridZ } = this._selection;
        this.blockManager.setBlockProps(gridX, gridY, gridZ, patch);
        Object.assign(this._selection, patch);
        this._notifyChange();
    }

    /** Поменять свойства модели (canCollide / visible / mass). */
    setSelectedModelProps(patch) {
        if (this._selection?.type !== 'model' || !this.modelManager) return;
        this.modelManager.setInstanceProps(this._selection.instanceId, patch);
        Object.assign(this._selection, patch);
        this._notifyChange();
    }

    /** Поменять mass у выделенного объекта (любого типа). */
    setSelectedMass(mass) {
        if (!this._selection) return;
        const m = Number(mass);
        if (!Number.isFinite(m) || m <= 0) return;
        if (this._selection.type === 'block' && this._selection.mesh) {
            this._selection.mesh.metadata.mass = m;
            this._selection.mass = m;
            this.blockManager?._notifyChange?.();
        } else if (this._selection.type === 'model') {
            this.setSelectedModelProps({ mass: m });
            return;
        } else if (this._selection.type === 'userModel') {
            this.setSelectedUserModelProps({ mass: m });
            return;
        } else if (this._selection.type === 'primitive' && this.primitiveManager) {
            this.primitiveManager.updateInstance(this._selection.id, { mass: m });
            this._selection.mass = m;
        }
        this._notifyChange();
    }

    /** Поменять anchored у выделенного объекта (любого типа). */
    setSelectedAnchored(anchored) {
        if (!this._selection) return;
        if (this._selection.type === 'block' && this._selection.mesh) {
            this._selection.mesh.metadata.anchored = !!anchored;
            this._selection.anchored = !!anchored;
            this.blockManager?._notifyChange?.();
        } else if (this._selection.type === 'model' && this.modelManager) {
            const data = this.modelManager.instances.get(this._selection.instanceId);
            if (data) {
                data.anchored = !!anchored;
                this._selection.anchored = !!anchored;
                this.modelManager._notifyChange?.();
            }
        } else if (this._selection.type === 'userModel') {
            this.setSelectedUserModelProps({ anchored: !!anchored });
            return;
        } else if (this._selection.type === 'primitive' && this.primitiveManager) {
            this.primitiveManager.updateInstance(this._selection.id, { anchored: !!anchored });
            this._selection.anchored = !!anchored;
        }
        this._notifyChange();
    }

    /** Удалить выделенный объект. */
    deleteSelected() {
        // Если есть multi-select — удаляем все
        if (this._multi.length > 0) {
            for (const it of this._multi) {
                if (it.kind === 'block') this.blockManager?.removeBlock(it.ref.x, it.ref.y, it.ref.z);
                else if (it.kind === 'model') this.modelManager?.removeInstance(it.ref);
                else if (it.kind === 'userModel') this.userModelManager?.removeInstance(it.ref);
                else if (it.kind === 'primitive') this.primitiveManager?.removeInstance(it.ref);
            }
            this.clear();
            return;
        }
        if (!this._selection) return;
        if (this._selection.type === 'block') {
            this.blockManager.removeBlock(this._selection.gridX, this._selection.gridY, this._selection.gridZ);
        } else if (this._selection.type === 'model') {
            this.modelManager.removeInstance(this._selection.instanceId);
            this.clear(); this._scene3d?._cleanupOrphanScripts?.(); return;
        } else if (this._selection.type === 'userModel') {
            this.userModelManager.removeInstance(this._selection.instanceId);
            this.clear(); this._scene3d?._cleanupOrphanScripts?.(); return;
        } else if (this._selection.type === 'primitive') {
            this.primitiveManager.removeInstance(this._selection.id);
            this.clear(); this._scene3d?._cleanupOrphanScripts?.(); return;
        } else if (this._selection.type === 'spawn') {
            // Удаление точки спавна → игрок будет появляться в (0, высота, 0).
            this._scene3d?.deleteSpawn?.();
        } else if (this._selection.type === 'folder') {
            // Папка целиком — удаляем со ВСЕМ содержимым (рекурсивно).
            const fid = this._selection.folderId;
            this.clear();
            this._scene3d?.folderManager?.removeFolder?.(fid, true);
            // Удаляем скрипты, привязанные к объектам этой папки? Они привязаны
            // к примитивам, которые removeFolder удалит; скрипты на них чистятся
            // через _onSceneChange / при сохранении. Дополнительно пусть движок
            // подчистит «осиротевшие» скрипты.
            this._scene3d?._cleanupOrphanScripts?.();
            return;
        }
        this.clear();
    }

    /** Снять выделение. */
    clear() {
        this._removeHighlight();
        const had = this._selection !== null || this._multi.length > 0;
        this._selection = null;
        this._multi = [];
        if (had) this._notifyChange();
    }

    /**
     * Добавить или убрать объект в мульти-выделении (Ctrl+ЛКМ).
     * Если такой объект уже выделен — снимает; иначе добавляет.
     */
    toggleMeshSelection(mesh) {
        if (!mesh || !mesh.metadata) return;
        const md = mesh.metadata;
        let kind = null, ref = null;
        if (md.isBlock) {
            kind = 'block'; ref = { x: md.gridX, y: md.gridY, z: md.gridZ };
        } else if (md.isModel) {
            kind = 'model'; ref = md.instanceId;
        } else if (md.isPrimitive) {
            kind = 'primitive'; ref = md.primitiveId;
        } else return;

        // Если в мульти ещё ничего нет — добавляем туда текущее single-selection
        if (this._multi.length === 0 && this._selection) {
            const s = this._selection;
            if (s.type === 'block') this._multi.push({ kind: 'block', ref: { x: s.gridX, y: s.gridY, z: s.gridZ } });
            else if (s.type === 'model') this._multi.push({ kind: 'model', ref: s.instanceId });
            else if (s.type === 'primitive') this._multi.push({ kind: 'primitive', ref: s.id });
        }

        // Проверяем, есть ли уже такой
        const eq = (a, b) => {
            if (a.kind !== b.kind) return false;
            if (a.kind === 'block') return a.ref.x === b.ref.x && a.ref.y === b.ref.y && a.ref.z === b.ref.z;
            return a.ref === b.ref;
        };
        const existing = { kind, ref };
        const idx = this._multi.findIndex(it => eq(it, existing));
        if (idx >= 0) {
            this._multi.splice(idx, 1);
        } else {
            this._multi.push(existing);
        }

        // Если осталось 0 — clear; если 1 — single-select; иначе оставляем _selection текущим (последний добавленный)
        this._removeHighlight();
        if (this._multi.length === 0) {
            this._selection = null;
        } else if (this._multi.length === 1) {
            const only = this._multi[0];
            this._multi = [];
            if (only.kind === 'block') this.selectBlockAt(only.ref.x, only.ref.y, only.ref.z);
            else if (only.kind === 'model') this.selectModelByInstanceId(only.ref);
            else if (only.kind === 'primitive') this.selectPrimitiveById(only.ref);
            return; // selectXxx уже notify
        } else {
            // Подсветим все меши в мульти
            this._highlightAllMulti();
            this._selection = { type: 'multi', count: this._multi.length, items: [...this._multi] };
        }
        this._notifyChange();
    }

    _highlightAllMulti() {
        for (const it of this._multi) {
            if (it.kind === 'block') {
                const m = this.blockManager?.blocks.get(`${it.ref.x},${it.ref.y},${it.ref.z}`);
                if (m) this._highlightMesh(m);
            } else if (it.kind === 'model') {
                const data = this.modelManager?.instances.get(it.ref);
                if (data?.clonedMeshes) {
                    for (const cm of data.clonedMeshes) this._highlightMesh(cm);
                }
            } else if (it.kind === 'primitive') {
                const data = this.primitiveManager?.instances.get(it.ref);
                if (data?.mesh) this._highlightMesh(data.mesh);
            }
        }
    }

    /** Получить массив multi-selection. */
    getMultiSelection() { return [...this._multi]; }

    /** Выделить ВСЁ в сцене (Ctrl+A). */
    selectAll() {
        this._removeHighlight();
        this._multi = [];
        if (this.blockManager) {
            for (const mesh of this.blockManager.blocks.values()) {
                const md = mesh.metadata;
                if (md?.isBlock) this._multi.push({ kind: 'block', ref: { x: md.gridX, y: md.gridY, z: md.gridZ } });
            }
        }
        if (this.modelManager) {
            for (const id of this.modelManager.instances.keys()) {
                this._multi.push({ kind: 'model', ref: id });
            }
        }
        if (this.primitiveManager) {
            for (const id of this.primitiveManager.instances.keys()) {
                this._multi.push({ kind: 'primitive', ref: id });
            }
        }
        if (this._multi.length === 0) {
            this._selection = null;
        } else {
            this._highlightAllMulti();
            this._selection = { type: 'multi', count: this._multi.length, items: [...this._multi] };
        }
        this._notifyChange();
    }

    /** Текущее выделение или null. */
    getSelection() {
        return this._selection;
    }

    // === ВНУТРЕННЕЕ ===

    _highlightMesh(mesh) {
        if (!mesh || mesh._isHighlighted) return;
        try {
            // enableEdgesRendering есть только у Mesh, не у TransformNode
            if (typeof mesh.enableEdgesRendering !== 'function') return;
            mesh.enableEdgesRendering();
            mesh.edgesWidth = EDGE_WIDTH;
            mesh.edgesColor = EDGE_COLOR;
            mesh._isHighlighted = true;
            this._highlightedMeshes.push(mesh);
        } catch (e) { /* ignore */ }
    }

    _removeHighlight() {
        for (const m of this._highlightedMeshes) {
            try {
                m.disableEdgesRendering?.();
                m._isHighlighted = false;
            } catch (e) { /* ignore */ }
        }
        this._highlightedMeshes = [];
        this._hideBlockHighlight();
    }

    _notifyChange() {
        if (this._onChange) this._onChange(this._selection);
    }

    /** Снять выделение. */
    clearSelection() {
        this._removeHighlight();
        this._selection = null;
        this._notifyChange();
    }

    dispose() {
        this._removeHighlight();
        this._selection = null;
        this._onChange = null;
    }
}
