import * as THREE from 'three';
import { Water } from 'three/addons/objects/Water.js';
import { Sky } from 'three/addons/objects/Sky.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import Stats from 'three/addons/libs/stats.module.js';
import { TerrainGenerator } from './TerrainGenerator.js';
import { VegetationSystem } from './VegetationSystem.js';
import { DEFAULT_SCENE_PARAMS } from './weatherPresets.js';

const RAIN_AUDIO_URL = '/audio/rain-calming.mp3';
const THUNDER_AUDIO_URL = '/audio/thunder-distant.mp3';

export class OceanScene {
    constructor(container) {
        this.container = container;
        this.scene = null;
        this.camera = null;
        this.renderer = null;
        this.controls = null;
        this.water = null;
        this.sky = null;
        this.sun = new THREE.Vector3();
        this.terrain = null;
        this.vegetation = null;
        this.vegetationSystem = null;
        this.pmremGenerator = null;
        this.renderTarget = null;
        this.sunLight = null;
        this.vegetationFillLight = null;
        this.lightningLight = null;
        this.lightningCloudGlow = null;
        this.composer = null;
        this.bloomPass = null;
        this.rainPass = null;
        this.snowPass = null;
        this.stats = null;
        this.cloudGroup = null;
        this.cloudMaterials = [];
        this.cloudLayers = [];
        this.fogGroup = null;
        this.fogLayers = [];
        this.horizonFog = null;
        this.skyHazeBand = null;
        this.rainAudioPool = [];
        this.rainAudioActiveIndex = 0;
        this.rainAudioIsPlaying = false;
        this.rainAudioCrossfading = false;
        this.rainAudioCrossfadeDuration = 1.6;
        this.thunderAudioPool = [];
        this.thunderAudioIndex = 0;
        this.scheduledThunder = [];
        
        this.params = { ...DEFAULT_SCENE_PARAMS };
        
        this.clock = new THREE.Clock();
        this.lightningFlash = 0;
        this.lightningLocalFlash = 0;
        this.lightningBurstEnd = 0;
        this.nextLightningAt = 0;
        this.lightningPulseSchedule = [];
    }
    
    async init() {
        this.initRenderer();
        this.initStats();
        this.initScene();
        this.initCamera();
        this.initControls();
        this.initLighting();
        this.initPostProcessing();
        this.initAudio();
        await this.initSky();
        this.initClouds();
        this.initFog();
        await this.initWater();
        await this.initTerrain();
        await this.initVegetation();
        this.initSunPosition();
        this.initEventListeners();
    }
    
    initRenderer() {
        this.renderer = new THREE.WebGLRenderer({
            antialias: true,
            powerPreference: 'high-performance'
        });
        this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
        this.renderer.toneMappingExposure = this.params.exposure;
        this.renderer.shadowMap.enabled = true;
        this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
        this.container.appendChild(this.renderer.domElement);
    }

    initStats() {
        this.stats = new Stats();
        this.stats.showPanel(0);
        this.stats.dom.style.position = 'fixed';
        this.stats.dom.style.left = '0';
        this.stats.dom.style.top = '0';
        this.stats.dom.style.bottom = 'auto';
        this.stats.dom.style.margin = '0';
        this.stats.dom.style.zIndex = '120';
        this.container.appendChild(this.stats.dom);
    }
    
    initScene() {
        this.scene = new THREE.Scene();
        this.scene.fog = new THREE.FogExp2(0x8cb8d4, 0.0006);
    }
    
    initCamera() {
        this.camera = new THREE.PerspectiveCamera(
            60,
            window.innerWidth / window.innerHeight,
            1,
            20000
        );
        this.camera.position.set(100, 50, 200);
    }
    
    initControls() {
        this.controls = new OrbitControls(this.camera, this.renderer.domElement);
        this.controls.enableDamping = true;
        this.controls.dampingFactor = 0.05;
        this.controls.maxPolarAngle = Math.PI * 0.48;
        this.controls.minDistance = 30;
        this.controls.maxDistance = 1000;
        this.controls.target.set(0, 10, 0);
        this.controls.update();
    }
    
    initLighting() {
        const ambientLight = new THREE.AmbientLight(0x555555);
        this.scene.add(ambientLight);
        
        this.sunLight = new THREE.DirectionalLight(0xffffff, 1.5);
        this.sunLight.castShadow = true;
        this.sunLight.shadow.mapSize.width = 2048;
        this.sunLight.shadow.mapSize.height = 2048;
        this.sunLight.shadow.camera.near = 0.5;
        this.sunLight.shadow.camera.far = 500;
        this.sunLight.shadow.camera.left = -100;
        this.sunLight.shadow.camera.right = 100;
        this.sunLight.shadow.camera.top = 100;
        this.sunLight.shadow.camera.bottom = -100;
        this.scene.add(this.sunLight);

        this.vegetationFillLight = new THREE.DirectionalLight(0xffb06a, 0.95);
        this.vegetationFillLight.castShadow = false;
        this.scene.add(this.vegetationFillLight);

        this.lightningLight = new THREE.DirectionalLight(0xddeeff, 0);
        this.lightningLight.castShadow = false;
        this.lightningLight.position.set(-120, 180, 40);
        this.scene.add(this.lightningLight);
    }

    initPostProcessing() {
        this.composer = new EffectComposer(this.renderer);
        this.composer.addPass(new RenderPass(this.scene, this.camera));

        this.bloomPass = new UnrealBloomPass(
            new THREE.Vector2(window.innerWidth, window.innerHeight),
            this.params.bloomStrength,
            this.params.bloomRadius,
            this.params.bloomThreshold
        );
        this.composer.addPass(this.bloomPass);

        this.rainPass = new ShaderPass(this.createRainShader());
        this.rainPass.enabled = this.params.rainEnabled;
        this.rainPass.material.uniforms.resolution.value.set(window.innerWidth, window.innerHeight);
        this.rainPass.material.uniforms.screenIntensity.value = this.params.rainScreenIntensity;
        this.rainPass.material.uniforms.veilIntensity.value = this.params.rainVeilIntensity;
        this.rainPass.material.uniforms.dropSize.value = this.params.rainDropSize;
        this.rainPass.material.uniforms.rainSpeed.value = this.params.rainSpeed;
        this.composer.addPass(this.rainPass);

        this.snowPass = new ShaderPass(this.createSnowShader());
        this.snowPass.enabled = this.params.snowEnabled;
        this.snowPass.material.uniforms.resolution.value.set(window.innerWidth, window.innerHeight);
        this.snowPass.material.uniforms.intensity.value = this.params.snowIntensity;
        this.snowPass.material.uniforms.snowSpeed.value = this.params.snowSpeed;
        this.composer.addPass(this.snowPass);
    }

    initAudio() {
        this.rainAudioPool = Array.from({ length: 2 }, () => {
            const audio = new Audio(RAIN_AUDIO_URL);
            audio.loop = false;
            audio.preload = 'auto';
            audio.volume = 0;
            audio.crossOrigin = 'anonymous';
            return audio;
        });

        this.thunderAudioPool = Array.from({ length: 3 }, () => {
            const audio = new Audio(THUNDER_AUDIO_URL);
            audio.preload = 'auto';
            audio.crossOrigin = 'anonymous';
            return audio;
        });
    }

    createRainShader() {
        return {
            uniforms: {
                tDiffuse: { value: null },
                time: { value: 0 },
                screenIntensity: { value: this.params.rainScreenIntensity },
                veilIntensity: { value: this.params.rainVeilIntensity },
                dropSize: { value: this.params.rainDropSize },
                rainSpeed: { value: this.params.rainSpeed },
                resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }
            },
            vertexShader: `
                varying vec2 vUv;

                void main() {
                    vUv = uv;
                    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                }
            `,
            fragmentShader: `
                uniform sampler2D tDiffuse;
                uniform float time;
                uniform float screenIntensity;
                uniform float veilIntensity;
                uniform float dropSize;
                uniform float rainSpeed;
                uniform vec2 resolution;

                varying vec2 vUv;

                float hash11(float p) {
                    p = fract(p * 0.1031);
                    p *= p + 33.33;
                    p *= p + p;
                    return fract(p);
                }

                float hash21(vec2 p) {
                    vec3 p3 = fract(vec3(p.xyx) * 0.1031);
                    p3 += dot(p3, p3.yzx + 33.33);
                    return fract((p3.x + p3.y) * p3.z);
                }

                vec3 hash31(float p) {
                    vec3 p3 = fract(vec3(p) * vec3(0.1031, 0.11369, 0.13787));
                    p3 += dot(p3, p3.yzx + 19.19);
                    return fract(vec3(
                        (p3.x + p3.y) * p3.z,
                        (p3.x + p3.z) * p3.y,
                        (p3.y + p3.z) * p3.x
                    ));
                }

                float easeOutCubic(float x) {
                    float inv = 1.0 - clamp(x, 0.0, 1.0);
                    return 1.0 - inv * inv * inv;
                }

                vec2 dropLayer(vec2 uv, float t, float sizeFactor) {
                    vec2 grid = vec2(9.0, 4.0) / mix(0.7, 1.45, sizeFactor);
                    vec2 p = uv * grid;
                    vec2 cell = floor(p);
                    vec2 local = fract(p) - 0.5;
                    vec3 n = hash31(cell.x * 53.17 + cell.y * 417.43);

                    float rate = (0.45 + n.z * 0.95) * mix(0.82, 1.45, sizeFactor);
                    float cycle = t * rate + n.z * 11.0;
                    float phase = fract(cycle);

                    vec2 spawn = (n.xy - 0.5) * vec2(0.72, 0.28);
                    float onset = smoothstep(0.0, 0.07, phase);
                    float lifeFade = 1.0 - smoothstep(0.52, 0.82, phase);
                    float fall = easeOutCubic(smoothstep(0.02, 0.58, phase));
                    float fallDistance = mix(0.9, 2.15, n.y) * mix(0.85, 1.4, sizeFactor) * fall;
                    vec2 center = vec2(spawn.x, 0.62 + spawn.y - fallDistance);

                    vec2 delta = local - center;
                    float aspect = mix(0.8, 1.45, n.x);
                    float bodyRadius = mix(0.18, 0.3, sizeFactor);
                    float body = smoothstep(bodyRadius, 0.03, length(delta * vec2(aspect, 1.0)));

                    float tail = smoothstep(mix(0.06, 0.095, sizeFactor), 0.0, abs(delta.x)) *
                        smoothstep(0.04, -0.12, delta.y) *
                        smoothstep(-0.72, -0.04, delta.y) *
                        smoothstep(0.04, 0.18, phase);

                    float spitSeed = hash21(cell + floor(cycle) + 3.7);
                    float spitY = center.y + 0.18 + fract(local.y * 3.7 + spitSeed) * 0.42;
                    float spit = smoothstep(mix(0.06, 0.11, sizeFactor), 0.0, length(local - vec2(center.x + (spitSeed - 0.5) * 0.14, spitY)));
                    spit *= smoothstep(0.02, 0.14, phase) * (1.0 - smoothstep(0.18, 0.34, phase));

                    float impactFlash = smoothstep(0.14, 0.0, phase) * smoothstep(0.24, 0.02, length(local - vec2(spawn.x, 0.58 + spawn.y)));
                    float mask = (max(body, tail * 0.9) * onset + spit * 0.55 + impactFlash * 0.7) * lifeFade;
                    float trail = tail * onset * lifeFade;
                    return vec2(mask, trail);
                }

                vec2 drops(vec2 uv, float t, float l0, float l1, float l2, float sizeFactor) {
                    vec2 m1 = dropLayer(uv, t, sizeFactor) * l1;
                    vec2 m2 = dropLayer(uv * 1.85, t, sizeFactor * 0.82) * l2;

                    float c = m1.x + m2.x;
                    c = smoothstep(0.3, 1.0, c);

                    float trail = max(m1.y * l1, m2.y * l2);
                    return vec2(c, trail);
                }

                vec3 impactLayer(vec2 uv, float t, float rainAmount, float sizeFactor) {
                    vec2 grid = vec2(11.0, 6.0) / mix(0.75, 1.35, sizeFactor);
                    vec2 p = uv * grid;
                    vec2 cell = floor(p);
                    vec2 local = fract(p) - 0.5;
                    vec3 n = hash31(cell.x * 97.13 + cell.y * 413.71);

                    float rate = (1.3 + n.z * 2.4) * mix(0.95, 1.55, sizeFactor);
                    float cycle = t * rate + n.x * 7.13 + n.y * 3.17;
                    float phase = fract(cycle);
                    vec2 center = (n.xy - 0.5) * vec2(0.72, 0.36);
                    float lifeFade = 1.0 - smoothstep(0.28, 0.56, phase);
                    center.y += easeOutCubic(smoothstep(0.01, 0.36, phase)) * mix(0.9, 1.45, sizeFactor);
                    vec2 d = local - center;
                    float radius = mix(0.012, mix(0.18, 0.32, sizeFactor), smoothstep(0.0, 0.22, phase));

                    float flash = smoothstep(0.09, 0.0, phase) * smoothstep(0.18, 0.015, length(d));
                    float ring = smoothstep(0.025, 0.0, abs(length(d) - radius)) * (1.0 - smoothstep(0.05, 0.24, phase));
                    float tail = smoothstep(0.055, 0.0, abs(d.x)) *
                        smoothstep(0.02, -0.12, d.y) *
                        smoothstep(-0.6, -0.04, d.y) *
                        smoothstep(0.02, 0.18, phase);
                    float sideSplash = smoothstep(0.055, 0.0, abs(d.y + 0.02)) *
                        smoothstep(0.16, 0.02, abs(abs(d.x) - mix(0.06, 0.22, phase))) *
                        (1.0 - smoothstep(0.06, 0.2, phase));

                    float mask = (flash + ring * 0.55 + tail * 0.65 + sideSplash * 0.4) * step(0.64, n.z) * rainAmount * lifeFade;
                    vec2 dir = normalize(d + vec2(0.0001));
                    return vec3(mask, dir * mask * (0.018 + flash * 0.028));
                }

                float rainVeil(vec2 uv, float t, float rainAmount) {
                    float accum = 0.0;
                    for (int i = 0; i < 4; i++) {
                        float fi = float(i);
                        float scale = 18.0 + fi * 11.0;
                        float speed = 2.0 + fi * 0.9;
                        float slant = 0.16 + fi * 0.07;
                        vec2 p = uv;
                        p.x += p.y * slant;
                        p.y += t * speed;
                        p.x += sin(p.y * (7.0 + fi * 2.3) + fi * 11.7) * (0.015 + fi * 0.004);

                        vec2 layerUv = p * scale;
                        vec2 cell = floor(layerUv);
                        vec2 local = fract(layerUv) - 0.5;
                        vec3 n = hash31(cell.x * (41.0 + fi * 13.0) + cell.y * (289.0 + fi * 71.0) + fi * 19.0);

                        float lane = (n.x - 0.5) * 0.62;
                        float width = mix(0.01, 0.045, n.y * n.y);
                        float length = mix(0.24, 0.95, n.x);
                        float bend = sin((cell.y + t * speed * 8.0) * (0.9 + n.z * 1.6) + n.x * 6.2831) * 0.18;
                        float dx = local.x - lane - bend * (0.15 + fi * 0.05);
                        float dy = local.y + (n.z - 0.5) * 0.3;

                        float streak = smoothstep(width, width * 0.15, abs(dx));
                        streak *= smoothstep(length, length * 0.12, abs(dy) * 2.0);

                        float breakup = smoothstep(0.2, 0.85, sin((dy + n.y * 2.0) * 18.0 + n.z * 12.0) * 0.5 + 0.5);
                        float flicker = 0.35 + 0.65 * fract(n.z * 31.7 + t * (4.0 + n.x * 3.0) + fi * 2.1);
                        float density = step(0.34 - fi * 0.05, n.y);
                        streak *= mix(1.0, breakup, 0.55) * flicker * density;

                        accum += streak * (0.52 - fi * 0.08);
                    }

                    return accum * smoothstep(0.08, 0.95, rainAmount);
                }

                void main() {
                    vec2 uv = vUv;
                    vec2 centeredUv = (uv - 0.5) * vec2(resolution.x / max(resolution.y, 1.0), 1.0);
                    float screenAmount = clamp(screenIntensity / 1.5, 0.0, 1.0);
                    float veilAmount = clamp(veilIntensity / 1.5, 0.0, 1.0);
                    float sizeFactor = clamp((dropSize - 0.4) / 1.4, 0.0, 1.0);
                    float rainAmount = max(screenAmount, veilAmount);
                    float t = time * rainSpeed * 0.18;

                    float staticLayer = 0.0;
                    float densityScale = mix(1.2, 0.55, sizeFactor);
                    float layer1 = smoothstep(0.08, 0.55, screenAmount) * densityScale;
                    float layer2 = smoothstep(0.25, 0.95, screenAmount) * densityScale;

                    vec2 c = drops(centeredUv * 0.9, t, staticLayer, layer1, layer2, sizeFactor);
                    vec3 impact = impactLayer(centeredUv * vec2(1.0, 1.2), t * 2.2, screenAmount, sizeFactor);
                    float veil = rainVeil(centeredUv * vec2(1.0, 1.4), time * rainSpeed * 0.55, veilAmount);
                    vec2 e = vec2(0.0015, 0.0);
                    float cx = drops(centeredUv + e, t, staticLayer, layer1, layer2, sizeFactor).x;
                    float cy = drops(centeredUv + e.yx, t, staticLayer, layer1, layer2, sizeFactor).x;
                    vec2 normal = vec2(cx - c.x, cy - c.x) + impact.yz;

                    float maxBlur = mix(0.002, 0.009, rainAmount);
                    float minBlur = 0.001;
                    float focus = mix(maxBlur - c.y * 0.5, minBlur, smoothstep(0.05, 0.2, c.x + impact.x * 0.5));

                    vec2 texel = 1.0 / max(resolution, vec2(1.0));
                    vec2 distortion = normal * (0.35 + focus * 48.0 + impact.x * 1.6);

                    vec3 base =
                        texture2D(tDiffuse, uv + distortion).rgb * 0.4 +
                        texture2D(tDiffuse, uv + distortion + vec2(texel.x * focus * 24.0, 0.0)).rgb * 0.15 +
                        texture2D(tDiffuse, uv + distortion - vec2(texel.x * focus * 24.0, 0.0)).rgb * 0.15 +
                        texture2D(tDiffuse, uv + distortion + vec2(0.0, texel.y * focus * 24.0)).rgb * 0.15 +
                        texture2D(tDiffuse, uv + distortion - vec2(0.0, texel.y * focus * 24.0)).rgb * 0.15;

                    vec3 sharp = texture2D(tDiffuse, uv + distortion * 0.6).rgb;
                    vec3 col = mix(base, sharp, smoothstep(0.04, 0.22, c.x));

                    vec3 rainTint = vec3(0.86, 0.91, 0.95);
                    col = mix(col, col * 0.88 + rainTint * 0.12, clamp(c.x * 0.65 + c.y * 0.3 + impact.x * 0.4, 0.0, 1.0));
                    col += c.y * rainTint * 0.08 * screenAmount;
                    col += impact.x * rainTint * 0.18 * screenAmount;
                    col += veil * rainTint * (0.12 + veilAmount * 0.18);
                    col = mix(col, col * 0.94 + rainTint * 0.06, veil * 0.18);
                    col = mix(texture2D(tDiffuse, uv).rgb, col, clamp(rainAmount * 1.1, 0.0, 1.0));

                    gl_FragColor = vec4(col, 1.0);
                }
            `
        };
    }

    createSnowShader() {
        return {
            uniforms: {
                tDiffuse: { value: null },
                time: { value: 0 },
                intensity: { value: this.params.snowIntensity },
                snowSpeed: { value: this.params.snowSpeed },
                resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }
            },
            vertexShader: `
                varying vec2 vUv;

                void main() {
                    vUv = uv;
                    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                }
            `,
            fragmentShader: `
                uniform sampler2D tDiffuse;
                uniform float time;
                uniform float intensity;
                uniform float snowSpeed;
                uniform vec2 resolution;

                varying vec2 vUv;

                float rnd(float x) {
                    return fract(sin(dot(vec2(x + 47.49, 38.2467 / (x + 2.3)), vec2(12.9898, 78.233))) * 43758.5453);
                }

                float drawCircle(vec2 uv, vec2 center, float radius) {
                    return 1.0 - smoothstep(0.0, radius, length(uv - center));
                }

                float snowField(vec2 uv, float t, float amount, float aspect) {
                    float snow = 0.0;
                    float blizzardFactor = mix(0.08, 0.3, amount);
                    int flakeCount = 220;

                    for (int i = 0; i < 220; i++) {
                        if (i >= flakeCount) break;
                        float j = float(i);
                        float baseRnd = rnd(cos(j));
                        float speed = (0.3 + baseRnd * (0.7 + 0.5 * cos(j / 55.0))) * mix(0.7, 1.65, amount);
                        float radius = (0.001 + speed * 0.012) * mix(0.7, 1.18, amount);
                        vec2 center = vec2(
                            ((0.25 - uv.y) * blizzardFactor + rnd(j) + 0.08 * cos(t * 0.7 + sin(j))) * aspect,
                            mod(sin(j) - speed * (t * 1.5 * (0.1 + blizzardFactor)), 1.35) - 0.25
                        );

                        float flake = drawCircle(uv, center, radius);
                        snow += flake * (0.035 + speed * 0.04);
                    }

                    return snow;
                }

                void main() {
                    vec2 uv = vUv;
                    float aspect = resolution.x / max(resolution.y, 1.0);
                    vec2 snowUv = vec2(vUv.x * aspect, vUv.y);
                    float snowAmount = clamp(intensity / 1.5, 0.0, 1.0);
                    float t = time * snowSpeed;
                    float snow = snowField(snowUv, t, snowAmount, aspect);
                    float snowMask = clamp(snow * mix(0.45, 1.15, snowAmount), 0.0, 1.0);
                    float atmosphere = (1.0 - vUv.y) * 0.12 * snowAmount;

                    vec3 base = texture2D(tDiffuse, vUv).rgb;
                    vec3 snowTint = vec3(0.92, 0.95, 1.0);
                    base = mix(base, base * 0.96 + snowTint * 0.04, snowAmount * 0.1);
                    base += snowTint * snowMask;
                    base += vec3(0.16, 0.28, 0.4) * atmosphere;

                    gl_FragColor = vec4(base, 1.0);
                }
            `
        };
    }
    
    async initSky() {
        this.sky = new Sky();
        this.sky.scale.setScalar(10000);
        this.sky.rotation.y = Math.PI;
        this.scene.add(this.sky);
        
        const skyUniforms = this.sky.material.uniforms;
        skyUniforms['turbidity'].value = this.params.turbidity;
        skyUniforms['rayleigh'].value = this.params.rayleigh;
        skyUniforms['mieCoefficient'].value = this.params.mieCoefficient;
        skyUniforms['mieDirectionalG'].value = this.params.mieDirectionalG;
        
        this.pmremGenerator = new THREE.PMREMGenerator(this.renderer);
    }

    initClouds() {
        this.cloudGroup = new THREE.Group();
        this.cloudGroup.position.y = 40;
        this.addCloudPlaneLayer({
            radius: 5200,
            y: 120,
            opacity: 0.42,
            scale: 1450,
            detailScale: 3.4,
            softness: 0.19,
            edgeFade: 0.2,
            shadowStrength: 0.36,
            highlightStrength: 0.18,
            erosionStrength: 0.2,
            ridgeStrength: 0.08,
            driftX: 0.0045,
            driftY: 0.0012,
            rotationZ: 0.06
        });
        this.addCloudPlaneLayer({
            radius: 4300,
            y: 250,
            opacity: 0.28,
            scale: 980,
            detailScale: 4.1,
            softness: 0.17,
            edgeFade: 0.24,
            shadowStrength: 0.42,
            highlightStrength: 0.24,
            erosionStrength: 0.28,
            ridgeStrength: 0.12,
            driftX: -0.0032,
            driftY: 0.0018,
            rotationZ: -0.04
        });
        this.addCloudPlaneLayer({
            radius: 3400,
            y: 360,
            opacity: 0.18,
            scale: 760,
            detailScale: 4.8,
            softness: 0.16,
            edgeFade: 0.28,
            shadowStrength: 0.46,
            highlightStrength: 0.3,
            erosionStrength: 0.34,
            ridgeStrength: 0.16,
            driftX: 0.0021,
            driftY: -0.0014,
            rotationZ: 0.1
        });

        this.scene.add(this.cloudGroup);
        this.initLightningCloudGlow();
        this.setCloudElevation(this.params.cloudElevation);
        this.setCloudCoverage(this.params.cloudCoverage);
        this.updateClouds();
    }

    initLightningCloudGlow() {
        const glowTexture = this.createLightningGlowTexture();
        const material = new THREE.SpriteMaterial({
            map: glowTexture,
            color: 0xddeeff,
            transparent: true,
            opacity: 0,
            depthWrite: false,
            depthTest: false,
            fog: false,
            blending: THREE.AdditiveBlending
        });

        this.lightningCloudGlow = new THREE.Sprite(material);
        this.lightningCloudGlow.scale.set(900, 900, 1);
        this.lightningCloudGlow.position.set(-420, 210, -760);
        this.lightningCloudGlow.visible = false;
        this.scene.add(this.lightningCloudGlow);
    }

    addCloudPlaneLayer(config) {
        const material = new THREE.ShaderMaterial({
            uniforms: {
                tintColor: { value: new THREE.Color(0xffffff) },
                layerOpacity: { value: config.opacity },
                time: { value: 0 },
                cloudCoverage: { value: this.params.cloudCoverage },
                cloudDensity: { value: this.params.cloudDensity },
                scale: { value: config.scale },
                detailScale: { value: config.detailScale },
                softness: { value: config.softness },
                edgeFade: { value: config.edgeFade },
                drift: { value: new THREE.Vector2(config.driftX, config.driftY) },
                seed: { value: Math.random() * 100.0 },
                lightDir: { value: new THREE.Vector2(1, 0) },
                shadowStrength: { value: config.shadowStrength ?? 0.42 },
                highlightStrength: { value: config.highlightStrength ?? 0.26 },
                erosionStrength: { value: config.erosionStrength ?? 0.24 },
                ridgeStrength: { value: config.ridgeStrength ?? 0.12 }
            },
            vertexShader: `
                varying vec2 vPlaneUv;
                varying vec3 vWorldPos;

                void main() {
                    vPlaneUv = uv;
                    vWorldPos = (modelMatrix * vec4(position, 1.0)).xyz;
                    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                }
            `,
            fragmentShader: `
                uniform vec3 tintColor;
                uniform float layerOpacity;
                uniform float time;
                uniform float cloudCoverage;
                uniform float cloudDensity;
                uniform float scale;
                uniform float detailScale;
                uniform float softness;
                uniform float edgeFade;
                uniform vec2 drift;
                uniform float seed;
                uniform vec2 lightDir;
                uniform float shadowStrength;
                uniform float highlightStrength;
                uniform float erosionStrength;
                uniform float ridgeStrength;

                varying vec2 vPlaneUv;
                varying vec3 vWorldPos;

                float hash(vec2 p) {
                    p = fract(p * vec2(123.34, 456.21));
                    p += dot(p, p + 78.233);
                    return fract(p.x * p.y);
                }

                float noise(vec2 p) {
                    vec2 i = floor(p);
                    vec2 f = fract(p);
                    vec2 u = f * f * (3.0 - 2.0 * f);

                    return mix(
                        mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x),
                        mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x),
                        u.y
                    );
                }

                float fbm(vec2 p) {
                    float value = 0.0;
                    float amplitude = 0.5;
                    for (int i = 0; i < 5; i++) {
                        value += amplitude * noise(p);
                        p = p * 2.03 + vec2(11.7, 7.3);
                        amplitude *= 0.5;
                    }
                    return value;
                }

                void main() {
                    vec2 flow = vWorldPos.xz / scale + drift * time + vec2(seed);
                    float base = fbm(flow);
                    float detail = fbm(flow * detailScale + vec2(0.0, time * 0.01));
                    float wisps = fbm(flow * (detailScale * 0.55) - vec2(time * 0.008, 0.0));
                    float billow = fbm(flow * 0.62 + vec2(seed * 0.37, -seed * 0.21));
                    float erosion = fbm(flow * (detailScale * 1.8) + vec2(seed * 1.3, -seed * 0.9));
                    float ridge = 1.0 - abs(fbm(flow * (detailScale * 0.85) - vec2(seed * 0.6, seed * 0.45)) * 2.0 - 1.0);
                    float shape = base * 0.5 + billow * 0.24 + detail * 0.14 + wisps * 0.12;
                    shape -= erosion * erosionStrength;
                    shape += ridge * ridgeStrength;

                    float densityBoost = mix(0.78, 1.28, clamp(cloudDensity, 0.0, 1.0));
                    float coverageThreshold = mix(0.84, 0.34, clamp(cloudCoverage, 0.0, 1.0));
                    float alpha = smoothstep(
                        coverageThreshold + softness,
                        coverageThreshold - softness,
                        shape * densityBoost
                    );

                    float core = smoothstep(
                        coverageThreshold + softness * 0.4,
                        coverageThreshold - softness * 1.4,
                        shape * densityBoost
                    );
                    vec2 eps = vec2(0.03, 0.0);
                    float sampleA = fbm((flow + lightDir * eps.x) * 0.62 + vec2(seed * 0.37, -seed * 0.21));
                    float sampleB = fbm((flow - lightDir * eps.x) * 0.62 + vec2(seed * 0.37, -seed * 0.21));
                    float lightEdge = clamp((sampleA - sampleB) * 3.2 + 0.5, 0.0, 1.0);
                    float underside = 1.0 - smoothstep(0.32, 0.88, shape);

                    float radial = distance(vPlaneUv, vec2(0.5)) * 2.0;
                    float edgeMask = 1.0 - smoothstep(1.0 - edgeFade, 1.0, radial);
                    alpha *= edgeMask * layerOpacity;
                    if (alpha <= 0.001) discard;

                    vec3 shadowColor = tintColor * (1.0 - shadowStrength);
                    vec3 litColor = mix(shadowColor, tintColor, core);
                    litColor += tintColor * lightEdge * core * highlightStrength;
                    litColor = mix(litColor, shadowColor, underside * shadowStrength * 0.7);
                    gl_FragColor = vec4(litColor, alpha);
                }
            `,
            transparent: true,
            side: THREE.DoubleSide,
            depthWrite: false,
            fog: false
        });

        const geometry = new THREE.CircleGeometry(config.radius, 96);
        const mesh = new THREE.Mesh(geometry, material);
        mesh.rotation.x = -Math.PI / 2;
        mesh.rotation.z = config.rotationZ ?? 0;
        mesh.position.y = config.y;
        this.cloudLayers.push({
            mesh,
            material,
            baseOpacity: config.opacity,
            baseY: config.y,
            baseScale: config.scale,
            baseSoftness: config.softness,
            baseShadowStrength: config.shadowStrength ?? 0.42,
            baseHighlightStrength: config.highlightStrength ?? 0.26,
            baseErosionStrength: config.erosionStrength ?? 0.24,
            baseRidgeStrength: config.ridgeStrength ?? 0.12
        });
        this.cloudGroup.add(mesh);
    }

    initFog() {
        const fogTexture = this.createFogTexture();
        this.fogGroup = new THREE.Group();
        this.fogLayers = [];

        const layerConfigs = [
            { width: 4600, height: 2400, y: 8, opacity: 0.26, speedX: 0.00055, speedY: 0.0001, rotation: 0.08 },
            { width: 3900, height: 1900, y: 22, opacity: 0.2, speedX: -0.00032, speedY: 0.00014, rotation: -0.05 },
            { width: 3200, height: 1500, y: 42, opacity: 0.13, speedX: 0.00024, speedY: -0.00008, rotation: 0.12 }
        ];

        layerConfigs.forEach((config) => {
            const texture = fogTexture.clone();
            texture.wrapS = THREE.RepeatWrapping;
            texture.wrapT = THREE.RepeatWrapping;
            texture.repeat.set(2.4, 1.4);
            texture.needsUpdate = true;

            const material = new THREE.MeshBasicMaterial({
                map: texture,
                alphaMap: texture,
                color: 0xdbe7ef,
                transparent: true,
                opacity: config.opacity,
                depthWrite: false,
                fog: false,
                side: THREE.DoubleSide,
                blending: THREE.NormalBlending
            });

            const geometry = new THREE.PlaneGeometry(config.width, config.height, 1, 1);
            const mesh = new THREE.Mesh(geometry, material);
            mesh.rotation.x = -Math.PI / 2;
            mesh.rotation.z = config.rotation;
            mesh.position.y = config.y;

            this.fogLayers.push({
                mesh,
                texture,
                baseY: config.y,
                baseOpacity: config.opacity,
                speedX: config.speedX,
                speedY: config.speedY
            });

            this.fogGroup.add(mesh);
        });

        this.horizonFog = this.createHorizonFog();
        this.fogGroup.add(this.horizonFog);
        this.skyHazeBand = this.createSkyHazeBand();
        this.fogGroup.add(this.skyHazeBand);
        this.scene.add(this.fogGroup);
        this.updateFog();
    }

    createLightningGlowTexture() {
        const canvas = document.createElement('canvas');
        canvas.width = 256;
        canvas.height = 256;

        const context = canvas.getContext('2d');
        const gradient = context.createRadialGradient(128, 128, 0, 128, 128, 128);
        gradient.addColorStop(0, 'rgba(255,255,255,0.95)');
        gradient.addColorStop(0.18, 'rgba(220,236,255,0.85)');
        gradient.addColorStop(0.42, 'rgba(180,205,255,0.35)');
        gradient.addColorStop(0.72, 'rgba(120,155,255,0.08)');
        gradient.addColorStop(1, 'rgba(120,155,255,0)');
        context.fillStyle = gradient;
        context.fillRect(0, 0, canvas.width, canvas.height);

        const texture = new THREE.CanvasTexture(canvas);
        texture.colorSpace = THREE.SRGBColorSpace;
        texture.needsUpdate = true;
        return texture;
    }

    createFogTexture() {
        const canvas = document.createElement('canvas');
        canvas.width = 512;
        canvas.height = 512;

        const context = canvas.getContext('2d');
        context.clearRect(0, 0, canvas.width, canvas.height);

        for (let i = 0; i < 42; i++) {
            const x = 30 + Math.random() * 452;
            const y = 40 + Math.random() * 432;
            const radius = 34 + Math.random() * 88;
            const gradient = context.createRadialGradient(x, y, 0, x, y, radius);

            gradient.addColorStop(0, 'rgba(255,255,255,0.82)');
            gradient.addColorStop(0.22, 'rgba(255,255,255,0.58)');
            gradient.addColorStop(0.58, 'rgba(255,255,255,0.16)');
            gradient.addColorStop(1, 'rgba(255,255,255,0)');

            context.fillStyle = gradient;
            context.fillRect(x - radius, y - radius, radius * 2, radius * 2);
        }

        const texture = new THREE.CanvasTexture(canvas);
        texture.colorSpace = THREE.SRGBColorSpace;
        texture.needsUpdate = true;

        return texture;
    }

    createHorizonFogTexture() {
        const canvas = document.createElement('canvas');
        canvas.width = 64;
        canvas.height = 512;

        const context = canvas.getContext('2d');
        const gradient = context.createLinearGradient(0, 0, 0, canvas.height);
        gradient.addColorStop(0, 'rgba(255,255,255,0)');
        gradient.addColorStop(0.08, 'rgba(255,255,255,0.02)');
        gradient.addColorStop(0.2, 'rgba(255,255,255,0.12)');
        gradient.addColorStop(0.35, 'rgba(255,255,255,0.38)');
        gradient.addColorStop(0.5, 'rgba(255,255,255,0.55)');
        gradient.addColorStop(0.65, 'rgba(255,255,255,0.38)');
        gradient.addColorStop(0.8, 'rgba(255,255,255,0.12)');
        gradient.addColorStop(0.92, 'rgba(255,255,255,0.02)');
        gradient.addColorStop(1, 'rgba(255,255,255,0)');
        context.fillStyle = gradient;
        context.fillRect(0, 0, canvas.width, canvas.height);

        const texture = new THREE.CanvasTexture(canvas);
        texture.colorSpace = THREE.SRGBColorSpace;
        texture.wrapS = THREE.RepeatWrapping;
        texture.wrapT = THREE.ClampToEdgeWrapping;
        texture.repeat.set(4, 1);
        texture.needsUpdate = true;
        return texture;
    }

    createSkyHazeTexture() {
        const canvas = document.createElement('canvas');
        canvas.width = 64;
        canvas.height = 512;

        const context = canvas.getContext('2d');
        const gradient = context.createLinearGradient(0, 0, 0, canvas.height);
        gradient.addColorStop(0, 'rgba(255,255,255,0)');
        gradient.addColorStop(0.06, 'rgba(255,255,255,0.01)');
        gradient.addColorStop(0.15, 'rgba(255,255,255,0.06)');
        gradient.addColorStop(0.28, 'rgba(255,255,255,0.18)');
        gradient.addColorStop(0.42, 'rgba(255,255,255,0.45)');
        gradient.addColorStop(0.55, 'rgba(255,255,255,0.62)');
        gradient.addColorStop(0.7, 'rgba(255,255,255,0.45)');
        gradient.addColorStop(0.85, 'rgba(255,255,255,0.12)');
        gradient.addColorStop(0.94, 'rgba(255,255,255,0.02)');
        gradient.addColorStop(1, 'rgba(255,255,255,0)');
        context.fillStyle = gradient;
        context.fillRect(0, 0, canvas.width, canvas.height);

        const texture = new THREE.CanvasTexture(canvas);
        texture.colorSpace = THREE.SRGBColorSpace;
        texture.wrapS = THREE.RepeatWrapping;
        texture.wrapT = THREE.ClampToEdgeWrapping;
        texture.repeat.set(3, 1);
        texture.needsUpdate = true;
        return texture;
    }

    createHorizonFog() {
        const texture = this.createHorizonFogTexture();
        const material = new THREE.MeshBasicMaterial({
            map: texture,
            alphaMap: texture,
            color: 0xdde8f2,
            transparent: true,
            opacity: 0.38,
            fog: false,
            depthWrite: false,
            side: THREE.BackSide
        });

        const geometry = new THREE.CylinderGeometry(4700, 4700, 900, 72, 1, true);
        const mesh = new THREE.Mesh(geometry, material);
        mesh.position.y = 130;
        mesh.renderOrder = -1;
        mesh.userData.texture = texture;
        return mesh;
    }

    createSkyHazeBand() {
        const texture = this.createSkyHazeTexture();
        const material = new THREE.MeshBasicMaterial({
            map: texture,
            alphaMap: texture,
            color: 0xdde8f2,
            transparent: true,
            opacity: 0.32,
            fog: false,
            depthWrite: false,
            side: THREE.BackSide,
            blending: THREE.NormalBlending
        });

        const geometry = new THREE.CylinderGeometry(5200, 5200, 1600, 72, 1, true);
        const mesh = new THREE.Mesh(geometry, material);
        mesh.position.y = 420;
        mesh.renderOrder = -2;
        mesh.userData.texture = texture;
        return mesh;
    }
    
    async initWater() {
        const waterGeometry = new THREE.PlaneGeometry(10000, 10000, 128, 128);
        
        const waterNormals = await new Promise((resolve) => {
            new THREE.TextureLoader().load(
                '/textures/waternormals.jpg',
                (texture) => {
                    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
                    resolve(texture);
                }
            );
        });
        
        this.water = new Water(waterGeometry, {
            textureWidth: 512,
            textureHeight: 512,
            waterNormals: waterNormals,
            sunDirection: new THREE.Vector3(),
            sunColor: 0xffffff,
            waterColor: this.params.waterColor,
            distortionScale: 3.7,
            fog: true
        });
        
        this.water.rotation.x = -Math.PI / 2;
        this.water.position.y = -0.15;
        this.setWaterColor(this.params.waterColor);
        this.scene.add(this.water);
    }
    
    async initTerrain() {
        const terrainGen = new TerrainGenerator({
            size: 1200, // 地形平面尺寸
            segments: 200, // 网格细分数，越高细节越多
            maxHeight: 34, // 海面以上地形的最大起伏高度
            waterLevel: 0, // 海平面基准高度
            underwaterDepthBias: 4.5, // 整体压低海面以下地形，避免浅滩露出
            underwaterBiasFadeWidth: 8, // 水下额外下沉的渐变宽度，越大近岸越平滑
            landBias: 0.2, // 整体抬升陆地倾向，增大陆地露出面积
            falloffStartRatio: 0.22, // 从中心向外开始下沉的起始比例
            maxLandRatio: 0.46, // 大陆海岸线的大致外缘比例
            edgeDepth: 12, // 海岸外侧向海底下沉的强度
            coreRadiusRatio: 0.24, // 大陆核心高地区域半径比例
            continentLift: 0.55, // 核心大陆的额外抬升强度
            coastVariance: 0.05, // 海岸线形状起伏幅度，越大越不规则
            outerShelfDepth: 4, // 大陆外侧陆架的额外下沉深度
            coastlineBlendWidth: 42, // 海岸线外侧过渡到海底的缓冲宽度
            seed: 23 // 固定随机种子，保证地形稳定复现
        });
        
        this.terrain = terrainGen.generate();
        this.scene.add(this.terrain);
        
        this.terrainGenerator = terrainGen;
    }
    
    async initVegetation() {
        const vegSystem = new VegetationSystem(this.terrainGenerator, {
            grassCount: 0, // 随机草簇数量，设为 0 时只使用 grassAreas
            shrubCount: 0, // 随机灌木数量，设为 0 时只使用 shrubPlacements
            lowPlantCount: 0, // 随机低矮植物数量，设为 0 时只使用 lowPlantPlacements
            treeCount: 0, // 随机树木数量，设为 0 时只使用 treePlacements
            terrainSize: 1200, // 随机植被允许分布的地形范围
            waterLevel: 1, // 植被生成时参考的水位，避免贴近海边
            treePlacements: [ // 手动指定树木坐标
                { x: 0, z: 50, rotation: 0.4, scale: 1.6 },
                { x: 21, z: 32, rotation: 1.2, scale: 1.35 },
                // { x: -40, z: -150, rotation: 2.1, scale: 1.75 },
                // { x: 70, z: -70, rotation: 2.8, scale: 1.45 },
                // { x: 135, z: 15, rotation: 4.1, scale: 1.55 },
                // { x: 30, z: 120, rotation: 5.2, scale: 1.7 }
            ],
            shrubPlacements: [ // 手动指定灌木坐标
                { x: -210, z: -65, rotation: 0.3, scale: 1.05 },
                { x: -195, z: -75, rotation: 1.4, scale: 0.95 },
                // { x: -20, z: -95, rotation: 2.2, scale: 1.1 },
                // { x: 55, z: -5, rotation: 3.6, scale: 0.9 },
                // { x: 150, z: -55, rotation: 4.5, scale: 1.15 },
                // { x: 185, z: 75, rotation: 5.4, scale: 1.0 }
            ],
            lowPlantPlacements: [ // 手动指定低矮植物坐标
                { x: -235, z: -20, rotation: 0.6, scale: 0.58 },
                { x: -205, z: 15, rotation: 1.8, scale: 0.52 },
                // { x: -10, z: -20, rotation: 2.7, scale: 0.48 },
                // { x: 82, z: -132, rotation: 3.4, scale: 0.62 },
                // { x: 118, z: 58, rotation: 4.2, scale: 0.56 },
                // { x: 225, z: 18, rotation: 5.1, scale: 0.6 }
            ],
            grassAreas: [ // 手动指定草簇生成区域
                { centerX: -140, centerZ: -10, width: 220, depth: 170, count: 4200 },
                { centerX: 110, centerZ: 65, width: 210, depth: 160, count: 3800 }
            ]
        });
        
        this.vegetation = vegSystem.generate();
        this.vegetationSystem = vegSystem;
        vegSystem.addToScene(this.scene);
    }
    
    initSunPosition() {
        this.updateSun();
    }
    
    updateSun() {
        const phi = THREE.MathUtils.degToRad(90 - this.params.elevation);
        const theta = THREE.MathUtils.degToRad(this.params.azimuth);
        
        this.sun.setFromSphericalCoords(1, phi, theta);
        
        this.sky.material.uniforms['sunPosition'].value.copy(this.sun);
        this.water.material.uniforms['sunDirection'].value.copy(this.sun).normalize();
        
        if (this.sunLight) {
            const sunDistance = 100;
            this.sunLight.position.set(
                this.sun.x * sunDistance,
                this.sun.y * sunDistance,
                this.sun.z * sunDistance
            );
        }

        if (this.vegetationFillLight) {
            const fillDistance = 90;
            this.vegetationFillLight.position.set(
                -this.sun.x * fillDistance * 0.45 + 35,
                Math.max(18, this.sun.y * fillDistance * 0.28 + 24),
                -this.sun.z * fillDistance * 0.35 + 28
            );
            this.vegetationFillLight.intensity = THREE.MathUtils.lerp(0.7, 1.15, THREE.MathUtils.clamp((this.sun.y + 0.2) / 0.9, 0, 1));
        }
        
        if (this.renderTarget) {
            this.renderTarget.dispose();
        }
        
        const sceneEnv = new THREE.Scene();
        sceneEnv.add(this.sky);
        this.renderTarget = this.pmremGenerator.fromScene(sceneEnv);
        this.scene.environment = this.renderTarget.texture;
        this.scene.add(this.sky);
        
        this.updateFog();
        this.updateClouds();
    }
    
    getFogColor() {
        const elevation = this.params.elevation;
        
        if (elevation < 0) {
            return 0x1a2a3a;
        } else if (elevation < 10) {
            return 0x4a5a6a;
        } else if (elevation < 20) {
            return 0x8cb8d4;
        } else if (elevation < 45) {
            return 0x9ec5db;
        } else if (elevation < 70) {
            return 0xb8d4e8;
        } else {
            return 0xd4e8f4;
        }
    }

    getAtmosphereColors() {
        const sunMix = THREE.MathUtils.clamp((this.params.elevation + 10) / 100, 0, 1);
        const fogColor = new THREE.Color(this.getFogColor());
        const warmHorizon = new THREE.Color(0xf0c7a3);
        const coolHorizon = new THREE.Color(0xcfe0ee);
        const horizonColor = warmHorizon.clone().lerp(coolHorizon, sunMix);
        const warmSkyBase = new THREE.Color(0xf6d7b8);
        const coolSkyBase = new THREE.Color(0xbfd8eb);
        const skyBaseColor = warmSkyBase.clone().lerp(coolSkyBase, sunMix * 0.92);
        const skyBlendColor = skyBaseColor.clone().lerp(fogColor, 0.42);

        return { sunMix, fogColor, horizonColor, skyBaseColor, skyBlendColor };
    }
    
    initEventListeners() {
        window.addEventListener('resize', () => this.onWindowResize());
    }
    
    onWindowResize() {
        this.camera.aspect = window.innerWidth / window.innerHeight;
        this.camera.updateProjectionMatrix();
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        if (this.composer) {
            this.composer.setSize(window.innerWidth, window.innerHeight);
        }
        if (this.rainPass) {
            this.rainPass.material.uniforms.resolution.value.set(window.innerWidth, window.innerHeight);
        }
        if (this.snowPass) {
            this.snowPass.material.uniforms.resolution.value.set(window.innerWidth, window.innerHeight);
        }
    }
    
    setSunElevation(value) {
        this.params.elevation = value;
        this.updateSun();
    }
    
    setSunAzimuth(value) {
        this.params.azimuth = value;
        this.updateSun();
    }
    
    setExposure(value) {
        this.params.exposure = value;
        this.renderer.toneMappingExposure = value;
    }
    
    setTurbidity(value) {
        this.params.turbidity = value;
        this.sky.material.uniforms['turbidity'].value = value;
        this.updateSun();
    }
    
    setRayleigh(value) {
        this.params.rayleigh = value;
        this.sky.material.uniforms['rayleigh'].value = value;
        this.updateSun();
    }

    setBloomStrength(value) {
        this.params.bloomStrength = value;
        if (this.bloomPass) {
            this.bloomPass.strength = value;
        }
    }

    setBloomRadius(value) {
        this.params.bloomRadius = value;
        if (this.bloomPass) {
            this.bloomPass.radius = value;
        }
    }

    setBloomThreshold(value) {
        this.params.bloomThreshold = value;
        if (this.bloomPass) {
            this.bloomPass.threshold = value;
        }
    }

    setWaterColor(value) {
        this.params.waterColor = value;
        if (this.water?.material?.uniforms?.waterColor?.value) {
            this.water.material.uniforms.waterColor.value.set(value);
        }
    }

    setCloudCoverage(value) {
        this.params.cloudCoverage = value;
        this.updateClouds();
    }

    setCloudDensity(value) {
        this.params.cloudDensity = value;
        this.updateClouds();
    }

    setCloudElevation(value) {
        this.params.cloudElevation = value;
        if (this.cloudGroup) {
            this.cloudGroup.position.y = THREE.MathUtils.lerp(-160, 260, value);
        }
    }

    setFogDensity(value) {
        this.params.fogDensity = value;
        this.updateFog();
    }

    setFogHeight(value) {
        this.params.fogHeight = value;
        this.updateFog();
    }

    setFogRange(value) {
        this.params.fogRange = value;
        this.updateFog();
    }

    setRainEnabled(value) {
        this.params.rainEnabled = value;
        if (this.rainPass) {
            this.rainPass.enabled = value;
        }
        this.updateRainAudioState();
        if (!value) {
            this.lightningFlash = 0;
            this.lightningLocalFlash = 0;
            this.lightningBurstEnd = 0;
            this.nextLightningAt = 0;
            this.lightningPulseSchedule = [];
            this.scheduledThunder = [];
            this.applyLightningState(0);
            this.stopThunderAudio();
        }
    }

    setRainScreenIntensity(value) {
        this.params.rainScreenIntensity = value;
        if (this.rainPass) {
            this.rainPass.material.uniforms.screenIntensity.value = value;
        }
    }

    setRainVeilIntensity(value) {
        this.params.rainVeilIntensity = value;
        if (this.rainPass) {
            this.rainPass.material.uniforms.veilIntensity.value = value;
        }
    }

    setRainDropSize(value) {
        this.params.rainDropSize = value;
        if (this.rainPass) {
            this.rainPass.material.uniforms.dropSize.value = value;
        }
    }

    setRainSpeed(value) {
        this.params.rainSpeed = value;
        if (this.rainPass) {
            this.rainPass.material.uniforms.rainSpeed.value = value;
        }
    }

    setRainAudioEnabled(value) {
        this.params.rainAudioEnabled = value;
        this.updateRainAudioState();
    }

    setRainAudioVolume(value) {
        this.params.rainAudioVolume = value;
        this.updateRainAudioState();
    }

    setSnowEnabled(value) {
        this.params.snowEnabled = value;
        if (this.snowPass) {
            this.snowPass.enabled = value;
        }
    }

    setSnowIntensity(value) {
        this.params.snowIntensity = value;
        if (this.snowPass) {
            this.snowPass.material.uniforms.intensity.value = value;
        }
    }

    setSnowSpeed(value) {
        this.params.snowSpeed = value;
        if (this.snowPass) {
            this.snowPass.material.uniforms.snowSpeed.value = value;
        }
    }

    updateRainAudioState() {
        if (this.rainAudioPool.length === 0) return;

        const shouldPlay = this.params.rainEnabled && this.params.rainAudioEnabled && this.params.rainAudioVolume > 0.001;
        if (shouldPlay) {
            if (!this.rainAudioIsPlaying) {
                const active = this.rainAudioPool[this.rainAudioActiveIndex];
                active.currentTime = 0;
                active.volume = this.params.rainAudioVolume;
                const playPromise = active.play();
                if (playPromise?.catch) {
                    playPromise.catch(() => {});
                }
                this.rainAudioIsPlaying = true;
                this.rainAudioCrossfading = false;
            }
        } else {
            for (const audio of this.rainAudioPool) {
                audio.pause();
                audio.currentTime = 0;
                audio.volume = 0;
            }
            this.rainAudioIsPlaying = false;
            this.rainAudioCrossfading = false;
        }
    }

    updateRainAudioLoop() {
        if (!this.rainAudioIsPlaying || this.rainAudioPool.length < 2) return;

        const active = this.rainAudioPool[this.rainAudioActiveIndex];
        const next = this.rainAudioPool[(this.rainAudioActiveIndex + 1) % this.rainAudioPool.length];
        const duration = Number.isFinite(active.duration) ? active.duration : 0;
        if (duration <= 0) {
            active.volume = this.params.rainAudioVolume;
            return;
        }

        const timeLeft = duration - active.currentTime;
        if (!this.rainAudioCrossfading && timeLeft <= this.rainAudioCrossfadeDuration) {
            next.currentTime = 0;
            next.volume = 0;
            const playPromise = next.play();
            if (playPromise?.catch) {
                playPromise.catch(() => {});
            }
            this.rainAudioCrossfading = true;
        }

        if (this.rainAudioCrossfading) {
            const progress = THREE.MathUtils.clamp(1.0 - timeLeft / this.rainAudioCrossfadeDuration, 0, 1);
            active.volume = this.params.rainAudioVolume * (1.0 - progress);
            next.volume = this.params.rainAudioVolume * progress;

            if (progress >= 0.999 || active.ended) {
                active.pause();
                active.currentTime = 0;
                active.volume = 0;
                this.rainAudioActiveIndex = (this.rainAudioActiveIndex + 1) % this.rainAudioPool.length;
                this.rainAudioCrossfading = false;
            }
        } else {
            active.volume = this.params.rainAudioVolume;
        }
    }

    setLightningEnabled(value) {
        this.params.lightningEnabled = value;
        if (!value) {
            this.lightningFlash = 0;
            this.lightningBurstEnd = 0;
            this.nextLightningAt = 0;
            this.lightningPulseSchedule = [];
            this.scheduledThunder = [];
            this.applyLightningState(0);
            this.stopThunderAudio();
        }
    }

    setLightningIntensity(value) {
        this.params.lightningIntensity = value;
    }

    applyParams(nextParams = {}) {
        const mergedParams = { ...DEFAULT_SCENE_PARAMS, ...nextParams };

        this.setSunElevation(mergedParams.elevation);
        this.setSunAzimuth(mergedParams.azimuth);
        this.setExposure(mergedParams.exposure);
        this.setTurbidity(mergedParams.turbidity);
        this.setRayleigh(mergedParams.rayleigh);
        this.setBloomStrength(mergedParams.bloomStrength);
        this.setBloomRadius(mergedParams.bloomRadius);
        this.setWaterColor(mergedParams.waterColor);
        this.setCloudCoverage(mergedParams.cloudCoverage);
        this.setCloudDensity(mergedParams.cloudDensity);
        this.setCloudElevation(mergedParams.cloudElevation);
        this.setFogDensity(mergedParams.fogDensity);
        this.setFogHeight(mergedParams.fogHeight);
        this.setFogRange(mergedParams.fogRange);
        this.setRainScreenIntensity(mergedParams.rainScreenIntensity);
        this.setRainVeilIntensity(mergedParams.rainVeilIntensity);
        this.setRainDropSize(mergedParams.rainDropSize);
        this.setRainSpeed(mergedParams.rainSpeed);
        this.setRainAudioVolume(mergedParams.rainAudioVolume);
        this.setRainAudioEnabled(mergedParams.rainAudioEnabled);
        this.setSnowIntensity(mergedParams.snowIntensity);
        this.setSnowSpeed(mergedParams.snowSpeed);
        this.setSnowEnabled(mergedParams.snowEnabled);
        this.setLightningIntensity(mergedParams.lightningIntensity);
        this.setLightningEnabled(mergedParams.lightningEnabled);
        this.setRainEnabled(mergedParams.rainEnabled);
    }

    scheduleNextLightning(time) {
        const rainActivity = Math.max(this.params.rainVeilIntensity, this.params.rainScreenIntensity);
        const densityBias = THREE.MathUtils.clamp(rainActivity / 1.5, 0, 1);
        const delay = THREE.MathUtils.lerp(7.5, 3.0, densityBias) + Math.random() * THREE.MathUtils.lerp(8.0, 4.0, densityBias);
        this.nextLightningAt = time + delay;
    }

    startLightningBurst(time) {
        const pulseCountRoll = Math.random();
        const pulseCount = pulseCountRoll > 0.8 ? 3 : pulseCountRoll > 0.45 ? 2 : 1;
        this.lightningPulseSchedule = [];

        let pulseTime = time;
        for (let i = 0; i < pulseCount; i++) {
            const duration = 0.05 + Math.random() * 0.07;
            const amplitude = this.params.lightningIntensity * (1.0 - i * 0.14) * (0.85 + Math.random() * 0.35);
            this.lightningPulseSchedule.push({
                time: pulseTime,
                duration,
                amplitude
            });
            pulseTime += 0.06 + Math.random() * 0.12;
        }

        this.lightningBurstEnd = pulseTime + 0.12;

        const flashX = THREE.MathUtils.randFloat(-1600, 1600);
        const flashY = THREE.MathUtils.randFloat(120, 340);
        const flashZ = THREE.MathUtils.randFloat(-1800, -420);
        this.lightningLight.position.set(flashX * 0.22, flashY, flashZ * 0.08);

        if (this.lightningCloudGlow) {
            this.lightningCloudGlow.position.set(flashX, flashY, flashZ);
            const size = THREE.MathUtils.randFloat(720, 1480);
            this.lightningCloudGlow.scale.set(size, size * THREE.MathUtils.randFloat(0.72, 1.08), 1);
        }

        this.scheduleThunderBurst(flashX, flashY, flashZ);
    }

    scheduleThunderBurst(flashX, flashY, flashZ) {
        const distanceNorm = THREE.MathUtils.clamp(
            (Math.abs(flashX) / 1600) * 0.35 + (Math.abs(flashZ) / 1800) * 0.65,
            0,
            1
        );
        const delay = THREE.MathUtils.lerp(0.65, 2.4, distanceNorm) + Math.random() * 0.45;
        const volume = this.params.lightningIntensity * THREE.MathUtils.lerp(1.0, 0.58, distanceNorm) * 1.12;
        this.scheduledThunder.push({
            playAt: this.lightningBurstEnd + delay,
            volume,
            playbackRate: THREE.MathUtils.randFloat(0.94, 1.03)
        });
    }

    stopThunderAudio() {
        for (const audio of this.thunderAudioPool) {
            audio.pause();
            audio.currentTime = 0;
        }
    }

    playThunder(volume, playbackRate) {
        if (!this.params.rainEnabled || !this.params.lightningEnabled || this.thunderAudioPool.length === 0) return;

        const audio = this.thunderAudioPool[this.thunderAudioIndex % this.thunderAudioPool.length];
        this.thunderAudioIndex += 1;
        audio.pause();
        audio.currentTime = 0;
        audio.volume = THREE.MathUtils.clamp(volume, 0, 1);
        audio.playbackRate = playbackRate;
        const playPromise = audio.play();
        if (playPromise?.catch) {
            playPromise.catch(() => {});
        }
    }

    updateThunder(time) {
        if (!this.params.rainEnabled || !this.params.lightningEnabled || this.scheduledThunder.length === 0) return;

        const pending = [];
        for (const thunder of this.scheduledThunder) {
            if (time >= thunder.playAt) {
                this.playThunder(thunder.volume, thunder.playbackRate);
            } else {
                pending.push(thunder);
            }
        }
        this.scheduledThunder = pending;
    }

    updateLightning(time) {
        if (!this.params.rainEnabled || !this.params.lightningEnabled) return;

        if (this.nextLightningAt === 0) {
            this.scheduleNextLightning(time);
        }

        if (time >= this.nextLightningAt && time >= this.lightningBurstEnd) {
            this.startLightningBurst(time);
            this.scheduleNextLightning(time);
        }

        let flash = 0;
        let localFlash = 0;
        for (const pulse of this.lightningPulseSchedule) {
            const dt = time - pulse.time;
            if (dt < -0.001 || dt > pulse.duration * 2.8) continue;

            const attack = Math.exp(-Math.pow((dt - pulse.duration * 0.12) / Math.max(pulse.duration * 0.32, 0.001), 2.0));
            const decay = Math.exp(-Math.max(dt, 0.0) / Math.max(pulse.duration * 1.45, 0.001));
            const pulseFlash = pulse.amplitude * attack * decay;
            flash += pulseFlash;
            localFlash += pulseFlash * 1.35;
        }

        if (flash > 0.001) {
            this.lightningFlash = Math.max(this.lightningFlash * 0.7, flash);
            this.lightningLocalFlash = Math.max(this.lightningLocalFlash * 0.62, localFlash);
        } else {
            this.lightningFlash *= 0.82;
            if (this.lightningFlash < 0.002) this.lightningFlash = 0;
            this.lightningLocalFlash *= 0.74;
            if (this.lightningLocalFlash < 0.002) this.lightningLocalFlash = 0;
        }

        this.applyLightningState(this.lightningFlash, this.lightningLocalFlash);
    }

    applyLightningState(flash, localFlash = 0) {
        if (this.lightningLight) {
            this.lightningLight.intensity = flash * 5.5;
        }

        if (this.lightningCloudGlow) {
            this.lightningCloudGlow.visible = localFlash > 0.002;
            this.lightningCloudGlow.material.opacity = THREE.MathUtils.clamp(localFlash * 0.95, 0, 0.95);
        }

        if (this.renderer) {
            this.renderer.toneMappingExposure = this.params.exposure * (1.0 + flash * 1.6);
        }

        if (this.bloomPass) {
            this.bloomPass.strength = this.params.bloomStrength + flash * 0.35;
        }
    }

    updateClouds() {
        if (!this.cloudGroup) return;

        const sunMix = THREE.MathUtils.clamp((this.params.elevation + 10) / 100, 0, 1);
        const dawnMix = 1.0 - THREE.MathUtils.smoothstep(this.params.elevation, 8, 34);
        const rainMix = this.params.rainEnabled ? THREE.MathUtils.clamp(this.params.rainVeilIntensity / 2.0, 0.35, 1.0) : 0;
        const snowMix = this.params.snowEnabled ? THREE.MathUtils.clamp(this.params.snowIntensity / 1.5, 0.35, 1.0) : 0;
        const stormMix = Math.max(rainMix, snowMix);
        const warmCloud = new THREE.Color(0xdab188);
        const dayCloud = new THREE.Color(0xd1dbe6);
        const cloudColor = warmCloud.lerp(dayCloud, sunMix);
        const lightningMix = THREE.MathUtils.clamp(this.lightningFlash * 0.32, 0, 1);
        cloudColor.lerp(new THREE.Color(0xbfcad6), stormMix * 0.45);
        cloudColor.lerp(new THREE.Color(0xe9f3ff), lightningMix);
        const lightDir = new THREE.Vector2(this.sun.x, this.sun.z).normalize();

        this.cloudLayers.forEach((layer, index) => {
            const coverageFactor = 0.15 + this.params.cloudCoverage * 1.15;
            const densityFactor = 0.2 + this.params.cloudDensity * 1.35;
            const layerDepth = index / Math.max(this.cloudLayers.length - 1, 1);
            const weatherOpacityBoost = THREE.MathUtils.lerp(1.0, 1.42 - layerDepth * 0.16, rainMix);
            const snowOpacityBoost = THREE.MathUtils.lerp(1.0, 1.22 - layerDepth * 0.08, snowMix);
            const opacity = layer.baseOpacity * coverageFactor * densityFactor * weatherOpacityBoost * snowOpacityBoost;
            const heightDrop = THREE.MathUtils.lerp(0, 120 + index * 55, rainMix);
            const snowLift = THREE.MathUtils.lerp(0, 40 + index * 24, snowMix);
            const layerScale = layer.baseScale * THREE.MathUtils.lerp(1.18 - index * 0.06, 0.9 + index * 0.04, rainMix);
            const softness = layer.baseSoftness * THREE.MathUtils.lerp(1.15, 0.82, rainMix) * THREE.MathUtils.lerp(1.0, 1.08, snowMix);
            const erosion = layer.baseErosionStrength * THREE.MathUtils.lerp(0.72 + index * 0.08, 1.28 + index * 0.1, rainMix);
            const ridge = layer.baseRidgeStrength * THREE.MathUtils.lerp(1.24, 0.78, rainMix) * THREE.MathUtils.lerp(1.0, 0.86, snowMix);
            const highlight = layer.baseHighlightStrength * THREE.MathUtils.lerp(1.45 - index * 0.12, 0.62, rainMix) * THREE.MathUtils.lerp(1.12, 0.84, snowMix);
            const shadow = layer.baseShadowStrength * THREE.MathUtils.lerp(0.94, 1.36, stormMix);

            layer.material.uniforms.layerOpacity.value = opacity;
            layer.material.uniforms.cloudCoverage.value = this.params.cloudCoverage;
            layer.material.uniforms.cloudDensity.value = this.params.cloudDensity;
            layer.material.uniforms.scale.value = layerScale;
            layer.material.uniforms.softness.value = softness;
            layer.material.uniforms.erosionStrength.value = erosion;
            layer.material.uniforms.ridgeStrength.value = ridge;
            layer.material.uniforms.highlightStrength.value = highlight;
            layer.material.uniforms.shadowStrength.value = shadow;
            layer.material.uniforms.tintColor.value.copy(cloudColor);
            layer.material.uniforms.lightDir.value.copy(lightDir);
            layer.mesh.position.y = layer.baseY - heightDrop + snowLift + dawnMix * index * 18.0;
            layer.mesh.visible = opacity > 0.015;
        });
    }

    updateFog() {
        const { sunMix, fogColor, horizonColor, skyBaseColor, skyBlendColor } = this.getAtmosphereColors();
        const fogDensity = THREE.MathUtils.lerp(0.00015, 0.0018, this.params.fogDensity);
        const lightningMix = THREE.MathUtils.clamp(this.lightningFlash * 0.42, 0, 1);
        fogColor.lerp(new THREE.Color(0xdbe8f5), lightningMix);
        horizonColor.lerp(new THREE.Color(0xe5eef9), lightningMix);
        skyBlendColor.lerp(new THREE.Color(0xdbe7f3), lightningMix);

        if (this.scene.fog) {
            this.scene.fog.color.copy(fogColor);
            this.scene.fog.density = fogDensity * THREE.MathUtils.lerp(0.7, 1.4, this.params.fogRange);
        }

        if (!this.fogGroup) return;

        const fogLayerColor = horizonColor.clone().lerp(fogColor, 0.55);
        const heightBase = THREE.MathUtils.lerp(-20, 95, this.params.fogHeight);
        const verticalSpread = THREE.MathUtils.lerp(20, 110, this.params.fogHeight);
        const rangeOpacity = THREE.MathUtils.lerp(0.55, 1.35, this.params.fogRange);

        this.fogLayers.forEach((layer, index) => {
            layer.mesh.position.y = heightBase + index * verticalSpread * 0.36;
            layer.mesh.scale.setScalar(THREE.MathUtils.lerp(0.82, 1.2, this.params.fogRange));
            layer.mesh.material.opacity = layer.baseOpacity * THREE.MathUtils.lerp(0.18, 1.35, this.params.fogDensity) * rangeOpacity;
            layer.mesh.material.color.copy(fogLayerColor);
            layer.mesh.visible = layer.mesh.material.opacity > 0.01;
        });

        if (this.horizonFog) {
            this.horizonFog.material.color.copy(horizonColor);
            this.horizonFog.material.opacity =
                THREE.MathUtils.lerp(0.16, 0.5, this.params.fogDensity) *
                THREE.MathUtils.lerp(0.7, 1.28, this.params.fogRange);
            this.horizonFog.position.y = THREE.MathUtils.lerp(70, 190, this.params.fogHeight);
            this.horizonFog.scale.set(
                THREE.MathUtils.lerp(0.88, 1.28, this.params.fogRange),
                THREE.MathUtils.lerp(0.8, 1.18, this.params.fogHeight),
                THREE.MathUtils.lerp(0.88, 1.28, this.params.fogRange)
            );
            this.horizonFog.visible = this.horizonFog.material.opacity > 0.01;
        }

        if (this.skyHazeBand) {
            this.skyHazeBand.material.color.copy(skyBlendColor);
            this.skyHazeBand.material.opacity =
                THREE.MathUtils.lerp(0.1, 0.32, this.params.fogDensity) *
                THREE.MathUtils.lerp(0.82, 1.18, this.params.fogRange);
            this.skyHazeBand.position.y = THREE.MathUtils.lerp(300, 520, this.params.fogHeight);
            this.skyHazeBand.scale.set(
                THREE.MathUtils.lerp(0.96, 1.16, this.params.fogRange),
                THREE.MathUtils.lerp(0.88, 1.14, this.params.fogHeight),
                THREE.MathUtils.lerp(0.96, 1.16, this.params.fogRange)
            );
            this.skyHazeBand.visible = this.skyHazeBand.material.opacity > 0.01;
        }
    }
    
    animate() {
        requestAnimationFrame(() => this.animate());
        this.stats?.begin();
        
        const time = this.clock.getElapsedTime();
        this.updateLightning(time);
        this.updateThunder(time);
        this.updateRainAudioLoop();
        
        if (this.water) {
            this.water.material.uniforms['time'].value += 1.0 / 60.0;
        }

        if (this.cloudGroup) {
            this.cloudLayers.forEach((layer) => {
                layer.material.uniforms.time.value = time;
            });
        }

        if (this.fogGroup) {
            this.fogLayers.forEach((layer, index) => {
                layer.texture.offset.x = time * layer.speedX;
                layer.texture.offset.y = time * layer.speedY;
                layer.mesh.rotation.z += Math.sin(time * 0.05 + index) * 0.00002;
            });
            if (this.horizonFog?.userData.texture) {
                this.horizonFog.userData.texture.offset.x = time * 0.00035;
            }
            if (this.skyHazeBand?.userData.texture) {
                this.skyHazeBand.userData.texture.offset.x = -time * 0.00018;
            }
        }

        if (this.vegetationSystem) {
            this.vegetationSystem.update(time);
        }

        if (this.lightningFlash > 0.001) {
            this.updateClouds();
            this.updateFog();
        }

        if (this.rainPass) {
            this.rainPass.material.uniforms.time.value = time;
        }
        if (this.snowPass) {
            this.snowPass.material.uniforms.time.value = time;
        }
        
        this.controls.update();
        if (this.composer) {
            this.composer.render();
        } else {
            this.renderer.render(this.scene, this.camera);
        }
        this.stats?.end();
    }
    
    hideLoading() {
        const loading = document.getElementById('loading');
        if (loading) {
            loading.style.opacity = '0';
            loading.style.transition = 'opacity 0.5s ease';
            setTimeout(() => {
                loading.style.display = 'none';
            }, 500);
        }
    }
}
