/**
 * voxelModelCodec — кодек воксельных моделей пользователя.
 *
 * ПРОБЛЕМА которую решает:
 *   Старый формат v2 хранил каждый воксель как
 *     {"x":2,"y":0,"z":24,"c":"#7c5430"}   ≈ 36 байт/воксель.
 *   Модель в 7000+ вокселей = 250+ КБ JSON. Парсинг, передача по сети
 *   и постановка в сцену — медленные. Редактор грузил модель >10 сек.
 *
 * ФОРМАТ v3 (компактный):
 *   {
 *     "version": 3,
 *     "size": 48,
 *     "palette": ["#7c5430", "a06a3a", "t:grass", ...],  // уникальные материалы
 *     "data": [coordIdx, palIdx, coordIdx, palIdx, ...]   // плоский массив чисел
 *   }
 *   - coordIdx = x + y*size + z*size*size — одно число вместо трёх полей.
 *   - palIdx — индекс в palette. Цвета/текстуры дедуплицированы.
 *   - Цвет в палитре: строка "#rrggbb" или "rrggbb" (# опционально).
 *     Текстура: строка с префиксом "t:" — например "t:grass".
 *   ≈ 7-9 байт/воксель — в 4-5 раз компактнее v2.
 *
 * СОВМЕСТИМОСТЬ:
 *   decodeVoxelModel() читает И v3, И v2 (старые модели), И v1 (только цвет).
 *   encodeVoxelModel() всегда пишет v3.
 *   Внутреннее представление — массив { x, y, z, c?, t? } (как было в v2),
 *   чтобы остальной код (рендер, физика) не переписывать.
 */

/** Нормализовать hex-цвет к виду "#rrggbb" (lowercase, с #). */
function normHex(hex) {
    if (typeof hex !== 'string') return '#ffffff';
    let h = hex.trim().toLowerCase();
    if (h[0] === '#') h = h.slice(1);
    if (!/^[0-9a-f]{6}$/.test(h)) return '#ffffff';
    return '#' + h;
}

/**
 * Декодировать model_data (строка JSON или объект) в нормализованную форму.
 * @returns {{ size:number, voxels:Array<{x,y,z,c?,t?}> } | null}
 */
export function decodeVoxelModel(modelData) {
    let data;
    try {
        data = typeof modelData === 'string' ? JSON.parse(modelData) : modelData;
    } catch (e) {
        return null;
    }
    if (!data || typeof data !== 'object') return null;

    const size = (typeof data.size === 'number' && data.size > 0) ? (data.size | 0) : 32;

    // --- Формат v3: палитра + плоский массив ---
    if (data.version === 3 && Array.isArray(data.palette) && Array.isArray(data.data)) {
        const palette = data.palette;
        const flat = data.data;
        const s2 = size * size;
        const voxels = [];
        for (let i = 0; i + 1 < flat.length; i += 2) {
            const coord = flat[i] | 0;
            const palIdx = flat[i + 1] | 0;
            const mat = palette[palIdx];
            if (mat == null) continue;
            const x = coord % size;
            const y = ((coord / size) | 0) % size;
            const z = (coord / s2) | 0;
            if (typeof mat === 'string' && mat.startsWith('t:')) {
                voxels.push({ x, y, z, t: mat.slice(2) });
            } else {
                voxels.push({ x, y, z, c: normHex(mat) });
            }
        }
        return { size, voxels };
    }

    // --- Формат v2 / v1: список объектов {x,y,z,c|t} ---
    if (Array.isArray(data.voxels)) {
        const voxels = [];
        for (const v of data.voxels) {
            if (!v || typeof v.x !== 'number') continue;
            const x = v.x | 0, y = v.y | 0, z = v.z | 0;
            if (v.t) voxels.push({ x, y, z, t: v.t });
            else voxels.push({ x, y, z, c: normHex(v.c || '#ffffff') });
        }
        return { size, voxels };
    }

    return null;
}

/**
 * Закодировать модель в компактный формат v3.
 * @param {number} size — размер сетки (8/16/32/48/64/128).
 * @param {Array<{x,y,z,c?,t?}>} voxels — список вокселей.
 * @returns {string} JSON-строка формата v3.
 */
export function encodeVoxelModel(size, voxels) {
    const sz = (typeof size === 'number' && size > 0) ? (size | 0) : 32;
    const s2 = sz * sz;
    const palette = [];
    const palMap = new Map();   // материал-строка → индекс палитры
    const flat = [];

    const palIndexOf = (matStr) => {
        let idx = palMap.get(matStr);
        if (idx === undefined) {
            idx = palette.length;
            palette.push(matStr);
            palMap.set(matStr, idx);
        }
        return idx;
    };

    for (const v of voxels) {
        if (!v || typeof v.x !== 'number') continue;
        const x = v.x | 0, y = v.y | 0, z = v.z | 0;
        if (x < 0 || y < 0 || z < 0 || x >= sz || y >= sz || z >= sz) continue;
        const coord = x + y * sz + z * s2;
        // Материал: текстура "t:<id>" или цвет "#rrggbb" (# убираем — экономия).
        let matStr;
        if (v.t) {
            matStr = 't:' + v.t;
        } else {
            matStr = normHex(v.c || '#ffffff').slice(1);   // без # — короче
        }
        flat.push(coord, palIndexOf(matStr));
    }

    return JSON.stringify({
        version: 3,
        size: sz,
        palette,
        data: flat,
    });
}
