/**
 * GraphicsManager — система визуальных эффектов («шейдеры») для игр Рублокса.
 *
 * Управляет:
 *   - постобработкой экрана через Babylon DefaultRenderingPipeline:
 *       bloom (свечение), FXAA (сглаживание), виньетка, цветокоррекция
 *       (контраст/насыщенность/экспозиция), тонмаппинг, глубина резкости (DoF);
 *   - качеством теней (через scene3d.setShadowQuality);
 *   - контактными тенями SSAO (через scene3d.setSsaoEnabled).
 *
 * Управляется И из настроек игры (вкладка «Графика»), И из скриптов
 * (game.graphics.*). По умолчанию ВСЁ ВЫКЛЮЧЕНО (preset 'off') — старые игры
 * не меняются, FPS не страдает. Автор включает осознанно.
 *
 * Mobile-safe: на слабых устройствах тяжёлые эффекты (DoF, SSAO, ultra-тени,
 * HDR-bloom) автоматически урезаются, даже если в пресете включены.
 *
 * Один и тот же класс используется в студии и плеере (фича-парность).
 *
 * Использование:
 *   const gfx = new GraphicsManager(scene, camera, scene3d, { mobile });
 *   gfx.apply({ preset: 'cinematic' });
 *   gfx.apply({ bloom: { enabled: true, intensity: 0.7 } });
 *   gfx.dispose();
 */
import {
    DefaultRenderingPipeline, Color4, ImageProcessingConfiguration,
} from '@babylonjs/core';

/**
 * Именованные пресеты. Каждый — полный набор настроек. 'off' = чистая картинка
 * (pipeline не создаётся вовсе). Значения подобраны так, чтобы быть заметными,
 * но не «кислотными».
 */
export const GRAPHICS_PRESETS = {
    off: {
        bloom: { enabled: false },
        fxaa: false,
        vignette: { enabled: false },
        grading: { enabled: false },
        dof: { enabled: false },
        ssao: false,
        shadows: null,        // null = не трогаем текущее качество теней
    },
    // Лёгкий: только мягкое свечение + сглаживание. Дёшево, годится почти везде.
    low: {
        bloom: { enabled: true, intensity: 0.3, threshold: 0.9 },
        fxaa: true,
        vignette: { enabled: false },
        grading: { enabled: false },
        dof: { enabled: false },
        ssao: false,
        shadows: 'hard',
    },
    // Средний: свечение + лёгкая виньетка + чуть насыщенности.
    medium: {
        bloom: { enabled: true, intensity: 0.45, threshold: 0.85 },
        fxaa: true,
        vignette: { enabled: true, weight: 0.5 },
        grading: { enabled: true, contrast: 1.05, saturation: 1.1, exposure: 1.0 },
        dof: { enabled: false },
        ssao: false,
        shadows: 'soft',
    },
    // Высокий: всё кроме DoF, SSAO включён.
    high: {
        bloom: { enabled: true, intensity: 0.6, threshold: 0.82 },
        fxaa: true,
        vignette: { enabled: true, weight: 0.6 },
        grading: { enabled: true, contrast: 1.1, saturation: 1.2, exposure: 1.05 },
        dof: { enabled: false },
        ssao: true,
        shadows: 'soft',
    },
    // Ультра: + глубина резкости + мягкие каскадные тени.
    ultra: {
        bloom: { enabled: true, intensity: 0.7, threshold: 0.8 },
        fxaa: true,
        vignette: { enabled: true, weight: 0.65 },
        grading: { enabled: true, contrast: 1.12, saturation: 1.25, exposure: 1.05 },
        dof: { enabled: true, focusDistance: 18, focalLength: 50, aperture: 1.2 },
        ssao: true,
        shadows: 'high',
    },
    // === Стилевые пресеты (художественные) ===
    cinematic: {
        bloom: { enabled: true, intensity: 0.55, threshold: 0.8 },
        fxaa: true,
        vignette: { enabled: true, weight: 0.85 },
        grading: { enabled: true, contrast: 1.18, saturation: 1.05, exposure: 1.0 },
        dof: { enabled: true, focusDistance: 22, focalLength: 60, aperture: 1.0 },
        ssao: true,
        shadows: 'soft',
    },
    vivid: {
        bloom: { enabled: true, intensity: 0.65, threshold: 0.78 },
        fxaa: true,
        vignette: { enabled: false },
        grading: { enabled: true, contrast: 1.1, saturation: 1.5, exposure: 1.1 },
        dof: { enabled: false },
        ssao: false,
        shadows: 'soft',
    },
    night: {
        bloom: { enabled: true, intensity: 0.8, threshold: 0.7 },
        fxaa: true,
        vignette: { enabled: true, weight: 1.0 },
        grading: { enabled: true, contrast: 1.2, saturation: 0.85, exposure: 0.8 },
        dof: { enabled: false },
        ssao: true,
        shadows: 'soft',
    },
    retro: {
        bloom: { enabled: false },
        fxaa: false,   // намеренно «пиксельно»
        vignette: { enabled: true, weight: 1.2 },
        grading: { enabled: true, contrast: 1.3, saturation: 0.7, exposure: 0.95 },
        dof: { enabled: false },
        ssao: false,
        shadows: 'hard',
    },
    soft: {
        bloom: { enabled: true, intensity: 0.4, threshold: 0.88 },
        fxaa: true,
        vignette: { enabled: true, weight: 0.4 },
        grading: { enabled: true, contrast: 0.95, saturation: 1.05, exposure: 1.05 },
        dof: { enabled: false },
        ssao: false,
        shadows: 'soft',
    },
};

// Глубокое слияние пресета и пользовательских оверрайдов.
function _mergeConfig(base, over) {
    const out = JSON.parse(JSON.stringify(base || {}));
    if (!over) return out;
    for (const k of Object.keys(over)) {
        const v = over[k];
        if (v && typeof v === 'object' && !Array.isArray(v)) {
            out[k] = { ...(out[k] || {}), ...v };
        } else {
            out[k] = v;
        }
    }
    return out;
}

export class GraphicsManager {
    /**
     * @param scene   Babylon Scene
     * @param camera  активная камера (для pipeline)
     * @param scene3d ссылка на BabylonScene (для setShadowQuality / setSsaoEnabled / света)
     * @param opts    { mobile:boolean }
     */
    constructor(scene, camera, scene3d, opts = {}) {
        this.scene = scene;
        this.camera = camera;
        this.scene3d = scene3d;
        this.mobile = !!opts.mobile;
        this._pipeline = null;
        // Текущая активная конфигурация (после merge + mobile-clamp).
        this.config = _mergeConfig(GRAPHICS_PRESETS.off, null);
        this.config.preset = 'off';
        this.enabled = false;
    }

    /** Сменить камеру (например после смены режима камеры) — пересобрать pipeline. */
    setCamera(camera) {
        if (camera === this.camera) return;
        this.camera = camera;
        if (this.enabled) this._rebuildPipeline();
    }

    /**
     * Применить настройки графики. Принимает либо {preset}, либо отдельные
     * секции (bloom/vignette/grading/dof/ssao/fxaa/shadows), либо и то и другое
     * (оверрайды поверх пресета). Сохраняет состояние в this.config.
     */
    apply(settings = {}) {
        let cfg;
        if (settings.preset && GRAPHICS_PRESETS[settings.preset]) {
            cfg = _mergeConfig(GRAPHICS_PRESETS[settings.preset], settings);
            cfg.preset = settings.preset;
        } else {
            // частичный апдейт поверх текущего
            cfg = _mergeConfig(this.config, settings);
            cfg.preset = settings.preset || this.config.preset || 'custom';
        }
        this.config = this._clampForMobile(cfg);
        this._applyConfig();
        return this.config;
    }

    /** Полностью выключить эффекты (как preset 'off'). */
    disableAll() {
        return this.apply({ preset: 'off' });
    }

    /** Текущая конфигурация (для serialize). */
    serialize() {
        // Храним «как просили» (preset + явные оверрайды). Для простоты — весь cfg.
        return JSON.parse(JSON.stringify(this.config));
    }

    // --- внутреннее ---

    /** На слабых устройствах гасим самое дорогое, что бы ни просили. */
    _clampForMobile(cfg) {
        if (!this.mobile) return cfg;
        const c = JSON.parse(JSON.stringify(cfg));
        if (c.dof) c.dof.enabled = false;       // DoF дорогой
        c.ssao = false;                          // SSAO дорогой
        if (c.shadows === 'high' || c.shadows === 'medium') c.shadows = 'hard';
        // bloom оставляем, но без HDR (решается в _rebuildPipeline)
        c._mobileClamped = true;
        return c;
    }

    _applyConfig() {
        const c = this.config;
        const anyPipelineFx = (c.bloom && c.bloom.enabled) || c.fxaa
            || (c.vignette && c.vignette.enabled) || (c.grading && c.grading.enabled)
            || (c.dof && c.dof.enabled);

        // Тени и SSAO — через scene3d (они вне pipeline).
        try {
            if (c.shadows && this.scene3d?.setShadowQuality) {
                this.scene3d.setShadowQuality(c.shadows);
            }
        } catch (e) { /* ignore */ }
        try {
            if (this.scene3d?.setSsaoEnabled) this.scene3d.setSsaoEnabled(!!c.ssao);
        } catch (e) { /* ignore */ }

        if (!anyPipelineFx) {
            this.enabled = false;
            this._disposePipeline();
            return;
        }
        this.enabled = true;
        this._rebuildPipeline();
    }

    _rebuildPipeline() {
        this._disposePipeline();
        if (!this.scene || !this.camera) return;
        const c = this.config;
        const useHdr = (c.bloom && c.bloom.enabled) && !this.mobile;

        const p = new DefaultRenderingPipeline('rbx_graphics', useHdr, this.scene, [this.camera]);

        // Bloom
        p.bloomEnabled = !!(c.bloom && c.bloom.enabled);
        if (p.bloomEnabled) {
            p.bloomThreshold = c.bloom.threshold ?? 0.85;
            p.bloomWeight = c.bloom.intensity ?? 0.5;
            p.bloomKernel = this.mobile ? 32 : 64;
            p.bloomScale = 0.5;
        }

        // FXAA
        p.fxaaEnabled = !!c.fxaa;
        p.samples = this.mobile ? 1 : 4;

        // Image processing: виньетка + цветокоррекция + (опц.) тонмаппинг
        const ip = p.imageProcessing;
        if (ip) {
            p.imageProcessingEnabled = true;
            ip.toneMappingEnabled = false; // как в GdPostFx — иначе картинка темнеет
            // экспозиция/контраст из grading
            if (c.grading && c.grading.enabled) {
                ip.exposure = c.grading.exposure ?? 1.0;
                ip.contrast = c.grading.contrast ?? 1.0;
                ip.colorCurvesEnabled = true;
                try {
                    const curves = ip.colorCurves;
                    if (curves) {
                        // saturation: 1.0 = норма → curves в диапазоне примерно -100..100
                        const sat = c.grading.saturation ?? 1.0;
                        curves.globalSaturation = Math.round((sat - 1.0) * 60);
                    }
                } catch (e) { /* ignore */ }
            } else {
                ip.exposure = 1.0; ip.contrast = 1.0;
            }
            // виньетка
            if (c.vignette && c.vignette.enabled) {
                ip.vignetteEnabled = true;
                ip.vignetteWeight = c.vignette.weight ?? 0.6;
                ip.vignetteColor = new Color4(0, 0, 0, 0);
                ip.vignetteStretch = 0.3;
                ip.vignetteCameraFov = 0.5;
                ip.vignetteBlendMode = ImageProcessingConfiguration.VIGNETTEMODE_MULTIPLY;
            } else {
                ip.vignetteEnabled = false;
            }
        }

        // Depth of Field (глубина резкости) — только desktop
        if (c.dof && c.dof.enabled && !this.mobile) {
            p.depthOfFieldEnabled = true;
            try {
                p.depthOfFieldBlurLevel = 1; // 0..2
                p.depthOfField.focusDistance = (c.dof.focusDistance ?? 18) * 1000; // мм
                p.depthOfField.focalLength = c.dof.focalLength ?? 50;
                p.depthOfField.fStop = c.dof.aperture ?? 1.2;
            } catch (e) { /* ignore */ }
        } else {
            p.depthOfFieldEnabled = false;
        }

        this._pipeline = p;
    }

    _disposePipeline() {
        if (this._pipeline) {
            try { this._pipeline.dispose(); } catch (e) { /* ignore */ }
            this._pipeline = null;
        }
    }

    dispose() {
        this._disposePipeline();
        this.scene = null;
        this.camera = null;
        this.scene3d = null;
    }
}
