/**
 * SmoothBrushes — кисти для редактирования гладкого ландшафта (DensityGrid).
 *
 * Все операции работают со СФЕРОЙ в мировых координатах + строгая привязка
 * к density-grid (4м/cell). Возвращают Set<chunkKey> затронутых чанков,
 * чтобы RobloxTerrain пересобрал только их (а не всю карту).
 *
 * Поддерживаемые кисти (как в Roblox Studio):
 *   - sculptUp:   добавить материал (поднять рельеф). Density += strength.
 *   - sculptDown: убрать материал (вырыть). Density -= strength.
 *   - smooth:     усреднить density с соседями (laplacian filter).
 *   - paint:      заменить материал в solid-ячейках, density не меняем.
 *   - flatten:    выровнять по плоскости Y=target — поднять/опустить density.
 *   - fill:       залить сферу density=255 материалом.
 *   - erase:      обнулить density в сфере.
 */

import { CELL_SIZE, DENSITY_THRESHOLD } from './DensityGrid';

/** Размер chunk'а — должен совпадать с RobloxTerrain.CHUNK_SIZE. */
const CHUNK_SIZE = 16;

function worldToCell(w) {
    return Math.floor(w / CELL_SIZE);
}

/** Ключ chunk'а из LOCAL cell-индекса (внутри grid). */
function localCellToChunkKey(lx, ly, lz) {
    const cx = Math.floor(lx / CHUNK_SIZE);
    const cy = Math.floor(ly / CHUNK_SIZE);
    const cz = Math.floor(lz / CHUNK_SIZE);
    return `${cx},${cy},${cz}`;
}

/**
 * Перебор cells в сфере с smoothstep-влиянием.
 * grid.origin — global origin. Координаты gx,gy,gz в callback — LOCAL индексы
 * (внутри grid 0..size). Используем local чтобы grid.set / grid.getDensity
 * работали правильно.
 */
function forCellsInSphere(grid, center, radius, callback) {
    const dirty = new Set();
    const r2 = radius * radius;
    // bbox в LOCAL индексах с запасом +1
    const minGxL = worldToCell(center.x - radius) - grid.origin.x - 1;
    const maxGxL = worldToCell(center.x + radius) - grid.origin.x + 1;
    const minGyL = worldToCell(center.y - radius) - grid.origin.y - 1;
    const maxGyL = worldToCell(center.y + radius) - grid.origin.y + 1;
    const minGzL = worldToCell(center.z - radius) - grid.origin.z - 1;
    const maxGzL = worldToCell(center.z + radius) - grid.origin.z + 1;
    for (let gzL = minGzL; gzL <= maxGzL; gzL++) {
        for (let gyL = minGyL; gyL <= maxGyL; gyL++) {
            for (let gxL = minGxL; gxL <= maxGxL; gxL++) {
                // Центр cell в мире
                const wx = (grid.origin.x + gxL + 0.5) * CELL_SIZE;
                const wy = (grid.origin.y + gyL + 0.5) * CELL_SIZE;
                const wz = (grid.origin.z + gzL + 0.5) * CELL_SIZE;
                const dx = wx - center.x;
                const dy = wy - center.y;
                const dz = wz - center.z;
                const d2 = dx * dx + dy * dy + dz * dz;
                if (d2 > r2) continue;
                const t = Math.sqrt(d2) / radius;
                const influence = 1 - t * t * (3 - 2 * t);
                const changed = callback(gxL, gyL, gzL, influence);
                if (changed) dirty.add(localCellToChunkKey(gxL, gyL, gzL));
            }
        }
    }
    return dirty;
}

/** Sculpt Up — добавляет density (поднять / нарастить).
 *  Важный фикс для низкой силы: если cell ПУСТАЯ (oldD<10) и попала в зону
 *  влияния (inf > 0.1) — сразу делаем density >= threshold (128). Иначе
 *  низкая strength не давала cell достичь порога видимости → пользователь
 *  кликал-кликал, density рос на 1-2 за клик, но визуально ничего не было
 *  пока 128 не достиг (≈40 кликов в одну cell).
 *  С прыжком до threshold — первый клик СРАЗУ показывает результат, дальше
 *  растёт линейно от strength.
 */
export function brushSculptUp(grid, params) {
    const { center, radius, strength = 80, material = 'grass' } = params;
    // strength → нормализованная sNorm 0..1 (60..400 → 0..1).
    const sNorm = Math.max(0, Math.min(1, (strength - 60) / 340));
    // === Эффективный радиус: основа влияния силы ===
    // sNorm=0   → effectiveRadius = 0.35×radius (маленький бугорок)
    // sNorm=1   → effectiveRadius = 1.0×radius (полный шар)
    // Это даёт реальное визуальное отличие — холм РАЗНОЙ ВЫСОТЫ:
    //   слабая сила — короткий бугор, сильная — полный купол.
    const effRadius = radius * (0.35 + sNorm * 0.65);
    return forCellsInSphere(grid, center, radius, (gx, gy, gz, inf) => {
        // Вычисляем РАССТОЯНИЕ cell-центра до центра кисти (в метрах).
        // Если оно > effRadius — пропускаем (cell вне effective-зоны).
        const wx = (grid.origin.x + gx + 0.5) * CELL_SIZE;
        const wy = (grid.origin.y + gy + 0.5) * CELL_SIZE;
        const wz = (grid.origin.z + gz + 0.5) * CELL_SIZE;
        const dx = wx - center.x;
        const dy = wy - center.y;
        const dz = wz - center.z;
        const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
        if (dist > effRadius) return false;
        // Внутри effRadius — плавный градиент density: центр=255, край=threshold.
        const t = dist / effRadius;
        const target = 255 - (255 - DENSITY_THRESHOLD) * t * t;   // мягкий falloff
        const oldD = grid.getDensity(gx, gy, gz);
        if (oldD >= target) return false;
        const newD = Math.round(target);
        if (newD === oldD) return false;
        const curMat = grid.getMatId(gx, gy, gz);
        const matKey = curMat === 0 ? material : null;
        grid.set(gx, gy, gz, newD, matKey);
        return true;
    });
}

/** Sculpt Down — симметрично sculptUp: effective radius растёт с силой. */
export function brushSculptDown(grid, params) {
    const { center, radius, strength = 80 } = params;
    const sNorm = Math.max(0, Math.min(1, (strength - 60) / 340));
    const effRadius = radius * (0.35 + sNorm * 0.65);
    return forCellsInSphere(grid, center, radius, (gx, gy, gz, inf) => {
        const wx = (grid.origin.x + gx + 0.5) * CELL_SIZE;
        const wy = (grid.origin.y + gy + 0.5) * CELL_SIZE;
        const wz = (grid.origin.z + gz + 0.5) * CELL_SIZE;
        const dx = wx - center.x;
        const dy = wy - center.y;
        const dz = wz - center.z;
        const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
        if (dist > effRadius) return false;
        const oldD = grid.getDensity(gx, gy, gz);
        if (oldD === 0) return false;
        // Center=0, край=threshold-2 (мягкий край ямы).
        const t = dist / effRadius;
        const target = 0 + (DENSITY_THRESHOLD - 2) * t * t;
        if (oldD <= target) return false;
        const newD = Math.max(0, Math.round(target));
        if (newD === oldD) return false;
        grid.set(gx, gy, gz, newD, null);
        return true;
    });
}

/** Smooth — усредняет density с соседями. */
export function brushSmooth(grid, params) {
    const { center, radius, strength = 50 } = params;
    const updates = [];
    forCellsInSphere(grid, center, radius, (gx, gy, gz, inf) => {
        const curD = grid.getDensity(gx, gy, gz);
        const sumD =
            grid.getDensity(gx - 1, gy, gz) + grid.getDensity(gx + 1, gy, gz) +
            grid.getDensity(gx, gy - 1, gz) + grid.getDensity(gx, gy + 1, gz) +
            grid.getDensity(gx, gy, gz - 1) + grid.getDensity(gx, gy, gz + 1);
        const avgD = sumD / 6;
        const blend = Math.min(1, (strength / 255) * inf);
        const newD = Math.round(curD * (1 - blend) + avgD * blend);
        if (newD !== curD) updates.push([gx, gy, gz, newD]);
        return false;
    });
    const dirty = new Set();
    for (const [gx, gy, gz, newD] of updates) {
        grid.set(gx, gy, gz, newD, null);
        dirty.add(localCellToChunkKey(gx, gy, gz));
    }
    return dirty;
}

/** Paint — меняет материал в solid-ячейках, density не трогаем. */
export function brushPaint(grid, params) {
    const { center, radius, material = 'grass' } = params;
    return forCellsInSphere(grid, center, radius, (gx, gy, gz, inf) => {
        const d = grid.getDensity(gx, gy, gz);
        if (d < DENSITY_THRESHOLD) return false;
        const curKey = grid.getMatKey(gx, gy, gz);
        if (curKey === material) return false;
        grid.set(gx, gy, gz, d, material);
        return true;
    });
}

/** Flatten — выровнять поверхность по плоскости Y=targetY. */
export function brushFlatten(grid, params) {
    const { center, radius, strength = 80, material = 'grass', targetY = center.y } = params;
    return forCellsInSphere(grid, center, radius, (gx, gy, gz, inf) => {
        const cellY = (grid.origin.y + gy + 0.5) * CELL_SIZE;
        const oldD = grid.getDensity(gx, gy, gz);
        let newD;
        if (cellY < targetY - CELL_SIZE * 0.5) {
            newD = Math.min(255, oldD + strength * inf);
        } else if (cellY > targetY + CELL_SIZE * 0.5) {
            newD = Math.max(0, oldD - strength * inf);
        } else {
            const target = DENSITY_THRESHOLD;
            const delta = (target - oldD) * inf * (strength / 255);
            newD = Math.round(Math.max(0, Math.min(255, oldD + delta)));
        }
        newD = newD | 0;
        if (newD === oldD) return false;
        const curMat = grid.getMatId(gx, gy, gz);
        const matKey = curMat === 0 ? material : null;
        grid.set(gx, gy, gz, newD, matKey);
        return true;
    });
}

/** Fill — жёстко заполняет сферу solid-density (255). */
export function brushFill(grid, params) {
    const { center, radius, material = 'grass' } = params;
    return forCellsInSphere(grid, center, radius, (gx, gy, gz, inf) => {
        if (inf < 0.05) return false;
        const oldD = grid.getDensity(gx, gy, gz);
        const curKey = grid.getMatKey(gx, gy, gz);
        if (oldD === 255 && curKey === material) return false;
        grid.set(gx, gy, gz, 255, material);
        return true;
    });
}

/** Erase — обнуляет density. */
export function brushErase(grid, params) {
    const { center, radius } = params;
    return forCellsInSphere(grid, center, radius, (gx, gy, gz, inf) => {
        const oldD = grid.getDensity(gx, gy, gz);
        if (oldD === 0) return false;
        const newD = Math.max(0, oldD - 255 * inf) | 0;
        if (newD === oldD) return false;
        grid.set(gx, gy, gz, newD, null);
        return true;
    });
}

/**
 * Главный API — диспетчер по типу кисти.
 * @returns {Set<string>} dirty chunks
 */
export function applyBrush(grid, brushType, params) {
    switch (brushType) {
        case 'sculptUp':   return brushSculptUp(grid, params);
        case 'sculptDown': return brushSculptDown(grid, params);
        case 'smooth':     return brushSmooth(grid, params);
        case 'paint':      return brushPaint(grid, params);
        case 'flatten':    return brushFlatten(grid, params);
        case 'fill':       return brushFill(grid, params);
        case 'erase':      return brushErase(grid, params);
        default:
            console.warn('[SmoothBrushes] unknown brush:', brushType);
            return new Set();
    }
}
