/**
 * GdGroundSkin — 3D-травинки, 3D-цветы и неоновая полоса для GD-уровней.
 *
 * Этап G2 плана RUBLOX_GD_GRAPHICS_PLAN.md (v2 — настоящие 3D-меши).
 *
 *   1. Grass tufts — пучки травы из 2-3 пересекающихся текстурированных
 *      плоскостей. Через thin instances ~600 пучков вдоль трассы.
 *      Анимация: лёгкое колебание sin как от ветра.
 *   2. Flowers — 3D-цветы (стебель + 5-лепестковая шапка) ~80 шт.
 *   3. Neon edge — тонкая светящаяся полоса вдоль края пола.
 *
 * Использование:
 *   const gs = new GdGroundSkin();
 *   gs.attach(scene, levelWidth);
 *   gs.dispose();
 */
import {
    MeshBuilder, StandardMaterial, Color3, Color4, Vector3, Matrix, Quaternion,
    DynamicTexture, Mesh, VertexData,
} from '@babylonjs/core';

const NEON_EDGE_Y = 0.55;
const NEON_EDGE_Z = -1.51;
const GROUND_TOP_Y = 1.0;   // верх grass-блока (блоки лежат от y=0 до y=1)
const TUFT_DENSITY = 1.2;   // ~1.2 пучка на метр X
const FLOWER_DENSITY = 0.15;

export class GdGroundSkin {
    constructor() {
        this.scene = null;
        this._grassTuftsProto = null;        // прототип меша пучка
        this._grassMatrices = null;          // Float32Array с матрицами всех инстансов
        this._grassCount = 0;
        this._grassBaseRotations = null;     // Float32Array — базовый угол колебания для каждого инстанса
        this._grassBasePos = null;           // [{x,y,z,scale,baseRotY}] для пересчёта матриц при ветре
        this._flowersProto = null;
        this._neonEdge = null;
        this._neonEdgeBack = null;
        this._onBeforeRender = null;
        this._windT = 0;
    }

    attach(scene, levelWidth = 1000, shadowGenerator = null, scene3d = null) {
        if (!scene) return;
        this.scene = scene;
        this._scene3d = scene3d;
        this._createGrassTufts(levelWidth);
        this._createFlowers(levelWidth);
        this._createFakeShadows(levelWidth);
        this._createPits(levelWidth);
        this._setupWind();
    }

    /** Сегменты bottom-cover ПОД grass-блоками + НЕТ ничего над ямами.
     *  Так в ямах ничего не загораживает — видна голубое небо/skybox (бездна). */
    _createPits(levelWidth) {
        const floorXs = this._collectFloorXs();
        if (floorXs.size === 0) return;
        const minX = Math.min(...floorXs);
        const maxX = Math.max(...floorXs);
        // Найти непрерывные сегменты пола (где блоки ЕСТЬ)
        const segments = [];
        let segStart = null;
        for (let x = minX; x <= maxX + 1; x++) {
            if (floorXs.has(x)) {
                if (segStart == null) segStart = x;
            } else {
                if (segStart != null) {
                    segments.push({ start: segStart, end: x - 1 });
                    segStart = null;
                }
            }
        }
        if (segments.length === 0) return;

        // Тёмный материал «земля под полом»
        const mat = new StandardMaterial('gd_undersole_mat', this.scene);
        mat.diffuseColor = new Color3(0.30, 0.20, 0.12);
        mat.emissiveColor = new Color3(0.25, 0.17, 0.10);
        mat.specularColor = new Color3(0, 0, 0);
        mat.disableLighting = true;
        this._pitMat = mat;
        this._pits = [];

        const COVER_DEPTH = 200;   // от y=0 далеко вниз
        for (const s of segments) {
            const w = s.end - s.start + 1;
            const cx = (s.start + s.end) / 2;
            // Сегмент = вертикальная стенка под полом этого сегмента.
            // Низ — далеко вниз (закрывает низ экрана), верх — y=0 (низ grass).
            const mesh = MeshBuilder.CreateBox(`gd_undersole_${s.start}_${s.end}`, {
                width: w, height: COVER_DEPTH, depth: 0.2,
            }, this.scene);
            mesh.position.set(cx, -COVER_DEPTH / 2, -1.55);
            mesh.material = mat;
            mesh.isPickable = false;
            mesh.applyFog = false;
            this._pits.push(mesh);
        }
        console.log(`[GdGroundSkin] floor segments: ${segments.length}, gaps: ${segments.length - 1}`);
    }

    /** Синтетические тени:
     *  - один follow-кружок под игроком (двигается каждый кадр).
     *  - статические кружки под каждым шипом/cone primitive.
     *  Это плоские круги тёмного цвета чуть выше пола (y=1.01). */
    _createFakeShadows(levelWidth) {
        const dt = this._makeShadowTexture();
        const mat = new StandardMaterial('gd_shadow_mat', this.scene);
        mat.diffuseTexture = dt;
        mat.diffuseTexture.hasAlpha = true;
        mat.emissiveTexture = dt;
        mat.emissiveColor = new Color3(0, 0, 0);
        mat.useAlphaFromDiffuseTexture = true;
        mat.backFaceCulling = false;
        mat.specularColor = new Color3(0, 0, 0);
        mat.disableLighting = true;
        mat.alphaMode = 2;   // BABYLON.Engine.ALPHA_COMBINE
        this._shadowMat = mat;

        // 1. Тень игрока — следует за ним
        const playerShadow = MeshBuilder.CreateGround('gd_player_shadow', {
            width: 1.4, height: 1.4,
        }, this.scene);
        playerShadow.position.y = 1.01;
        playerShadow.material = mat;
        playerShadow.isPickable = false;
        playerShadow.renderingGroupId = 0;
        this._playerShadow = playerShadow;

        // 2. Тени под примитивами (cone/sphere/box)
        const pm = this._scene3d?.primitiveManager;
        const staticShadows = [];
        // ID куба игрока — он двигается, тень делается через _playerShadow.
        // В GD-уровнях скрипт использует REF_BODY = 'primitive:10001'.
        const PLAYER_CUBE_ID = 10001;
        if (pm) {
            for (const data of pm.instances.values()) {
                if (typeof data.x !== 'number') continue;
                if (data.id === PLAYER_CUBE_ID) continue;   // не дублируем тень игрока
                const type = String(data.type || '');
                let size = 1.2;
                if (type === 'cone') size = 1.1;
                else if (type === 'sphere') size = 1.0;
                else if (type === 'cube') size = 1.4;
                else continue;
                // Пропускаем кубы лежащие на y=0 (это декоративный пол)
                if (type === 'cube' && data.y === 0) continue;
                const sh = MeshBuilder.CreateGround(`gd_shadow_${data.id || Math.random()}`, {
                    width: size, height: size,
                }, this.scene);
                sh.position.set(data.x, 1.01, data.z || 0);
                sh.material = mat;
                sh.isPickable = false;
                staticShadows.push(sh);
            }
        }
        this._staticShadows = staticShadows;
        console.log(`[GdGroundSkin] fake shadows: 1 player + ${staticShadows.length} static`);
    }

    /** Canvas с радиальным градиентом — мягкий тёмный круг. */
    _makeShadowTexture() {
        const S = 128;
        const dt = new DynamicTexture('gd_shadow_tex', { width: S, height: S }, this.scene, true);
        const ctx = dt.getContext();
        ctx.clearRect(0, 0, S, S);
        const grad = ctx.createRadialGradient(S / 2, S / 2, 0, S / 2, S / 2, S / 2);
        grad.addColorStop(0.0, 'rgba(0,0,0,0.55)');
        grad.addColorStop(0.6, 'rgba(0,0,0,0.25)');
        grad.addColorStop(1.0, 'rgba(0,0,0,0)');
        ctx.fillStyle = grad;
        ctx.beginPath();
        ctx.arc(S / 2, S / 2, S / 2, 0, Math.PI * 2);
        ctx.fill();
        dt.hasAlpha = true;
        dt.update();
        return dt;
    }

    /** 3D-пучки травы вдоль уровня через thin instances. */
    _createGrassTufts(levelWidth) {
        const proto = this._buildTuftProto();
        this._grassTuftsProto = proto;

        // Собираем множество x-координат где есть пол (y=0). Трава ставится
        // только там — над пропастями (дырами) её не будет.
        const floorXs = this._collectFloorXs();
        const hasFloorAt = (xWorld) => {
            // По текущим z пол узкий (-1..1). Проверяем x округлённо.
            const x = Math.round(xWorld);
            return floorXs.has(x);
        };

        const count = Math.floor(levelWidth * TUFT_DENSITY);
        const matrices = new Float32Array(count * 16);
        const basePos = new Array(count);
        let n = 0;
        for (let i = 0; i < count; i++) {
            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 / count) * levelWidth + (xRand - 0.5) * (1 / TUFT_DENSITY);
            const z = (zRand - 0.5) * 2.4;
            if (!hasFloorAt(x)) continue;   // нет пола → пропуск (дыра/пропасть)
            const scale = 0.55 + sRand * 0.6;
            const baseRotY = yawRand * Math.PI * 2;
            basePos[n] = { x, z, scale, baseRotY, phaseSeed: i };
            n++;
        }
        this._grassBasePos = basePos.slice(0, n);
        // re-allocate matrices под реальное n (могло быть меньше count)
        this._grassMatrices = new Float32Array(n * 16);
        this._grassCount = n;

        this._refreshGrassMatrices(0);
        proto.thinInstanceSetBuffer('matrix', this._grassMatrices, 16, false);
        proto.thinInstanceCount = n;
    }

    /** Собрать множество x-координат, где есть блок на y=0 (пол).
     *  Используется и для травы, и для цветов — не сеем над пропастями. */
    _collectFloorXs() {
        const set = new Set();
        const bm = this._scene3d?.blockManager;
        if (!bm || !bm.blocks) return set;
        for (const key of bm.blocks.keys()) {
            const parts = String(key).split(',');
            const x = parseInt(parts[0], 10);
            const y = parseInt(parts[1], 10);
            if (Number.isFinite(x) && y === 0) set.add(x);
        }
        return set;
    }

    /** Построить mesh-пучок — 3 пересекающиеся текстурированные плоскости. */
    _buildTuftProto() {
        // Текстура одного «листа» травы: тонкий вертикальный градиент с прозрачностью
        const TW = 64, TH = 64;
        const dt = new DynamicTexture('gd_tuft_tex', { width: TW, height: TH }, this.scene, true);
        const ctx = dt.getContext();
        ctx.clearRect(0, 0, TW, TH);
        // Несколько «травинок» в одном тайле
        const blades = [
            { x: 12, color: '#3a8a3a' },
            { x: 22, color: '#5acc5a' },
            { x: 32, color: '#88dd55' },
            { x: 42, color: '#5acc5a' },
            { x: 52, color: '#3a8a3a' },
        ];
        for (const b of blades) {
            const grad = ctx.createLinearGradient(b.x, TH, b.x, 0);
            grad.addColorStop(0, b.color);
            grad.addColorStop(1, '#88dd55');
            ctx.strokeStyle = grad;
            ctx.lineWidth = 4;
            ctx.lineCap = 'round';
            ctx.beginPath();
            ctx.moveTo(b.x, TH - 2);
            ctx.quadraticCurveTo(b.x + 3, TH * 0.4, b.x + (b.x % 2 ? 4 : -4), 6);
            ctx.stroke();
        }
        dt.hasAlpha = true;
        dt.update();

        const mat = new StandardMaterial('gd_tuft_mat', this.scene);
        mat.diffuseTexture = dt;
        mat.diffuseTexture.hasAlpha = true;
        mat.emissiveTexture = dt;
        mat.emissiveColor = new Color3(0.55, 0.55, 0.55);
        mat.useAlphaFromDiffuseTexture = true;
        mat.backFaceCulling = false;
        mat.specularColor = new Color3(0, 0, 0);
        mat.transparencyMode = 1;   // ALPHATEST — нет issue с порядком сортировки

        // Меш — 3 квадрата, повёрнутые на 0°, 60°, 120° вокруг Y, склеенные в один
        const proto = this._makeCrossPlanes('gd_tuft_proto', 0.32, 0.35);
        proto.material = mat;
        proto.isPickable = false;
        return proto;
    }

    /** Создать меш из 3-х пересекающихся плоскостей (cross-billboard). */
    _makeCrossPlanes(name, halfWidth, height) {
        const positions = [];
        const indices = [];
        const uvs = [];
        const normals = [];
        const blades = 3;
        for (let i = 0; i < blades; i++) {
            const ang = (i / blades) * Math.PI;   // 0°, 60°, 120°
            const cs = Math.cos(ang), sn = Math.sin(ang);
            const baseIdx = positions.length / 3;
            // 4 угла: bottom-left, bottom-right, top-right, top-left
            const corners = [
                [-halfWidth, 0,   0],
                [ halfWidth, 0,   0],
                [ halfWidth, height, 0],
                [-halfWidth, height, 0],
            ];
            for (const c of corners) {
                const x = c[0] * cs;
                const z = c[0] * sn;
                positions.push(x, c[1], z);
                normals.push(-sn, 0, cs);
            }
            uvs.push(0, 0, 1, 0, 1, 1, 0, 1);
            indices.push(baseIdx, baseIdx + 1, baseIdx + 2);
            indices.push(baseIdx, baseIdx + 2, baseIdx + 3);
            // Обратная сторона (для двусторонних листьев)
            indices.push(baseIdx, baseIdx + 2, baseIdx + 1);
            indices.push(baseIdx, baseIdx + 3, baseIdx + 2);
        }
        const mesh = new Mesh(name, this.scene);
        const vd = new VertexData();
        vd.positions = positions;
        vd.indices = indices;
        vd.uvs = uvs;
        vd.normals = normals;
        vd.applyToMesh(mesh, false);
        return mesh;
    }

    /** Пересчитать матрицы инстансов травы с учётом ветра (windT). */
    _refreshGrassMatrices(windT) {
        const m = this._grassMatrices;
        const items = this._grassBasePos;
        if (!m || !items) return;
        const tmp = new Matrix();
        for (let i = 0; i < items.length; i++) {
            const it = items[i];
            // Лёгкое колебание: tilt + yaw чуть-чуть
            const phase = it.phaseSeed * 0.13;
            const tilt = Math.sin(windT * 1.6 + phase) * 0.12;
            const yaw = it.baseRotY + Math.sin(windT * 0.7 + phase * 1.3) * 0.03;
            const q = Quaternion.RotationYawPitchRoll(yaw, 0, tilt);
            const s = new Vector3(it.scale, it.scale, it.scale);
            const p = new Vector3(it.x, GROUND_TOP_Y, it.z);
            Matrix.ComposeToRef(s, q, p, tmp);
            tmp.copyToArray(m, i * 16);
        }
    }

    /** 3D-цветы — стебель + 5-лепестковая шляпка. */
    _createFlowers(levelWidth) {
        const proto = this._buildFlowerProto();
        this._flowersProto = proto;
        const floorXs = this._collectFloorXs();
        const hasFloorAt = (xWorld) => floorXs.has(Math.round(xWorld));
        const want = Math.floor(levelWidth * FLOWER_DENSITY);
        const items = [];
        for (let i = 0; i < want; i++) {
            const xRand = Math.abs((Math.sin(i * 12.9898 + 100) * 43758)) % 1;
            const zRand = Math.abs((Math.sin(i * 78.233 + 200) * 43758)) % 1;
            const sRand = Math.abs((Math.sin(i * 41.21 + 300) * 43758)) % 1;
            const yawRand = Math.abs((Math.sin(i * 23.45 + 400) * 43758)) % 1;
            const x = (i / want) * levelWidth + (xRand - 0.5) * (1 / FLOWER_DENSITY);
            const z = (zRand - 0.5) * 2.4;
            if (!hasFloorAt(x)) continue;
            items.push({ x, z, scale: 0.45 + sRand * 0.45, yaw: yawRand * Math.PI * 2 });
        }
        const matrices = new Float32Array(items.length * 16);
        const tmp = new Matrix();
        for (let i = 0; i < items.length; i++) {
            const it = 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, GROUND_TOP_Y, it.z);
            Matrix.ComposeToRef(s, q, p, tmp);
            tmp.copyToArray(matrices, i * 16);
        }
        proto.thinInstanceSetBuffer('matrix', matrices, 16, true);
        proto.thinInstanceCount = items.length;
    }

    _buildFlowerProto() {
        // Текстура: 5-лепестковый цветок + желтый центр
        const T = 64;
        const dt = new DynamicTexture('gd_flower_tex', { width: T, height: T }, this.scene, true);
        const ctx = dt.getContext();
        ctx.clearRect(0, 0, T, T);
        // Случайно выберем один из цветов — но раз нужны разные цветы, нарисуем сразу несколько
        // и выберем через UV. Простой путь: один тип цветка (розовый) — позже можно расширить.
        const cx = T / 2, cy = T / 2;
        const petalR = 18, centerR = 7;
        ctx.fillStyle = '#ff6b9b';
        for (let p = 0; p < 5; p++) {
            const ang = (p / 5) * Math.PI * 2 - Math.PI / 2;
            const px = cx + Math.cos(ang) * 13;
            const py = cy + Math.sin(ang) * 13;
            ctx.beginPath();
            ctx.arc(px, py, petalR / 2, 0, Math.PI * 2);
            ctx.fill();
        }
        ctx.fillStyle = '#ffe44a';
        ctx.beginPath();
        ctx.arc(cx, cy, centerR, 0, Math.PI * 2);
        ctx.fill();
        dt.hasAlpha = true;
        dt.update();

        const mat = new StandardMaterial('gd_flower_mat', this.scene);
        mat.diffuseTexture = dt;
        mat.diffuseTexture.hasAlpha = true;
        mat.emissiveTexture = dt;
        mat.emissiveColor = new Color3(0.7, 0.7, 0.7);
        mat.useAlphaFromDiffuseTexture = true;
        mat.backFaceCulling = false;
        mat.specularColor = new Color3(0, 0, 0);
        mat.transparencyMode = 1;

        // Цветок = 2 крест-плоскости (поменьше, повыше)
        const proto = this._makeCrossPlanes('gd_flower_proto', 0.25, 0.35);
        proto.material = mat;
        proto.isPickable = false;
        return proto;
    }

    /** Анимация ветра + follow-тень игрока. */
    _setupWind() {
        let frame = 0;
        this._onBeforeRender = () => {
            frame++;
            // Тень игрока — каждый кадр (player._pos живой, в отличие от .position)
            const pp = this._scene3d?.player?._pos;
            if (this._playerShadow && pp) {
                this._playerShadow.position.x = pp.x;
                this._playerShadow.position.z = pp.z || 0;
                const h = Math.max(0, pp.y - 1);
                const visScale = Math.max(0.6, 1 - h * 0.12);
                this._playerShadow.scaling.x = visScale;
                this._playerShadow.scaling.z = visScale;
            }
            // Ветер — раз в 3 кадра
            if (frame % 3 !== 0) return;
            this._windT += 0.05;
            this._refreshGrassMatrices(this._windT);
            if (this._grassTuftsProto && this._grassMatrices) {
                this._grassTuftsProto.thinInstanceBufferUpdated('matrix');
            }
        };
        this.scene.onBeforeRenderObservable.add(this._onBeforeRender);
    }

    /** Светящаяся зелёная полоса по краю пола (передний и задний). */
    _createNeonEdge(levelWidth) {
        const W = levelWidth + 40;
        const H = 0.12;   // тонкая
        // Передний край (z = -1.51)
        const front = MeshBuilder.CreateBox('gd_neon_edge_front', {
            width: W,
            height: H,
            depth: 0.05,
        }, this.scene);
        front.position = new Vector3(levelWidth / 2 - 10, NEON_EDGE_Y + 0.45, NEON_EDGE_Z);
        const matF = new StandardMaterial('gd_neon_edge_mat', this.scene);
        matF.diffuseColor = new Color3(0.13, 1, 0.4);
        matF.emissiveColor = new Color3(0.13, 1, 0.4);
        matF.disableLighting = true;
        front.material = matF;
        front.isPickable = false;
        front.applyFog = false;
        this._neonEdge = front;

        // Задний край (z = 1.51) — на всякий случай если камера сверху/сбоку
        const back = MeshBuilder.CreateBox('gd_neon_edge_back', {
            width: W,
            height: H,
            depth: 0.05,
        }, this.scene);
        back.position = new Vector3(levelWidth / 2 - 10, NEON_EDGE_Y + 0.45, 1.51);
        back.material = matF;
        back.isPickable = false;
        back.applyFog = false;
        this._neonEdgeBack = back;
    }

    dispose() {
        if (!this.scene) return;
        try {
            if (this._onBeforeRender) {
                this.scene.onBeforeRenderObservable.removeCallback(this._onBeforeRender);
                this._onBeforeRender = null;
            }
        } catch (e) {}
        const all = [
            this._grassTuftsProto, this._flowersProto, this._neonEdge, this._neonEdgeBack,
            this._playerShadow, ...(this._staticShadows || []), ...(this._pits || []),
        ];
        for (const m of all) {
            if (m) {
                try { m.material?.dispose(true, true); } catch (e) {}
                try { m.dispose(); } catch (e) {}
            }
        }
        this._grassTuftsProto = this._flowersProto = this._neonEdge = this._neonEdgeBack = null;
        this._playerShadow = null;
        this._staticShadows = null;
        this._scene3d = null;
        this._grassMatrices = null;
        this._grassBasePos = null;
        this.scene = null;
    }
}
