/**
 * GdForest — расстановка декораций ландшафта в GD-уровне (этап G6 v2).
 *
 * Загружает GLB-модели из gd_deco_choices (выбор юзера в /admin-preview/gd-deco)
 * для текущей эпохи, расставляет их вдоль уровня случайно из выбранного набора.
 *
 * - Дальние позиции (z=8..22) — крупные деревья/камни.
 * - Ближние (z=-3..-7) — мелкие, мешают видимости меньше.
 * - Через thin-instances: 1 draw call на тип модели.
 */
import {
    Mesh, MeshBuilder, SceneLoader, Matrix, Quaternion, Vector3, Color3,
    StandardMaterial, DynamicTexture, VertexData, Ray,
} from '@babylonjs/core';
import '@babylonjs/loaders/glTF';
import { DECO_CATALOG } from '../../admin-preview/gdDeco/decoFactories';
import { RobloxTerrain } from './robloxterrain/RobloxTerrain';
import { DensityGrid, CELL_SIZE } from './robloxterrain/DensityGrid';
import { applyBrush } from './robloxterrain/SmoothBrushes';

const ASSET_ROOT = '/kubikon-assets/models/nature-kit/';

// Дефолтный выбор по эпохе (выбор юзера сохранён в gd_deco_choices).
const DEFAULT_DECO_BY_EPOCH = {
    1: ['d1_v1', 'd1_v2', 'd1_v5', 'd1_v6', 'd1_v10'],
    2: ['d2_v1', 'd2_v3', 'd2_v5', 'd2_v7', 'd2_v9'],   // 5 видов сосен
    3: ['d3_v8', 'd3_v10', 'd3_v7', 'd3_v4', 'd3_v5'],
    4: ['d4_v4', 'd4_v1', 'd4_v2', 'd4_v8', 'd4_v7'],
    5: ['d5_v4', 'd5_v2', 'd5_v1', 'd5_v8', 'd5_v5'],
    6: ['d6_v5', 'd6_v6', 'd6_v8', 'd6_v9', 'd6_v10'],
    7: ['d7_v3', 'd7_v4', 'd7_v5', 'd7_v9', 'd7_v10'],
    8: ['d8_v5', 'd8_v4', 'd8_v3', 'd8_v2', 'd8_v9'],
    9: ['d9_v5', 'd9_v4', 'd9_v3', 'd9_v2', 'd9_v1'],
};

// Кол-во инстансов на тип модели — только дальний план, перед камерой пусто.
// В low-quality режиме уменьшаем в 2 раза для FPS.
const _quality = (typeof localStorage !== 'undefined' && localStorage.getItem('gd_graphics_quality')) || 'high';
const FAR_TOTAL = _quality === 'low' ? 40 : 80;
const FAR_Z_MIN = 8;
const FAR_Z_MAX = 22;
// Y базы — корни врастают ниже верха пола (отрицательное Y), чтобы дерево
// сидело глубже в траве и не «парило».
const BASE_Y = -0.5;
// Глобальный коэффициент масштаба (уменьшение в 1.4 раза от каталога).
const SCALE_MULT = 0.7;

export class GdForest {
    constructor() {
        this.scene = null;
        this._levelWidth = 1000;
        this._protos = [];          // [{ mesh, items }]
        this._onBeforeRender = null;
        this._t = 0;
    }

    /**
     * @param scene Babylon scene
     * @param levelWidth ширина уровня в метрах
     * @param epoch номер эпохи (1..10)
     * @param decoIds массив id моделей (если не задан — берём DEFAULT_DECO_BY_EPOCH)
     */
    async attach(scene, levelWidth = 1000, epoch = 1, decoIds = null) {
        if (!scene) return;
        this.scene = scene;
        this._levelWidth = levelWidth;
        this._epoch = epoch;
        const ids = decoIds || DEFAULT_DECO_BY_EPOCH[epoch] || [];
        if (ids.length === 0) {
            console.log('[GdForest] нет выбранных декораций для эпохи', epoch);
            return;
        }
        // Загружаем все GLB параллельно
        const loaded = await Promise.all(ids.map((id) => this._loadDecoProto(id)));
        const valid = loaded.filter((x) => x && x.proto);
        if (valid.length === 0) {
            console.warn('[GdForest] ни одна модель не загрузилась');
            return;
        }
        this._protos = valid;
        this._createForestGround();
        this._distributeInstances();
        this._setupWind();
        console.log(`[GdForest] загружено ${valid.length}/${ids.length} моделей для эпохи ${epoch}`);
    }

    /** Гладкий 3D-ландшафт «лесная поляна» через RobloxTerrain (как в редакторе).
     *  Скульптим холмы вдоль трассы и просим RobloxTerrain построить все chunks. */
    _createForestGround() {
        // === 1. DensityGrid под лесную полосу за трассой ===
        const Z_NEAR = FAR_Z_MIN - 4;
        const Z_FAR = FAR_Z_MAX + 8;
        const xCells = Math.ceil((this._levelWidth + 60) / CELL_SIZE);
        const zCells = Math.ceil((Z_FAR - Z_NEAR) / CELL_SIZE);
        const yCells = 8;
        const originX = Math.floor((-30) / CELL_SIZE);
        const originY = Math.floor(-12 / CELL_SIZE);
        const originZ = Math.floor(Z_NEAR / CELL_SIZE);
        const grid = new DensityGrid({
            origin: { x: originX, y: originY, z: originZ },
            size: { x: xCells, y: yCells, z: zCells },
        });
        // === 2. Создаём RobloxTerrain и используем applyBrushAndRebuild ===
        const rt = new RobloxTerrain(this.scene);
        rt.loadFromGrid(grid, { skipEmpty: true });
        const Z_CENTER = (FAR_Z_MIN + FAR_Z_MAX) / 2;
        // Материал ландшафта по эпохе (соответствует TERRAIN_MATERIALS).
        const TERRAIN_MAT_BY_EPOCH = {
            1: 'grass', 2: 'snow', 3: 'grass', 4: 'grass',
            5: 'sand', 6: 'sand', 7: 'rock', 8: 'rock', 9: 'rock', 10: 'rock',
        };
        const tMat = TERRAIN_MAT_BY_EPOCH[this._epoch] || 'grass';
        // Базовый «толстый слой грунта» вдоль всей полосы
        for (let x = -20; x <= this._levelWidth + 20; x += 12) {
            rt.applyBrushAndRebuild('sculptUp', {
                center: { x, y: -2, z: Z_CENTER },
                radius: 12, strength: 400, material: tMat,
            });
        }
        // Холмы повыше — реже, разной высоты
        const numHills = Math.floor(this._levelWidth / 25);
        for (let i = 0; i < numHills; i++) {
            const x = (i + 0.5) * 25 + Math.sin(i * 12.9) * 6;
            const z = Z_CENTER + Math.sin(i * 7.7) * 4;
            const yPeak = 1 + Math.abs(Math.sin(i * 3.3)) * 3;
            rt.applyBrushAndRebuild('sculptUp', {
                center: { x, y: yPeak, z },
                radius: 10 + Math.abs(Math.sin(i * 2.1)) * 4,
                strength: 350, material: tMat,
            });
        }
        // Сглаживание
        for (let x = -20; x <= this._levelWidth + 20; x += 18) {
            rt.applyBrushAndRebuild('smooth', {
                center: { x, y: 0, z: Z_CENTER },
                radius: 18, strength: 200,
            });
        }
        this._robloxTerrain = rt;
        console.log('[GdForest] RobloxTerrain создан, chunks=', rt.chunks?.size);
    }

    /** Canvas-текстура: трава/земля с цветами/пятнами. Тайлится. */
    _makeForestGroundTexture() {
        const W = 512, H = 256;
        const dt = new DynamicTexture('gd_forest_ground_tex', { width: W, height: H }, this.scene, true);
        const ctx = dt.getContext();
        // Базовый зелёный (с лёгким noise)
        for (let y = 0; y < H; y += 4) {
            for (let x = 0; x < W; x += 4) {
                const r = Math.abs((Math.sin(x * 0.13 + y * 0.21) * 43758)) % 1;
                const g = 90 + Math.floor(r * 50);
                const rd = 35 + Math.floor(r * 25);
                ctx.fillStyle = `rgb(${rd},${g},45)`;
                ctx.fillRect(x, y, 4, 4);
            }
        }
        // Тёмные пятна — мох / земля
        for (let i = 0; i < 60; i++) {
            const x = Math.abs((Math.sin(i * 12.9898) * 43758)) % W;
            const y = Math.abs((Math.sin(i * 78.233 + 1) * 43758)) % H;
            const r = 25 + (i % 7) * 6;
            const grad = ctx.createRadialGradient(x, y, 0, x, y, r);
            grad.addColorStop(0, 'rgba(50,30,15,0.45)');
            grad.addColorStop(1, 'rgba(50,30,15,0)');
            ctx.fillStyle = grad;
            ctx.beginPath();
            ctx.arc(x, y, r, 0, Math.PI * 2);
            ctx.fill();
        }
        // Светлые травянистые пятна
        for (let i = 0; i < 80; i++) {
            const x = Math.abs((Math.sin(i * 17.1 + 0.7) * 43758)) % W;
            const y = Math.abs((Math.sin(i * 91.3 + 1.1) * 43758)) % H;
            const r = 12 + (i % 5) * 4;
            const grad = ctx.createRadialGradient(x, y, 0, x, y, r);
            grad.addColorStop(0, 'rgba(120,200,80,0.55)');
            grad.addColorStop(1, 'rgba(120,200,80,0)');
            ctx.fillStyle = grad;
            ctx.beginPath();
            ctx.arc(x, y, r, 0, Math.PI * 2);
            ctx.fill();
        }
        dt.update();
        // Тайлим по X много раз — длина уровня большая
        dt.wrapU = 1; dt.wrapV = 1;
        dt.uScale = Math.ceil(this._levelWidth / 30);
        dt.vScale = 1;
        return dt;
    }

    /** Загрузить один прото-меш по id из DECO_CATALOG.
     *  Логика как в SmoothDecoManager: bake parent-chain (root scale/rotation
     *  из glTF) → merge → центрирование bbox. После этого инстансы можно
     *  ставить через простую TRS матрицу. */
    async _loadDecoProto(id) {
        const def = DECO_CATALOG.find((d) => d.id === id);
        if (!def) { console.warn('[GdForest] неизвестный deco-id:', id); return null; }
        try {
            const container = await SceneLoader.LoadAssetContainerAsync(
                ASSET_ROOT, def.file, this.scene,
            );
            container.addAllToScene();
            const allMeshes = container.meshes.slice();
            const geoMeshes = allMeshes.filter(
                (m) => m.getTotalVertices && m.getTotalVertices() > 0,
            );
            if (geoMeshes.length === 0) {
                console.warn('[GdForest]', def.file, 'нет геометрии');
                return null;
            }
            // Bake parent-chain (Y-up→Z-up + scale из glTF __root__) в вершины.
            for (const m of geoMeshes) {
                m.computeWorldMatrix(true);
                m.bakeCurrentTransformIntoVertices();
                m.parent = null;
                if (m.rotationQuaternion) m.rotationQuaternion = null;
                m.rotation.set(0, 0, 0);
                m.scaling.set(1, 1, 1);
                m.position.set(0, 0, 0);
            }
            // Уничтожаем вспомогательные node-ы (__root__)
            for (const m of allMeshes) {
                if (!geoMeshes.includes(m)) {
                    try { m.dispose(); } catch (e) {}
                }
            }
            let proto;
            if (geoMeshes.length === 1) {
                proto = geoMeshes[0];
            } else {
                proto = Mesh.MergeMeshes(geoMeshes, true, true, undefined, false, true);
                if (!proto) {
                    console.warn('[GdForest] merge failed', def.file);
                    return null;
                }
            }
            proto.name = `gd_deco_proto_${id}`;
            proto.isPickable = false;
            // Отключить туман — иначе дальние деревья бледнеют до серого
            proto.applyFog = false;
            // Центрируем XZ + поднимаем низ до Y=0
            proto.refreshBoundingInfo();
            const bb = proto.getBoundingInfo().boundingBox;
            const minY = bb.minimumWorld.y;
            const cx = (bb.minimumWorld.x + bb.maximumWorld.x) * 0.5;
            const cz = (bb.minimumWorld.z + bb.maximumWorld.z) * 0.5;
            const sizeY = bb.maximumWorld.y - bb.minimumWorld.y;
            if (Math.abs(minY) > 0.001 || Math.abs(cx) > 0.001 || Math.abs(cz) > 0.001) {
                proto.position.set(-cx, -minY, -cz);
                proto.bakeCurrentTransformIntoVertices();
                proto.position.set(0, 0, 0);
            }
            // Прото отключаем от рендера (рисуются только thin-instances)
            proto.isVisible = true;
            // Двусторонний рендер материала + лёгкая emissive-заливка
            // (как в SmoothDecoManager — иначе нижние грани чёрные)
            const meshesToFix = proto.subMeshes ? [proto] : geoMeshes;
            const seenMats = new Set();
            for (const m of meshesToFix) {
                const mat = m.material;
                if (!mat || seenMats.has(mat)) continue;
                seenMats.add(mat);
                try {
                    mat.backFaceCulling = false;
                    if ('twoSidedLighting' in mat) mat.twoSidedLighting = true;
                    const base = mat.albedoColor || mat.diffuseColor;
                    if (base && base.scale) {
                        mat.emissiveColor = base.scale(0.08);
                    }
                } catch (e) {}
            }
            console.log(`[GdForest] ${id} (${def.file}): sizeY=${sizeY.toFixed(2)}м, baseScale=${def.scale}`);
            return { proto, baseScale: def.scale || 1.0, items: [] };
        } catch (e) {
            console.warn('[GdForest] load failed', def.file, e);
            return null;
        }
    }

    _distributeInstances() {
        for (const proto of this._protos) {
            proto.items = [];
        }
        // FAR — только задний план. Передний план перед камерой пуст,
        // чтобы не закрывать обзор игроку.
        for (let i = 0; i < FAR_TOTAL; i++) {
            const protoIdx = i % this._protos.length;
            const proto = this._protos[protoIdx];
            const xRand = Math.abs((Math.sin(i * 12.9898) * 43758)) % 1;
            const zRand = Math.abs((Math.sin(i * 78.233 + 1.1) * 43758)) % 1;
            const sRand = Math.abs((Math.sin(i * 41.21 + 0.5) * 43758)) % 1;
            const yawRand = Math.abs((Math.sin(i * 23.45 + 2.7) * 43758)) % 1;
            const x = (i / FAR_TOTAL) * this._levelWidth + (xRand - 0.5) * 12;
            const z = FAR_Z_MIN + zRand * (FAR_Z_MAX - FAR_Z_MIN);
            const scale = proto.baseScale * SCALE_MULT * (0.85 + sRand * 0.35);
            const yaw = yawRand * Math.PI * 2;
            // Узнаём высоту террейна в (x,z) raycast'ом сверху-вниз — модель
            // ставится корнями на surface, а не «утопает» в склоне.
            const surfaceY = this._sampleTerrainHeight(x, z);
            const y = Number.isFinite(surfaceY) ? surfaceY - 0.1 : BASE_Y;
            proto.items.push({ x, y, z, scale, yaw, phaseSeed: i });
        }
        // Заливаем матрицы каждого прото в thin-instances
        for (const proto of this._protos) {
            const n = proto.items.length;
            const matrices = new Float32Array(n * 16);
            const tmp = new Matrix();
            for (let i = 0; i < n; i++) {
                const it = proto.items[i];
                const q = Quaternion.RotationYawPitchRoll(it.yaw, 0, 0);
                const s = new Vector3(it.scale, it.scale, it.scale);
                const p = new Vector3(it.x, it.y, it.z);
                Matrix.ComposeToRef(s, q, p, tmp);
                tmp.copyToArray(matrices, i * 16);
            }
            proto.proto.thinInstanceSetBuffer('matrix', matrices, 16, false);
            proto.proto.thinInstanceCount = n;
            proto.matrices = matrices;
        }
    }

    /** Опрос высоты террейна в точке (x,z) через raycast сверху вниз.
     *  Берём только меши RobloxTerrain (по имени-префиксу). */
    _sampleTerrainHeight(x, z) {
        if (!this._robloxTerrain) return NaN;
        const origin = new Vector3(x, 50, z);
        const direction = new Vector3(0, -1, 0);
        const ray = new Ray(origin, direction, 100);
        const hit = this.scene.pickWithRay(ray, (mesh) => {
            // меши RobloxTerrain имеют имя "__robloxMesh_..."
            const n = mesh.name || '';
            return n.startsWith('__robloxMesh_');
        });
        if (hit && hit.hit && hit.pickedPoint) return hit.pickedPoint.y;
        return NaN;
    }

    /** Лёгкое колебание моделей от ветра — каждые 4 кадра. */
    _setupWind() {
        let frame = 0;
        this._onBeforeRender = () => {
            frame++;
            if (frame % 4 !== 0) return;
            this._t += 0.04;
            for (const proto of this._protos) {
                if (!proto.matrices) continue;
                const tmp = new Matrix();
                for (let i = 0; i < proto.items.length; i++) {
                    const it = proto.items[i];
                    const phase = it.phaseSeed * 0.17;
                    const sway = Math.sin(this._t * 0.7 + phase) * 0.05;
                    const q = Quaternion.RotationYawPitchRoll(it.yaw, 0, sway);
                    const s = new Vector3(it.scale, it.scale, it.scale);
                    const p = new Vector3(it.x, it.y, it.z);
                    Matrix.ComposeToRef(s, q, p, tmp);
                    tmp.copyToArray(proto.matrices, i * 16);
                }
                proto.proto.thinInstanceBufferUpdated('matrix');
            }
        };
        this.scene.onBeforeRenderObservable.add(this._onBeforeRender);
    }

    dispose() {
        if (!this.scene) return;
        try {
            if (this._onBeforeRender) {
                this.scene.onBeforeRenderObservable.removeCallback(this._onBeforeRender);
                this._onBeforeRender = null;
            }
        } catch (e) {}
        for (const proto of this._protos) {
            try { proto.proto.dispose(false, true); } catch (e) {}
        }
        if (this._forestGround) {
            try { this._forestGround.material?.dispose(true, true); } catch (e) {}
            try { this._forestGround.dispose(); } catch (e) {}
            this._forestGround = null;
        }
        if (this._robloxTerrain) {
            try { this._robloxTerrain.disposeAll && this._robloxTerrain.disposeAll(); } catch (e) {}
            this._robloxTerrain = null;
        }
        this._protos = [];
        this.scene = null;
    }
}
