/**
 * VoxelTreeBuilder — алгоритм генерации красивого voxel-дерева.
 *
 * Перенесён из WorldGenerator._placeTree (там используется для процедурной
 * генерации мира). Здесь экспортирован как чистая функция, чтобы можно было
 * звать из brush-инструмента "Деревья" в редакторе.
 *
 * Виды деревьев:
 *   - 'oak'    — дуб: коричневый ствол, зелёная крона
 *   - 'birch'  — берёза: белый тонкий ствол, листва с оранжевыми вкраплениями
 *   - 'autumn' — осеннее: коричневый ствол, ОРАНЖЕВАЯ листва
 *
 * Алгоритм:
 *   1. Толстый ствол (offsets по thickness 0..3) с сужением к верху
 *   2. Утолщение у основания (корни) для thickness >= 2
 *   3. Зигзагообразные ветви — 2-8 шт., расходятся под углом
 *   4. Главная крона (сфера) + крона на конце каждой ветви
 *
 * sizeScale: 0.3..5.0
 *   0.3..0.8 → саженец (~3-5 voxel высота, thin ствол)
 *   1.0      → стандарт (~8-12 voxel, средняя крона)
 *   2.0      → большое (~20 voxel, толстый ствол 3×3, много ветвей)
 *   5.0      → гигантское (~50 voxel, ствол 5×5)
 */

/** Простой 2D-hash, возвращает float ∈ [0..1). Детерминированный. */
function hash2(x, y, seed) {
    let h = ((x | 0) * 73856093) ^ ((y | 0) * 19349663) ^ ((seed | 0) * 83492791);
    h = (h ^ (h >>> 13)) * 1274126177;
    return ((h ^ (h >>> 16)) >>> 0) / 0xffffffff;
}

/**
 * Шаблон офсетов ствола.
 *   0: один voxel
 *   1: крест 5 voxel'ов (центр + 4 стороны)
 *   2: полный 3×3 (9 voxel'ов)
 *   3: 5×5 без 4-х угловых (21 voxel)
 */
function trunkOffsets(thickness) {
    if (thickness <= 0) return [[0, 0]];
    if (thickness === 1) return [[0, 0], [1, 0], [-1, 0], [0, 1], [0, -1]];
    if (thickness === 2) {
        return [
            [0, 0], [1, 0], [-1, 0], [0, 1], [0, -1],
            [1, 1], [-1, -1], [1, -1], [-1, 1],
        ];
    }
    const out = [];
    for (let dx = -2; dx <= 2; dx++) {
        for (let dz = -2; dz <= 2; dz++) {
            if (Math.abs(dx) === 2 && Math.abs(dz) === 2) continue;
            out.push([dx, dz]);
        }
    }
    return out;
}

/**
 * Сфера кроны с пятнистой раскраской и лохматыми краями.
 */
function placeCrown(setVoxelFn, cx, cy, cz, r, squish, placeLeaf, seed) {
    const r2sq = r * r;
    const innerR2 = r > 2 ? (r - 2) * (r - 2) : 0;
    const yRange = Math.floor(r * squish) + 2;
    for (let dx = -r - 1; dx <= r + 1; dx++) {
        for (let dy = -Math.floor(r / 2); dy <= yRange; dy++) {
            for (let dz = -r - 1; dz <= r + 1; dz++) {
                const dyEff = dy / squish;
                const d2 = dx * dx + dyEff * dyEff + dz * dz;
                if (d2 > r2sq + 2) continue;
                if (d2 < innerR2) continue;
                const edgeThresh = r2sq - 1.5;
                if (d2 > edgeThresh) {
                    if (hash2(cx + dx, cz + dz, seed + 3000 + dy) > 0.5) continue;
                }
                if (dx === 0 && dz === 0 && dy < 0) continue;
                const colorNoise = hash2(cx + dx, cz + dz + dy * 11, seed + 3500);
                placeLeaf(cx + dx, cy + dy, cz + dz, colorNoise);
            }
        }
    }
}

/**
 * Главная функция — сгенерировать дерево.
 *
 * @param {function(x,y,z,matId):void} setVoxelFn — колбэк постановки voxel
 * @param {number} tx — voxel-x ствола
 * @param {number} topY — voxel-y поверхности (1-я "земля" под деревом)
 * @param {number} tz — voxel-z ствола
 * @param {'oak'|'birch'|'autumn'} type
 * @param {number} sizeScale — 0.3..5.0
 * @param {number} seed — детерминизирует форму. Дай разные значения для разных деревьев.
 */
export function placeVoxelTree(setVoxelFn, tx, topY, tz, type, sizeScale = 1.0, seed = 12345) {
    const r1 = hash2(tx, tz, seed + 2001);
    const r2 = hash2(tx, tz, seed + 2002);
    const r3 = hash2(tx, tz, seed + 2003);
    const scale = Math.max(0.3, Math.min(5.0, sizeScale));

    let primaryTrunk, secondaryTrunk;
    let primaryLeaf, secondaryLeaf;
    let secondaryLeafChance;
    let trunkH, crownR, crownSquish;
    let trunkThickness;

    if (type === 'birch') {
        primaryTrunk = 'trunk_white';
        secondaryTrunk = 'trunk_white';
        primaryLeaf = 'leaves';
        secondaryLeaf = 'leaves_orange';
        secondaryLeafChance = 0.10;
        trunkH = Math.round((10 + r1 * 4) * scale);
        crownR = Math.round(3 * scale);
        crownSquish = 1.5;
        trunkThickness = scale < 1.5 ? 0 : 1;
    } else if (type === 'autumn') {
        primaryTrunk = 'trunk';
        secondaryTrunk = 'wood';
        primaryLeaf = 'leaves_orange';
        secondaryLeaf = 'leaves';
        secondaryLeafChance = 0.25;
        trunkH = Math.round((9 + r1 * 4) * scale);
        crownR = Math.round((4 + r2 * 2) * scale);
        crownSquish = 0.85;
        trunkThickness = scale < 0.8 ? 0 : scale < 1.5 ? 1 : scale < 2.5 ? 2 : 3;
    } else {
        // oak (default)
        primaryTrunk = 'trunk';
        secondaryTrunk = 'wood';
        primaryLeaf = 'leaves';
        secondaryLeaf = 'leaves_orange';
        secondaryLeafChance = 0.07;
        trunkH = Math.round((8 + r1 * 4) * scale);
        crownR = Math.round((3 + r2 * 2) * scale);
        crownSquish = 1.0;
        trunkThickness = scale < 0.8 ? 0 : scale < 1.5 ? 1 : scale < 2.5 ? 2 : 3;
    }
    if (trunkH < 3) trunkH = 3;
    if (crownR < 2) crownR = 2;

    const placeLeaf = (px, py, pz, noise) => {
        const isSecondary = noise < secondaryLeafChance;
        setVoxelFn(px, py, pz, isSecondary ? secondaryLeaf : primaryLeaf);
    };
    const placeTrunk = (px, py, pz, noise) => {
        const isSecondary = (type !== 'birch') && noise < 0.05;
        setVoxelFn(px, py, pz, isSecondary ? secondaryTrunk : primaryTrunk);
    };

    // Ствол с сужением
    const taperStart = Math.floor(trunkH * 0.7);
    for (let dy = 1; dy <= trunkH; dy++) {
        const thicknessAtY = dy < taperStart
            ? trunkThickness
            : Math.max(0, trunkThickness - Math.floor((dy - taperStart) * 0.5));
        const offsets = trunkOffsets(thicknessAtY);
        for (const [ox, oz] of offsets) {
            const noise = hash2(tx + ox * 13, tz + oz * 17 + dy * 7, seed + 2100);
            placeTrunk(tx + ox, topY + dy, tz + oz, noise);
        }
    }

    // Корни у основания (thickness >= 2)
    if (trunkThickness >= 2) {
        const baseH = Math.min(3, Math.floor(trunkH / 5));
        for (let dy = 1; dy <= baseH; dy++) {
            for (const [ox, oz] of [[2, 0], [-2, 0], [0, 2], [0, -2], [1, 1], [-1, -1], [1, -1], [-1, 1]]) {
                if (hash2(tx + ox * 13, tz + oz * 17 + dy, seed + 2200) > 0.55) continue;
                placeTrunk(tx + ox, topY + dy, tz + oz, 0.5);
            }
        }
    }

    // Ветви — зигзаг
    const branchTips = [];
    const numBranches = type === 'birch'
        ? (scale < 1.5 ? 2 : 3)
        : Math.max(2, Math.min(8, Math.round((3 + r3 * 2) * Math.sqrt(scale))));

    const branchLenBase = Math.max(2, Math.floor(crownR * 0.8 + scale));
    for (let b = 0; b < numBranches; b++) {
        const baseAngle = (b / numBranches) * Math.PI * 2;
        const angleJitter = (hash2(tx + b * 31, tz + b * 53, seed + 2300) - 0.5) * 0.6;
        const angle = baseAngle + angleJitter + r1 * 0.5;
        const startYFrac = 0.5 + (b / numBranches) * 0.45;
        const branchStartY = Math.max(2, Math.floor(trunkH * startYFrac));
        const branchLen = branchLenBase + Math.floor(hash2(tx + b, tz, seed + 2400) * 3);

        let bx = tx, by = topY + branchStartY, bz = tz;
        const dirX = Math.cos(angle);
        const dirZ = Math.sin(angle);
        for (let step = 1; step <= branchLen; step++) {
            bx = Math.round(tx + dirX * step);
            bz = Math.round(tz + dirZ * step);
            const upBias = step * 0.4;
            const jitterY = (hash2(bx * 7, bz * 11, seed + 2500 + step) - 0.3) * 1.5;
            by = topY + branchStartY + Math.floor(upBias + jitterY);

            const noise = hash2(bx, bz + step * 3, seed + 2310);
            placeTrunk(bx, by, bz, noise);

            if (trunkThickness >= 2 && step <= branchLen / 2) {
                const sx = Math.round(bx - dirZ * 0.7);
                const sz = Math.round(bz + dirX * 0.7);
                if (sx !== bx || sz !== bz) {
                    placeTrunk(sx, by, sz, noise + 0.1);
                }
            }
        }
        branchTips.push({ x: bx, y: by, z: bz });
    }

    // Главная крона
    const crownCy = topY + trunkH;
    const mainCrownR = Math.max(2, Math.floor(crownR * 0.85));
    placeCrown(setVoxelFn, tx, crownCy, tz, mainCrownR, crownSquish, placeLeaf, seed);

    // Кроны на ветвях
    const subCrownRBase = Math.max(2, Math.floor(crownR * 0.6));
    for (const tip of branchTips) {
        const sizeJitter = hash2(tip.x, tip.z, seed + 2600) * 0.4 - 0.2;
        const subR = Math.max(2, Math.round(subCrownRBase * (1.0 + sizeJitter)));
        placeCrown(setVoxelFn, tip.x, tip.y + 1, tip.z, subR, crownSquish, placeLeaf, seed);
    }
}

/** Все доступные виды деревьев — для случайного выбора в plant-кисти. */
export const TREE_TYPES = ['oak', 'birch', 'autumn'];
