/**
 * VoxelChunk — единица хранения и рендеринга воксельного мира.
 *
 * Один чанк — куб 32×32×32 ячеек (стандарт Minecraft).
 * Внутри — Uint8Array(32768): индекс материала каждой ячейки в палитре слоя.
 *   0       = пусто (воздух)
 *   1..255  = индекс в palette[] слоя
 *
 * Чанк идентифицируется (cx, cy, cz) в chunk-space — целые числа.
 * Мировые координаты ячейки внутри чанка:
 *   worldX = (cx * 32 + localX) * voxelSize
 * где voxelSize — параметр слоя (0.25 для terrain, 0.05 для deco).
 *
 * Layout: linear-ordered по Y → Z → X для cache-friendly меширования
 * (когда мы идём по слоям сверху вниз во время surface culling).
 *   index = localY * 32*32 + localZ * 32 + localX
 *
 * Production assumptions:
 *   - Чанк создаётся пустым (вся ячейка = 0)
 *   - Прямая модификация data — допустимо, но dirty-флаг надо ставить вручную
 *   - Когда чанк пустой (nonEmptyCount=0) — слой может удалить его из Map
 */

export const CHUNK_SIZE = 32;
export const CHUNK_VOLUME = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE;   // 32768

export class VoxelChunk {
    /**
     * @param {number} cx - chunk X (целое)
     * @param {number} cy - chunk Y
     * @param {number} cz - chunk Z
     */
    constructor(cx, cy, cz) {
        this.cx = cx;
        this.cy = cy;
        this.cz = cz;
        /** Uint8Array(32768) — material index каждой ячейки. */
        this.data = new Uint8Array(CHUNK_VOLUME);
        /** Сколько занятых (non-zero) ячеек. Поддерживается setLocal. */
        this.nonEmptyCount = 0;
        /** Нужно ли перестроить mesh при следующем frame'е. */
        this.dirty = true;
        /** Ссылка на собранный mesh (или Map<matIdx, Mesh>). Устанавливает
         *  ChunkMesher/Renderer; чанк сам не знает что это. */
        this.meshRef = null;
        /** Версия — инкрементится при каждой записи. Используется
         *  для дельта-синка в мультиплеере (Этап 8). */
        this.version = 0;
    }

    /**
     * Преобразовать локальные (x,y,z) в индекс data[].
     * Координаты — 0..31. Без bounds check (для скорости — caller должен
     * сам проверять, что в диапазоне).
     */
    static localIndex(lx, ly, lz) {
        return ly * CHUNK_SIZE * CHUNK_SIZE + lz * CHUNK_SIZE + lx;
    }

    /**
     * Прочитать material index в локальной (lx,ly,lz). Возвращает 0 если
     * вне границ чанка — caller обычно использует это для проверки соседей
     * (если мы за границей чанка → воздух, или вызывающий должен сам
     * проверять соседний чанк).
     */
    getLocal(lx, ly, lz) {
        if (lx < 0 || lx >= CHUNK_SIZE ||
            ly < 0 || ly >= CHUNK_SIZE ||
            lz < 0 || lz >= CHUNK_SIZE) {
            return 0;
        }
        return this.data[VoxelChunk.localIndex(lx, ly, lz)];
    }

    /**
     * Записать material index в локальную ячейку.
     * matIdx = 0 — очистка (удаление voxel'а).
     * Возвращает true если значение изменилось (нужна перестройка mesh'а).
     */
    setLocal(lx, ly, lz, matIdx) {
        if (lx < 0 || lx >= CHUNK_SIZE ||
            ly < 0 || ly >= CHUNK_SIZE ||
            lz < 0 || lz >= CHUNK_SIZE) {
            return false;
        }
        const idx = VoxelChunk.localIndex(lx, ly, lz);
        const old = this.data[idx];
        if (old === matIdx) return false;
        // Обновляем nonEmptyCount
        if (old === 0 && matIdx !== 0) {
            this.nonEmptyCount++;
        } else if (old !== 0 && matIdx === 0) {
            this.nonEmptyCount--;
        }
        this.data[idx] = matIdx;
        this.dirty = true;
        this.version++;
        return true;
    }

    /** Полная очистка чанка. */
    clear() {
        if (this.nonEmptyCount === 0) return;
        this.data.fill(0);
        this.nonEmptyCount = 0;
        this.dirty = true;
        this.version++;
    }

    isEmpty() {
        return this.nonEmptyCount === 0;
    }

    /**
     * Координаты левого-нижнего-переднего угла чанка в мире, в воксельных
     * (не мировых!) индексах. Используется при surface culling для
     * вычисления глобальных координат воксел'а.
     */
    voxelOriginX() { return this.cx * CHUNK_SIZE; }
    voxelOriginY() { return this.cy * CHUNK_SIZE; }
    voxelOriginZ() { return this.cz * CHUNK_SIZE; }
}

/**
 * Хелпер: преобразовать мировые целочисленные voxel-координаты (gx,gy,gz)
 * в (chunkKey, localX, localY, localZ).
 * gx — это grid-индекс voxel'а (как сейчас в TerrainManager.voxels Map),
 * не координата в метрах. Перевод в метры — отдельно: world = gx * voxelSize.
 */
export function voxelToChunk(gx, gy, gz) {
    // Math.floor вместо >>0 — потому что отрицательные voxel-координаты
    // должны идти в чанки -1, -2, ... а не в 0 (как делает побитовый shift).
    const cx = Math.floor(gx / CHUNK_SIZE);
    const cy = Math.floor(gy / CHUNK_SIZE);
    const cz = Math.floor(gz / CHUNK_SIZE);
    // Локальные координаты — всегда 0..31 (используем mod с защитой от
    // отрицательных).
    const lx = gx - cx * CHUNK_SIZE;
    const ly = gy - cy * CHUNK_SIZE;
    const lz = gz - cz * CHUNK_SIZE;
    return { cx, cy, cz, lx, ly, lz };
}

/** Ключ чанка в Map (строка "cx,cy,cz"). */
export function chunkKey(cx, cy, cz) {
    return `${cx},${cy},${cz}`;
}
