/**
 * GdSkybox — небо, параллакс-горы/лес и туман для GD-уровней.
 *
 * Этап G1 плана RUBLOX_GD_GRAPHICS_PLAN.md.
 *
 * Применяется только если в сцене есть primitive с kind=gd_finish (= GD-уровень).
 * Не трогает другие проекты.
 *
 * Что создаёт:
 *   1. Skybox — большой куб с canvas-текстурой градиента (утро→полдень).
 *   2. Дальние горы (z=80) — billboard с canvas-силуэтом гор. Двигается со скроллом ×0.2.
 *   3. Лес поближе (z=40) — canvas-силуэт леса. Двигается со скроллом ×0.5.
 *   4. Babylon fog — мягкое растворение дальних объектов.
 *
 * Использование:
 *   const gdSkybox = new GdSkybox();
 *   gdSkybox.attach(scene, sideviewCamera);
 *   // ... при выгрузке проекта:
 *   gdSkybox.dispose();
 */
import {
    MeshBuilder, StandardMaterial, Texture, Color3, Color4, Vector3,
    DynamicTexture,
} from '@babylonjs/core';

const SKYBOX_SIZE = 2000;
// Дальние горы — далеко по z, низ упирается в горизонт (y≈0).
const MOUNTAIN_Z = 220;
const MOUNTAIN_W = 600;
const MOUNTAIN_H = 80;
const MOUNTAIN_BOTTOM_Y = -5;   // низ за полом
// Лес — поближе, но всё равно очень далеко (чтобы не перекрывать игрока).
const FOREST_Z = 120;
const FOREST_W = 400;
const FOREST_H = 35;
const FOREST_BOTTOM_Y = -5;
const MOUNTAIN_SCROLL = 0.15;
const FOREST_SCROLL = 0.35;

export class GdSkybox {
    constructor() {
        this.scene = null;
        this.camera = null;
        this._skybox = null;
        this._mountains = null;
        this._forest = null;
        this._groundFiller = null;
        this._prevFogMode = 0;
        this._prevFogColor = null;
        this._prevFogDensity = 0;
        this._prevClearColor = null;
        this._onBeforeRender = null;
    }

    /** Подключить к сцене. camera нужна для параллакса (читаем .position.x). */
    attach(scene, camera) {
        if (!scene) return;
        this.scene = scene;
        this.camera = camera;

        // Запомним прежнее состояние, чтобы dispose откатил
        this._prevClearColor = scene.clearColor?.clone?.() || new Color4(0.5, 0.7, 0.9, 1);
        this._prevFogMode = scene.fogMode;
        this._prevFogColor = scene.fogColor?.clone?.() || new Color3(0.55, 0.7, 0.85);
        this._prevFogDensity = scene.fogDensity;

        // Чтобы за skybox-ом не было голой clearColor (полосы на швах),
        // ставим её под цвет неба.
        scene.clearColor = new Color4(0.55, 0.78, 0.92, 1);

        this._createSkybox();
        // _createBottomCover убран — теперь под полом сегменты bottom-cover
        // рисуются в GdGroundSkin (с дырками в местах ям, чтобы pit-mesh
        // спускались до низа экрана).
        this._createMountains();
        this._createForest();
        this._setupFog();
        this._setupParallax();
    }

    /** Вертикальная стенка ПОД полом — закрывает низ кадра.
     *  Верх на y=0 (низ grass-блоков), низ далеко вниз. */
    _createBottomCover() {
        const cover = MeshBuilder.CreateBox('gd_bottom_cover', {
            width: 4000, height: 500, depth: 0.2,
        }, this.scene);
        cover.position = new Vector3(0, -250, -1.55);
        const mat = new StandardMaterial('gd_bottom_cover_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;
        cover.material = mat;
        cover.isPickable = false;
        cover.applyFog = false;
        this._bottomCover = cover;
    }

    /** Темная плоскость ВНУТРИ области пола (z=-1.45..-1.5, y=0..1) — рисует
     *  «глубокую пропасть» в ямах. За grass-блоками не видна (она z=-1.5+ε
     *  чуть перед задней стенкой пола, но СЗАДИ передней стенки видимых блоков).
     *  ВНИМАНИЕ: пока не использую — оставлено для следующей итерации. */

    /** Плоскость-заглушка под лесом/полом — закрывает голубое «дно» skybox.
     *  Тёмно-зелёный/коричневый, имитирует уходящую к горизонту землю.
     *  ВАЖНО: ставим БЛИЖЕ камеры чем лес — иначе fog делает её бледно-серой. */
    _createGroundFiller() {
        const W = 16, H = 256;
        const dt = new DynamicTexture('gd_groundfill_tex', { width: W, height: H }, this.scene, false);
        const ctx = dt.getContext();
        const g = ctx.createLinearGradient(0, 0, 0, H);
        g.addColorStop(0.00, '#4a6a3a');   // верх — насыщенная зелень (вровень с травой)
        g.addColorStop(0.40, '#3a4a25');   // середина — землянистый
        g.addColorStop(1.00, '#2a3018');   // низ — тёмная земля
        ctx.fillStyle = g;
        ctx.fillRect(0, 0, W, H);
        dt.update();

        const mesh = MeshBuilder.CreatePlane('gd_groundfiller', {
            width: 800,
            height: 220,
        }, this.scene);
        // Z=15 — это БЛИЖЕ камеры чем лес (FOREST_Z=120). Туман сюда почти не достаёт.
        // Y=-111 → верх на y≈-1 (под полом который y=0), низ далеко внизу.
        mesh.position = new Vector3(0, -111, 15);
        const mat = new StandardMaterial('gd_groundfiller_mat', this.scene);
        mat.diffuseTexture = dt;
        mat.emissiveTexture = dt;
        mat.emissiveColor = new Color3(1.0, 1.0, 1.0);
        mat.disableLighting = true;
        mat.backFaceCulling = true;
        mesh.material = mat;
        // НЕ применять fog — иначе посереет
        mesh.applyFog = false;
        this._groundFiller = mesh;
    }

    /** Главный куб неба с canvas-градиентом. */
    _createSkybox() {
        const sky = MeshBuilder.CreateBox('gd_skybox', { size: SKYBOX_SIZE }, this.scene);
        sky.infiniteDistance = true;   // следует за камерой
        sky.applyFog = false;          // skybox не должен сам растворяться в тумане

        const mat = new StandardMaterial('gd_skybox_mat', this.scene);
        mat.backFaceCulling = false;
        mat.disableLighting = true;
        mat.diffuseColor = new Color3(0, 0, 0);

        // Эмиссивная текстура с градиентом — canvas через DynamicTexture
        const tex = this._makeSkyTexture();
        mat.emissiveTexture = tex;

        sky.material = mat;
        this._skybox = sky;
    }

    /** Canvas-градиент неба: верх — тёплое голубое, низ — бледное. */
    _makeSkyTexture() {
        const W = 512, H = 512;
        const dt = new DynamicTexture('gd_sky_tex', { width: W, height: H }, this.scene, false);
        const ctx = dt.getContext();
        const g = ctx.createLinearGradient(0, 0, 0, H);
        g.addColorStop(0.00, '#6db4e8');   // верх — насыщенно-голубой
        g.addColorStop(0.45, '#9ed1f0');   // середина — небо
        g.addColorStop(0.70, '#cfe6f4');   // горизонт — почти белый
        g.addColorStop(1.00, '#e8f4fb');   // низ — пастель (под линию горизонта)
        ctx.fillStyle = g;
        ctx.fillRect(0, 0, W, H);
        // Мягкое солнце в правом-верхнем углу
        const sunX = W * 0.78, sunY = H * 0.22, sunR = 60;
        const sg = ctx.createRadialGradient(sunX, sunY, 10, sunX, sunY, sunR);
        sg.addColorStop(0, 'rgba(255,255,230,0.95)');
        sg.addColorStop(1, 'rgba(255,255,230,0)');
        ctx.fillStyle = sg;
        ctx.beginPath();
        ctx.arc(sunX, sunY, sunR, 0, Math.PI * 2);
        ctx.fill();
        dt.update();
        return dt;
    }

    /** Дальние горы — billboard plane на горизонте. Низ упирается в землю. */
    _createMountains() {
        // Canvas — длинная горизонтальная полоса. Силуэт занимает только верх,
        // низ canvas прозрачный (это «небо» под горами, оно совпадает с skybox).
        const W = 2048, H = 512;
        const dt = new DynamicTexture('gd_mountains_tex', { width: W, height: H }, this.scene, false);
        const ctx = dt.getContext();
        ctx.clearRect(0, 0, W, H);
        // Силуэт двух слоёв: дальний (светлее) + ближний (темнее)
        this._drawMountainLayer(ctx, W, H, /*baseY*/ 0.55, '#94aec8', /*peaks*/ 10, /*amp*/ 120);
        this._drawMountainLayer(ctx, W, H, /*baseY*/ 0.72, '#6f88a4', /*peaks*/ 14, /*amp*/ 170);
        dt.hasAlpha = true;
        dt.update();

        const mesh = MeshBuilder.CreatePlane('gd_mountains', {
            width: MOUNTAIN_W,
            height: MOUNTAIN_H,
        }, this.scene);
        // Поднимем так, чтобы низ planes был на MOUNTAIN_BOTTOM_Y (= за полом)
        mesh.position = new Vector3(0, MOUNTAIN_BOTTOM_Y + MOUNTAIN_H / 2, MOUNTAIN_Z);

        const mat = new StandardMaterial('gd_mountains_mat', this.scene);
        mat.diffuseTexture = dt;
        mat.diffuseTexture.hasAlpha = true;
        mat.emissiveTexture = dt;
        mat.emissiveColor = new Color3(0.85, 0.85, 0.85);
        mat.disableLighting = true;
        mat.useAlphaFromDiffuseTexture = true;
        // Одностороннее отображение — иначе видна зеркальная копия снизу
        mat.backFaceCulling = true;
        mesh.material = mat;
        mesh.applyFog = true;
        // План смотрит лицом в −z (на камеру), но MeshBuilder создаёт лицом в +z.
        // Камера у нас смотрит в +z (sideview), значит лицо planes должно быть в −z.
        // Без поворота alpha-маска применяется корректно с обеих сторон, проверим.
        this._mountains = mesh;
    }

    /** Лес поближе — тёмные ёлки на горизонте. */
    _createForest() {
        const W = 2048, H = 256;
        const dt = new DynamicTexture('gd_forest_tex', { width: W, height: H }, this.scene, false);
        const ctx = dt.getContext();
        ctx.clearRect(0, 0, W, H);
        this._drawForestLayer(ctx, W, H);
        dt.hasAlpha = true;
        dt.update();

        const mesh = MeshBuilder.CreatePlane('gd_forest', {
            width: FOREST_W,
            height: FOREST_H,
        }, this.scene);
        mesh.position = new Vector3(0, FOREST_BOTTOM_Y + FOREST_H / 2, FOREST_Z);
        const mat = new StandardMaterial('gd_forest_mat', this.scene);
        mat.diffuseTexture = dt;
        mat.diffuseTexture.hasAlpha = true;
        mat.emissiveTexture = dt;
        mat.emissiveColor = new Color3(0.75, 0.78, 0.75);
        mat.disableLighting = true;
        mat.useAlphaFromDiffuseTexture = true;
        mat.backFaceCulling = true;
        mesh.material = mat;
        mesh.applyFog = true;
        this._forest = mesh;
    }

    /** Слой гор: силуэт в верхней половине canvas. */
    _drawMountainLayer(ctx, W, H, baseY, color, peaks, maxAmp) {
        ctx.fillStyle = color;
        ctx.beginPath();
        ctx.moveTo(0, H);
        const step = W / peaks;
        for (let i = 0; i <= peaks; i++) {
            const x = i * step;
            const r1 = Math.abs((Math.sin(i * 12.9898) * 43758.5453) % 1);
            const r2 = Math.abs((Math.sin(i * 78.233 + 1) * 43758.5453) % 1);
            const yOffset = (r1 * 0.6 + r2 * 0.4) * maxAmp;
            // под-пики (мелкие зазубрины)
            const sub = 5;
            const subStep = step / sub;
            for (let s = 0; s < sub; s++) {
                const subX = x + s * subStep - step / 2;
                if (subX < 0) continue;
                const subR = Math.abs((Math.sin((i * sub + s) * 41.421) * 43758) % 1);
                const subY = H * baseY - yOffset + (subR - 0.5) * maxAmp * 0.4;
                ctx.lineTo(subX, subY);
            }
            const y = H * baseY - yOffset;
            ctx.lineTo(x, y);
        }
        ctx.lineTo(W, H);
        ctx.closePath();
        ctx.fill();
    }

    /** Ёлки — низ canvas, верх прозрачный. */
    _drawForestLayer(ctx, W, H) {
        const treeStep = 20;
        for (let x = 0; x < W; x += treeStep) {
            const r = Math.abs((Math.sin(x * 0.231) * 43758.5453) % 1);
            const treeW = 18 + r * 12;
            const treeH = 80 + r * 60;
            const cx = x + treeW / 2;
            const baseY = H - 2;   // низ ствола почти у нижнего края canvas
            // ствол
            ctx.fillStyle = '#3a2a1a';
            ctx.fillRect(cx - 2, baseY - 8, 4, 8);
            // крона — 3 наслоённых треугольника
            ctx.fillStyle = '#2e4e2e';
            for (let i = 0; i < 3; i++) {
                const lvlH = treeH * 0.4;
                const lvlW = treeW * (1 - i * 0.18);
                const yTop = baseY - 8 - (treeH - i * (treeH / 4));
                const yBot = yTop + lvlH;
                ctx.beginPath();
                ctx.moveTo(cx, yTop);
                ctx.lineTo(cx + lvlW / 2, yBot);
                ctx.lineTo(cx - lvlW / 2, yBot);
                ctx.closePath();
                ctx.fill();
            }
        }
    }

    _setupFog() {
        const sc = this.scene;
        sc.fogMode = 2;   // FOGMODE_EXP
        sc.fogColor = new Color3(0.81, 0.90, 0.96);   // под горизонт skybox
        sc.fogDensity = 0.008;
    }

    /** Каждый кадр — фоны следуют за камерой по X, параллакс через uOffset текстуры. */
    _setupParallax() {
        // Включаем wrap для текстур чтобы uOffset закольцовывался
        try {
            const t1 = this._mountains?.material?.diffuseTexture;
            if (t1) {
                t1.wrapU = 1;   // BABYLON.Texture.WRAP_ADDRESSMODE
                t1.wrapV = 1;
            }
            const t2 = this._forest?.material?.diffuseTexture;
            if (t2) {
                t2.wrapU = 1;
                t2.wrapV = 1;
            }
        } catch (e) {}
        // U-coord смещение: камера движется на 1м → текстура сдвигается на
        // (1 - scroll) / MOUNTAIN_W. То есть видим параллакс через смещение
        // текстуры, а сама плоскость всегда перед камерой.
        this._onBeforeRender = () => {
            if (!this.camera || !this._mountains) return;
            const camX = this.camera.position?.x || 0;
            this._mountains.position.x = camX;
            this._forest.position.x = camX;
            if (this._groundFiller) this._groundFiller.position.x = camX;
            // bottom-cover тоже едет за камерой по X (это «бесконечная» стенка)
            if (this._bottomCover) this._bottomCover.position.x = camX;
            // Параллакс — текстура «отстаёт» от движения камеры
            const t1 = this._mountains.material?.diffuseTexture;
            const t2 = this._forest.material?.diffuseTexture;
            const t1e = this._mountains.material?.emissiveTexture;
            const t2e = this._forest.material?.emissiveTexture;
            const off1 = (camX * MOUNTAIN_SCROLL) / MOUNTAIN_W;
            const off2 = (camX * FOREST_SCROLL) / FOREST_W;
            if (t1) t1.uOffset = off1;
            if (t1e) t1e.uOffset = off1;
            if (t2) t2.uOffset = off2;
            if (t2e) t2e.uOffset = off2;
        };
        this.scene.onBeforeRenderObservable.add(this._onBeforeRender);
    }

    /** Снять всё и вернуть прежние fog/clearColor. */
    dispose() {
        if (!this.scene) return;
        try {
            if (this._onBeforeRender) {
                this.scene.onBeforeRenderObservable.removeCallback(this._onBeforeRender);
                this._onBeforeRender = null;
            }
        } catch (e) {}
        for (const m of [this._skybox, this._mountains, this._forest, this._groundFiller, this._bottomCover]) {
            if (m) {
                try { m.material?.dispose(true, true); } catch (e) {}
                try { m.dispose(); } catch (e) {}
            }
        }
        this._skybox = this._mountains = this._forest = this._groundFiller = this._bottomCover = null;
        try {
            this.scene.clearColor = this._prevClearColor;
            this.scene.fogMode = this._prevFogMode;
            this.scene.fogColor = this._prevFogColor;
            this.scene.fogDensity = this._prevFogDensity;
        } catch (e) {}
        this.scene = null;
        this.camera = null;
    }
}

/** Хелпер: определить, является ли сцена GD-уровнем (есть kind=gd_finish). */
export function isGdLevel(stateOrPrimitives) {
    let primitives = stateOrPrimitives;
    if (stateOrPrimitives?.scene?.primitives) {
        primitives = stateOrPrimitives.scene.primitives;
    }
    if (!Array.isArray(primitives)) return false;
    for (const p of primitives) {
        if (p?.type === 'gd_finish') return true;
    }
    return false;
}
