/**
 * VoxelWorld — корневой объект воксельного мира.
 *
 * Содержит несколько слоёв (VoxelLayer) с разными voxel-size:
 *   terrain — 0.25м (земля, скалы)
 *   deco    — 0.05м (трава, цветы, грибы; не коллидит)
 *   build   — 0.5м (пользовательские блоки; не первая версия)
 *
 * Также хранит seed для процедурной генерации (Этап 7) и параметры
 * генератора (после Этапа 7b).
 *
 * Сериализация (Этап 3 — RLE):
 *   project_data.scene.voxelWorld = {
 *     seed: number,
 *     version: 1,
 *     layers: {
 *       terrain: { voxelSize: 0.25, palette: [...], chunks: { "0,0,0": "<base64>", ... } },
 *       deco:    { voxelSize: 0.05, palette: [...], chunks: { ... } },
 *     },
 *     generator: { ... GeneratorParams ... }  // только после Этапа 7b
 *   }
 *
 * Legacy миграция:
 *   Если есть project_data.scene.terrain (массив {x,y,z,m}) — конвертируем
 *   в voxelWorld.layers.terrain через loadFromArray.
 */

import { VoxelLayer } from './VoxelLayer';
import { serializeLayer, deserializeLayerData } from './ChunkSerializer';

/** Стандартные размеры ячеек слоёв. */
export const LAYER_VOXEL_SIZES = {
    terrain: 0.25,
    deco: 0.05,
    build: 0.5,
};

export class VoxelWorld {
    constructor() {
        /** Map<name, VoxelLayer>. */
        this.layers = new Map();
        /** Seed для процедурной генерации (заполняется при загрузке/создании). */
        this.seed = 0;
        /** Версия формата данных (для миграций в будущем). */
        this.version = 1;
        /** Колбэк изменений (для авто-сохранения и оповещения рендера). */
        this._onChange = null;
        /** Параметры процедурной генерации (заполняются на Этапе 7b). */
        this.generatorParams = null;
    }

    setOnChange(cb) { this._onChange = cb; }
    _emit() { try { this._onChange?.(); } catch (e) {} }

    // ========================================================================
    // Слои
    // ========================================================================

    /** Получить (или создать) слой. */
    getOrCreateLayer(name, voxelSize = null) {
        let layer = this.layers.get(name);
        if (!layer) {
            const vs = voxelSize ?? LAYER_VOXEL_SIZES[name] ?? 0.25;
            layer = new VoxelLayer(name, vs);
            this.layers.set(name, layer);
        }
        return layer;
    }

    getLayer(name) {
        return this.layers.get(name) ?? null;
    }

    hasLayer(name) {
        return this.layers.has(name);
    }

    // ========================================================================
    // Удобные обёртки на слое terrain (90% операций идут с ним)
    // ========================================================================

    /** Кратко: VoxelWorld.setVoxel = setVoxelOnLayer('terrain', ...). */
    setVoxel(gx, gy, gz, matId, layerName = 'terrain') {
        const layer = this.getOrCreateLayer(layerName);
        const changed = layer.setVoxel(gx, gy, gz, matId);
        if (changed) this._emit();
        return changed;
    }

    getVoxel(gx, gy, gz, layerName = 'terrain') {
        const layer = this.getLayer(layerName);
        return layer ? layer.getVoxel(gx, gy, gz) : null;
    }

    removeVoxel(gx, gy, gz, layerName = 'terrain') {
        return this.setVoxel(gx, gy, gz, null, layerName);
    }

    // ========================================================================
    // Legacy миграция: terrain[] → voxelWorld.layers.terrain
    // ========================================================================

    /**
     * Конвертирует legacy-формат [{x,y,z,m}, ...] в новый формат с чанками.
     * Не очищает существующий слой — добавляет поверх.
     */
    loadLegacyTerrain(voxels) {
        if (!Array.isArray(voxels) || voxels.length === 0) return;
        const layer = this.getOrCreateLayer('terrain');
        layer.loadFromArray(voxels);
        this._emit();
    }

    /**
     * Конвертирует legacy-формат decorations[] в слой deco (если когда-нибудь
     * будет такой формат до миграции). На текущий момент — заглушка.
     */
    loadLegacyDecorations(decos) {
        if (!Array.isArray(decos) || decos.length === 0) return;
        const layer = this.getOrCreateLayer('deco');
        layer.loadFromArray(decos);
        this._emit();
    }

    // ========================================================================
    // Сериализация (Этап 3 — RLE + base64)
    // ========================================================================

    /**
     * Компактная сериализация в plain object для JSON. Каждый чанк —
     * одна base64-строка с RLE-байтами. Палитра общая для слоя.
     *
     * Размер на проекте 1 (110К voxel'ов): ~150-300 КБ vs 5.6 МБ legacy.
     * Уменьшение в ~30 раз.
     */
    serialize() {
        const layersOut = {};
        for (const [name, layer] of this.layers) {
            layersOut[name] = serializeLayer(layer);
        }
        return {
            version: this.version,
            seed: this.seed,
            format: 'rle-v1',
            layers: layersOut,
            generator: this.generatorParams,
        };
    }

    /**
     * Десериализация. Поддерживает оба формата:
     *   - format='rle-v1' — новый RLE+base64 (Этап 3+)
     *   - legacy без format — массив {voxels:[{x,y,z,m},...]} per layer
     */
    static deserialize(data) {
        const w = new VoxelWorld();
        if (!data) return w;
        w.version = data.version ?? 1;
        w.seed = data.seed ?? 0;
        w.generatorParams = data.generator ?? null;
        if (!data.layers) return w;

        const isRLE = data.format === 'rle-v1';
        for (const [name, layerData] of Object.entries(data.layers)) {
            const layer = w.getOrCreateLayer(name, layerData.voxelSize);
            if (isRLE) {
                // RLE формат: { voxelSize, palette: [...], chunks: { "cx,cy,cz": "base64" } }
                const decoded = deserializeLayerData(layerData);
                // Прямая запись данных слоя: палитра + чанки.
                layer.palette = decoded.palette;
                layer.idToIdx = new Map();
                for (let i = 1; i < decoded.palette.length; i++) {
                    if (decoded.palette[i]) layer.idToIdx.set(decoded.palette[i], i);
                }
                for (const [k, ch] of decoded.chunks) {
                    layer.chunks.set(k, ch);
                }
            } else if (Array.isArray(layerData.voxels)) {
                // Legacy plain JSON
                layer.loadFromArray(layerData.voxels);
            }
        }
        return w;
    }

    /**
     * Подробная статистика размера сериализованных данных. Использовать
     * для debug-команды __voxelWorldBenchmarkRLE.
     */
    measureSize() {
        const out = { layers: {}, totalBytes: 0 };
        for (const [name, layer] of this.layers) {
            const layerData = serializeLayer(layer);
            const json = JSON.stringify(layerData);
            const bytes = new Blob([json]).size;
            const chunkCount = Object.keys(layerData.chunks).length;
            // Средний размер одного chunk base64
            let chunkBytes = 0;
            for (const v of Object.values(layerData.chunks)) chunkBytes += v.length;
            out.layers[name] = {
                voxels: layer.voxelCount(),
                chunks: chunkCount,
                jsonBytes: bytes,
                chunkBase64Bytes: chunkBytes,
                avgChunkBytes: chunkCount > 0 ? Math.round(chunkBytes / chunkCount) : 0,
            };
            out.totalBytes += bytes;
        }
        return out;
    }

    // ========================================================================
    // Stats и отладка
    // ========================================================================

    stats() {
        const layers = {};
        let totalVoxels = 0;
        let totalChunks = 0;
        for (const [name, layer] of this.layers) {
            const s = layer.stats();
            layers[name] = s;
            totalVoxels += s.voxels;
            totalChunks += s.chunks;
        }
        return { seed: this.seed, totalVoxels, totalChunks, layers };
    }
}
