/* eslint-disable */
/**
 * TerrainRegionWorker — фоновый воркер сборки mesh-геометрии региона.
 *
 * Основной поток отдаёт массив voxel'ов одного материала и желаемый
 * detail-уровень (1 = full, 2 = half, 4 = quarter). Воркер собирает:
 *   - positions: Float32Array вершин
 *   - normals: Float32Array нормалей
 *   - uvs: Float32Array UV
 *   - indices: Uint32Array индексов
 *
 * Главный поток создаёт Mesh + vertexData.applyToMesh.
 *
 * Передача через Transferable — zero-copy.
 *
 * Зачем это нужно: на больших картах (1М+ voxel'ов) сборка mesh'ей в
 * main thread лагает рендер. Worker делает это асинхронно в фоне.
 *
 * Формат сообщения {type: 'build', regionId, voxels, voxelSize, lod}:
 *   - voxels: Int16Array, шаг 4: x, y, z, matIdx (т.к. матриалы упакованы)
 *   - matKeys: string[] — материалы по индексу (или 'matId:top'/':side'/':bottom'
 *     для MultiCube). Их main thread разворачивает обратно.
 *
 * Сообщение ответа {type: 'built', regionId, byMaterial:{[matKey]: {positions, normals, uvs, indices}}}.
 */

// ============================================================================
// Конфигурация граней — копия с ChunkMesher
// ============================================================================
const FACES = [
    { faceIdx: 0, dx: 0, dy: 0, dz: -1, nx: 0, ny: 0, nz: -1, corners: [[0,0,0],[0,1,0],[1,1,0],[1,0,0]] },
    { faceIdx: 1, dx: 1, dy: 0, dz: 0,  nx: 1, ny: 0, nz: 0,  corners: [[1,0,0],[1,1,0],[1,1,1],[1,0,1]] },
    { faceIdx: 2, dx: 0, dy: 0, dz: 1,  nx: 0, ny: 0, nz: 1,  corners: [[1,0,1],[1,1,1],[0,1,1],[0,0,1]] },
    { faceIdx: 3, dx: -1, dy: 0, dz: 0, nx: -1, ny: 0, nz: 0, corners: [[0,0,1],[0,1,1],[0,1,0],[0,0,0]] },
    { faceIdx: 4, dx: 0, dy: -1, dz: 0, nx: 0, ny: -1, nz: 0, corners: [[0,0,1],[0,0,0],[1,0,0],[1,0,1]] },
    { faceIdx: 5, dx: 0, dy: 1, dz: 0,  nx: 0, ny: 1, nz: 0,  corners: [[0,1,0],[0,1,1],[1,1,1],[1,1,0]] },
];

// MultiCube материалы — для каждого создаём отдельный ключ матерала
// для top/side/bottom. main thread знает какие текстуры применить.
const MULTICUBE = new Set(['grass', 'trunk', 'trunk_white']);

const TRANSPARENT = new Set([
    'water', 'glacier',
    'leaves', 'leaves_orange',
    'flower_red', 'flower_blue', 'flower_yellow',
    'mushroom_red', 'tall_grass',
]);

function materialKey(matId, faceIdx) {
    if (!MULTICUBE.has(matId)) return matId;
    if (faceIdx === 5) return matId + ':top';
    if (faceIdx === 4) return matId + ':bottom';
    return matId + ':side';
}

function isFaceVisible(ownMat, neighborMat) {
    if (!neighborMat) return true;
    const ownT = TRANSPARENT.has(ownMat);
    const nbT = TRANSPARENT.has(neighborMat);
    if (!ownT && !nbT) return false;
    if (!ownT && nbT) return true;
    if (ownT && !nbT) return false;
    return ownMat !== neighborMat;
}

// ============================================================================
// Основная функция сборки геометрии региона
// ============================================================================

/**
 * @param {Object} msg - {voxels: Array<{x,y,z,m}>, voxelSize, lod}
 *   lod 1 = полное разрешение (каждый voxel)
 *   lod 2 = каждый 2-й (×8 меньше треугольников, для дальних регионов)
 *   lod 4 = каждый 4-й (×64 меньше)
 * @returns {Object} byMaterial: {[matKey]: {positions, normals, uvs, indices}}
 */
function buildRegionGeometry(msg) {
    const { voxels, voxelSize, lod = 1 } = msg;

    // 1. Построить hash voxels: "x,y,z" → matId (для проверки соседей)
    const voxMap = new Map();
    for (const v of voxels) {
        if (lod > 1) {
            // LOD: фильтруем — берём только voxel'ы кратные lod
            if (v.x % lod !== 0 || v.y % lod !== 0 || v.z % lod !== 0) continue;
        }
        voxMap.set(v.x + ',' + v.y + ',' + v.z, v.m);
    }

    // 2. Для каждого voxel — проверяем 6 граней
    // accumulators: matKey → {positions:[], normals:[], uvs:[], indices:[]}
    const accumulators = new Map();
    function getAccum(matKey) {
        let a = accumulators.get(matKey);
        if (!a) {
            a = { positions: [], normals: [], uvs: [], indices: [] };
            accumulators.set(matKey, a);
        }
        return a;
    }

    const step = lod; // шаг при LOD — увеличенный размер voxel'а
    const visualSize = voxelSize * step;

    for (const [key, ownMat] of voxMap) {
        const parts = key.split(',');
        const x = parseInt(parts[0], 10);
        const y = parseInt(parts[1], 10);
        const z = parseInt(parts[2], 10);

        const wx0 = x * voxelSize;
        const wy0 = y * voxelSize;
        const wz0 = z * voxelSize;

        for (let f = 0; f < 6; f++) {
            const face = FACES[f];
            // Сосед на расстоянии step (учитываем LOD)
            const nx = x + face.dx * step;
            const ny = y + face.dy * step;
            const nz = z + face.dz * step;
            const nbMat = voxMap.get(nx + ',' + ny + ',' + nz);
            if (!isFaceVisible(ownMat, nbMat)) continue;

            const matKey = materialKey(ownMat, face.faceIdx);
            const accum = getAccum(matKey);
            const baseV = accum.positions.length / 3;
            const uvCorners = [[0, 1], [0, 0], [1, 0], [1, 1]];

            for (let i = 0; i < 4; i++) {
                const c = face.corners[i];
                accum.positions.push(
                    wx0 + c[0] * visualSize,
                    wy0 + c[1] * visualSize,
                    wz0 + c[2] * visualSize,
                );
                accum.normals.push(face.nx, face.ny, face.nz);
                const uv = uvCorners[i];
                accum.uvs.push(uv[0], uv[1]);
            }
            accum.indices.push(baseV, baseV + 1, baseV + 2);
            accum.indices.push(baseV, baseV + 2, baseV + 3);
        }
    }

    // 3. Конвертируем в Typed Arrays (для Transferable)
    const result = {};
    const transferables = [];
    for (const [matKey, accum] of accumulators) {
        const positions = new Float32Array(accum.positions);
        const normals = new Float32Array(accum.normals);
        const uvs = new Float32Array(accum.uvs);
        const indices = new Uint32Array(accum.indices);
        result[matKey] = { positions, normals, uvs, indices };
        transferables.push(positions.buffer, normals.buffer, uvs.buffer, indices.buffer);
    }
    return { result, transferables };
}

// ============================================================================
// Worker message handler
// ============================================================================
self.onmessage = (e) => {
    const msg = e.data;
    if (msg.type === 'build') {
        try {
            const t0 = performance.now();
            const { result, transferables } = buildRegionGeometry(msg);
            const dt = performance.now() - t0;
            self.postMessage(
                {
                    type: 'built',
                    regionId: msg.regionId,
                    byMaterial: result,
                    timeMs: dt,
                },
                transferables,
            );
        } catch (err) {
            self.postMessage({
                type: 'error',
                regionId: msg.regionId,
                error: err.message,
            });
        }
    }
};
