// ============================================================================
// CLOUD RENDERING
// ============================================================================
#ifndef CLOUDS_GLSL
#define CLOUDS_GLSL

#include "/lib/weather.glsl"
#include "/lib/noises.glsl"

float getSingleNoise(int type, vec3 p) {
    vec2 uv = p.xy;
    if (type == 1) return texture2DLod(noisetex1, uv, 0.0).r;
    if (type == 2) return texture2DLod(noisetex2, uv, 0.0).r;
    if (type == 3) return texture2DLod(noisetex3, uv, 0.0).r;
    if (type == 4) return texture2DLod(noisetex4, uv, 0.0).r;
    if (type == 5) return texture2DLod(noisetex5, uv, 0.0).r;
    return texture2DLod(noisetex6, uv, 0.0).r;
}

vec2 roundedCoord(vec2 pos, float roundness) {
    vec2 coord = pos + 0.5;
    vec2 signCoord = sign(coord);
    coord = abs(coord) + 1.0;
    vec2 i, f = modf(coord, i);
    f = smoothstep(0.5 - roundness, 0.5 + roundness, f);
    coord = i + f;
    return (coord - 0.5) * signCoord / 256.0;
}

// Switch function to fetch the selected noise type efficiently.
float getCloudNoise(vec3 p, int layerIdx) {
    float noise_val = 0.0;

    #if CLOUD_NOISE_TYPE <= 5
        #if CLOUD_NOISE_TYPE == 1
            noise_val = vanillaNoise3D(p);
        #elif CLOUD_NOISE_TYPE == 2
            noise_val = perlinNoise3D(p);
        #elif CLOUD_NOISE_TYPE == 3
            noise_val = simplexNoise3D(p);
        #elif CLOUD_NOISE_TYPE == 4
            noise_val = worleyNoise3D(p);
        #elif CLOUD_NOISE_TYPE == 5
            noise_val = perlinWorleyNoise3D(p);
        #endif
    #else
        // --- Textures 2D MIX ---
        vec2 uv = p.xy + (p.z * 0.2);
        
        vec2 uvB = (uv * 1.31) + vec2(0.14, 0.68); 
        
        float texA = 0.0;
        float texB = 0.0;

        if (layerIdx == 1) {
            texA = texture2DLod(noisetex1, uv, 0.0).r;
            texB = texture2DLod(noisetex2, uvB, 0.0).r;
        } else if (layerIdx == 2) {
            texA = texture2DLod(noisetex3, uv, 0.0).r;
            texB = texture2DLod(noisetex4, uvB, 0.0).r;
        } else {
            texA = texture2DLod(noisetex5, uv, 0.0).r;
            texB = texture2DLod(noisetex6, uvB, 0.0).r;
        }

        noise_val = mix(texA, texB, float(CLOUD_TEXTURE_BLEND));
    #endif

    return noise_val;
}

// Cloud coverage and density mapping.
float cloudMap(vec2 worldXZ, float time, float coverage, float narrowness, int layerIdx) {
    vec2 windOff = getWindOffset(float(CLOUD_SPEED_MULTIPLIER), float(CLOUD_RAIN_SPEED_MULTIPLIER), float(CLOUD_WIND_VARIATION_INTENSITY));
    vec2 moved = worldXZ + windOff;
    
    int n1 = 1; int n2 = 1;
    float s1 = 1.0; float s2 = 1.0;
    float mixFactor = 0.5;
    
    if (layerIdx == 1) {
        n1 = CLOUD_L1_NOISE_1; n2 = CLOUD_L1_NOISE_2;
        s1 = float(CLOUD_L1_SCALE_1); s2 = float(CLOUD_L1_SCALE_2);
        mixFactor = float(CLOUD_L1_MIX);
    } else if (layerIdx == 2) {
        moved += vec2(5333.0, 2187.0); 
        n1 = CLOUD_L2_NOISE_1; n2 = CLOUD_L2_NOISE_2;
        s1 = float(CLOUD_L2_SCALE_1); s2 = float(CLOUD_L2_SCALE_2);
        mixFactor = float(CLOUD_L2_MIX);
    } else {
        moved += vec2(-2814.0, 6942.0);
        n1 = CLOUD_L3_NOISE_1; n2 = CLOUD_L3_NOISE_2;
        s1 = float(CLOUD_L3_SCALE_1); s2 = float(CLOUD_L3_SCALE_2);
        mixFactor = float(CLOUD_L3_MIX);
    }

    float baseScale = (narrowness * 0.15) / float(CLOUD_NOISE_SMOOTHNESS);
    
    // TEMPS DE BASE (Constant) : Utilisé pour le glissement XY
    float baseMorphTime = time * float(CLOUD_MORPH_SPEED) * 0.05; 
    
    // TEMPS DE MORPHING Z (Boosté) : Accélère la transformation sur place pendant la pluie
    float currentRainSpeed = mix(1.0, float(CLOUD_RAIN_SPEED_MULTIPLIER), rainStrength);
    float zMorphTime = baseMorphTime * currentRainSpeed; 
    
    float angleRad = radians(float(WIND_BASE_ANGLE));
    vec2 windDir = vec2(sin(angleRad), cos(angleRad));
    
    // On utilise le temps constant pour le décalage XY afin de bloquer tout effet de téléportation
    vec2 morphOffset = windDir * baseMorphTime * 15.0; 
    
    // On applique le zMorphTime accéléré uniquement sur l'axe Z (l'évolution du nuage)
    vec3 p3_1 = vec3((moved + morphOffset) * baseScale * s1, zMorphTime);
    vec3 p3_2 = vec3((moved - morphOffset) * baseScale * s2, zMorphTime);
    
    float f = 0.0;
    float sample1 = 0.0;
    float sample2 = 0.0;
    
    // --- Octave 1 ---
    sample1 = getSingleNoise(n1, p3_1);
    sample2 = getSingleNoise(n2, p3_2);
    f += 0.5000 * mix(sample1, sample2, mixFactor);
    
    // --- Octave 2 ---
    p3_1.xy = p3_1.xy * 2.02 + vec2(12.3); 
    p3_1.z *= 1.2;
    p3_2.xy = p3_2.xy * 2.02 + vec2(12.3); 
    p3_2.z *= 1.2;
    
    sample1 = getSingleNoise(n1, p3_1);
    sample2 = getSingleNoise(n2, p3_2);
    f += 0.2500 * mix(sample1, sample2, mixFactor);

    float currentCoverage = coverage + (rainStrength * float(CLOUD_RAIN_COVERAGE));
    float cutoff = 1.0 - (currentCoverage * 1.2);
    float shoreWidth = mix(0.001, 0.04, float(CLOUD_SHORE));
    
    if (f + 0.1875 < (0.1 + 0.8 * sqrt(max(cutoff - shoreWidth, 0.0) / 3.0))) {
        return 0.0;
    }
    
    // --- Octave 3 ---
    p3_1.xy = p3_1.xy * 2.03 + vec2(45.6); 
    p3_1.z *= 1.2;
    p3_2.xy = p3_2.xy * 2.03 + vec2(45.6); 
    p3_2.z *= 1.2;
    
    sample1 = getSingleNoise(n1, p3_1);
    sample2 = getSingleNoise(n2, p3_2);
    f += 0.1250 * mix(sample1, sample2, mixFactor);

    // --- Octave 4 ---
    p3_1.xy = p3_1.xy * 2.01 + vec2(78.9); 
    p3_1.z *= 1.2;
    p3_2.xy = p3_2.xy * 2.01 + vec2(78.9); 
    p3_2.z *= 1.2;
    
    sample1 = getSingleNoise(n1, p3_1);
    sample2 = getSingleNoise(n2, p3_2);
    f += 0.0625 * mix(sample1, sample2, mixFactor);
    
    f = smoothstep(0.1, 0.9, f);
    return smoothstep(cutoff - shoreWidth, cutoff + shoreWidth, f);
}

// DDA Raymarching for cloud rendering.
bool raySlabIntersect(vec3 origin, vec3 dir, float yBottom, float yTop, out float tNear, out float tFar) {
    if (abs(dir.y) < 0.0005) {
        if (origin.y >= yBottom && origin.y <= yTop) { tNear = 0.0;
        tFar = 1e6; return true; }
        return false;
    }
    float t0 = (yBottom - origin.y) / dir.y;
    float t1 = (yTop    - origin.y) / dir.y;
    tNear = min(t0, t1);
    tFar  = max(t0, t1);
    if (tFar < 0.0) return false;
    tNear = max(tNear, 0.0);
    return tNear < tFar;
}

vec4 traceLayer(
    vec3 camPos, vec3 dir, float tNear, float tFar, float layerBottom, float layerTop, 
    float coverage, float cellSize, float narrowness, float time,
    float cloudRenderDist, float smoothDown, float smoothUp, int layerIdx,
    vec3 zenithCol, vec3 horizonCol, float maxDist
) {
    int maxSteps = min(int(cloudRenderDist / (cellSize * 0.7071)) + 2, 1536);
    float layerRenderDist = min(cloudRenderDist, float(maxSteps) * cellSize * 0.7071);
    
    if (tNear >= tFar || tNear >= layerRenderDist || tNear >= maxDist) return vec4(0.0);
    vec2 windOff = getWindOffset(float(CLOUD_SPEED_MULTIPLIER), float(CLOUD_RAIN_SPEED_MULTIPLIER), float(CLOUD_WIND_VARIATION_INTENSITY));
    vec3 entryPos = camPos + dir * tNear;
    vec2 startXZ = entryPos.xz + windOff;
    vec2 dirXZ = dir.xz;
    bool cameraInside = !(camPos.y < layerBottom || camPos.y > layerTop);
    vec2 cellCenter = floor((entryPos.xz + windOff) / cellSize) * cellSize + (cellSize * 0.5);
    vec2 cellWorldXZ = cellCenter - windOff;
    vec4 accColor = vec4(0.0);
	
	// Precompute colors before the raymarching loop for performance.
    float skyBrightness = clamp(length(zenithCol) * 1.5, 0.05, 1.0);
    vec3 baseCloudColor = vec3(1.0) * skyBrightness;
    vec3 preTopColor = mix(baseCloudColor, zenithCol, CLOUD_SKY_BLEND * 0.3);
    vec3 preBottomColor = mix(baseCloudColor * (1.0 - CLOUD_BASE_DARKNESS), horizonCol, CLOUD_SKY_BLEND);
    float rainDarkening = mix(1.0, 1.0 - float(CLOUD_RAIN_DARKNESS), rainStrength);
    preTopColor *= rainDarkening;
    preBottomColor *= rainDarkening;
    
    // Track previous density for proper depth accumulation.
    float prevDensity = 0.0;
    float blockDensityCap = cloudMap(cellWorldXZ, time, coverage, narrowness, layerIdx);

    // Handle camera entering the cloud layer.
    if (!cameraInside && blockDensityCap > 0.001) {
        float hitDist = length(entryPos.xz - camPos.xz);
        
        // Start fading alpha earlier during rain.
        float alphaFadeStart = mix(0.6, 0.2, rainStrength);
        float distFade = 1.0 - smoothstep(layerRenderDist * alphaFadeStart, layerRenderDist, hitDist);

        if (distFade > 0.001) {
            float localY = clamp((entryPos.y - layerBottom) / (layerTop - layerBottom), 0.0, 1.0);
            vec3 col = mix(preBottomColor, preTopColor, smoothstep(0.0, 1.0, localY));
            
            // Force clouds to become 100% horizon color BEFORE they fully disappear.
            float colorBlendStart = mix(0.4, 0.05, rainStrength);
            float colorBlendEnd   = mix(0.9, 0.6, rainStrength);
            col = mix(col, horizonCol, smoothstep(layerRenderDist * colorBlendStart, layerRenderDist * colorBlendEnd, hitDist));

            float topEdge = smoothUp * 2.0;
            float topAlpha = 1.0 - smoothstep(topEdge - 1.0, topEdge, localY);
            float bottomEdge = (1.0 - smoothDown) * 2.0 - 1.0;
            float bottomAlpha = smoothstep(bottomEdge, bottomEdge + 1.0, localY);
            float baseAlpha = topAlpha * bottomAlpha;
            float stepAlpha = baseAlpha * distFade * blockDensityCap;
            accColor.rgb += col * stepAlpha * (1.0 - accColor.a);
            accColor.a += stepAlpha * (1.0 - accColor.a);
        }
        prevDensity = blockDensityCap;
    } else if (cameraInside) {
        prevDensity = blockDensityCap;
    }
    
    if (length(dirXZ) < 0.0005) return accColor;
    
    vec2 cellIdx = floor(startXZ / cellSize);
    vec2 stepDir = vec2(dirXZ.x > 0.0 ? 1.0 : (dirXZ.x < 0.0 ? -1.0 : 0.0), dirXZ.y > 0.0 ? 1.0 : (dirXZ.y < 0.0 ? -1.0 : 0.0));
    vec2 invDir = vec2(1e20); vec2 tMaxXZ = vec2(1e20); vec2 tDelta = vec2(1e20);
    if (abs(dirXZ.x) > 0.0001) { invDir.x = 1.0 / dirXZ.x; tDelta.x = abs(cellSize * invDir.x);
        tMaxXZ.x = ((cellIdx.x + (stepDir.x > 0.0 ? 1.0 : 0.0)) * cellSize - startXZ.x) * invDir.x;
    }
    if (abs(dirXZ.y) > 0.0001) { invDir.y = 1.0 / dirXZ.y; tDelta.y = abs(cellSize * invDir.y);
        tMaxXZ.y = ((cellIdx.y + (stepDir.y > 0.0 ? 1.0 : 0.0)) * cellSize - startXZ.y) * invDir.y;
    }
    
    float tLimit = min(tFar, min(cloudRenderDist, maxDist));

    // Main DDA raymarching loop.
    for (int i = 0; i < maxSteps; i++) {
        if (accColor.a >= 0.99) {
            accColor.a = 1.0;
            break;
        }

        vec2 worldCell = (cellIdx + 0.5) * cellSize - windOff;
        float blockDensity = cloudMap(worldCell, time, coverage, narrowness, layerIdx);
        
        // Only accumulate positive density differences.
        float deltaDensity = max(0.0, blockDensity - prevDensity);
        if (deltaDensity > 0.001) {
            vec2 cellMin = cellIdx * cellSize;
            vec2 cellMax = cellMin + cellSize;
            float tEntryX = ((dirXZ.x > 0.0 ? cellMin.x : cellMax.x) - startXZ.x) * invDir.x;
            float tEntryZ = ((dirXZ.y > 0.0 ? cellMin.y : cellMax.y) - startXZ.y) * invDir.y;
            float curHitT = tNear + max(max(tEntryX, tEntryZ), 0.0);
            
            if (curHitT > tLimit) break;
            vec3 hitPos = camPos + dir * curHitT;
            float hitDist = length(hitPos.xz - camPos.xz);
            
            // Start fading alpha earlier during rain.
            float alphaFadeStart = mix(0.6, 0.2, rainStrength);
            float distFade = 1.0 - smoothstep(layerRenderDist * alphaFadeStart, layerRenderDist, hitDist);

            if (distFade > 0.001) {
                float localY = clamp((hitPos.y - layerBottom) / (layerTop - layerBottom), 0.0, 1.0);
                vec3 col = mix(preBottomColor, preTopColor, smoothstep(0.0, 1.0, localY));
                
                // Force clouds to become 100% horizon color BEFORE they fully disappear.
                float colorBlendStart = mix(0.4, 0.05, rainStrength);
                float colorBlendEnd   = mix(0.9, 0.6, rainStrength);
                col = mix(col, horizonCol, smoothstep(layerRenderDist * colorBlendStart, layerRenderDist * colorBlendEnd, hitDist));

                float topEdge = smoothUp * 2.0;
                float topAlpha = 1.0 - smoothstep(topEdge - 1.0, topEdge, localY);
                float bottomEdge = (1.0 - smoothDown) * 2.0 - 1.0;
                float bottomAlpha = smoothstep(bottomEdge, bottomEdge + 1.0, localY);
                float baseAlpha = topAlpha * bottomAlpha;
                
                float stepAlpha = baseAlpha * distFade * deltaDensity;
                
                // Accumulate color avoiding false border shadows.
                accColor.rgb += col * stepAlpha * (1.0 - accColor.a);
                accColor.a += stepAlpha * (1.0 - accColor.a);
            }
        }
        
        // Update stored density for the next step.
        prevDensity = blockDensity;
        if (tMaxXZ.x < tMaxXZ.y) { cellIdx.x += stepDir.x; if (tNear + tMaxXZ.x > tLimit) break; tMaxXZ.x += tDelta.x;
        } 
        else { cellIdx.y += stepDir.y;
            if (tNear + tMaxXZ.y > tLimit) break; tMaxXZ.y += tDelta.y;
        }
    }
    
    return accColor;
}

// Multi-layer cloud compositing.
vec4 renderClouds(vec3 worldDir, vec3 worldSunDir, float time, vec3 camPos, vec3 zenithCol, vec3 horizonCol, float maxDist) {
    vec3 dir = normalize(worldDir);
    float cloudRenderDist = float(CLOUD_DISTANCE);
    
    float baseAltitude = float(CLOUD_HEIGHT);
    float thick = float(CLOUD_THICKNESS);
    float spread = float(CLOUD_LAYER_SPACING);
    float narrowness = 0.07;
    float pixelSize = float(CLOUD_PIXEL_SIZE);
    
    float sD = float(CLOUD_SMOOTH_DOWN) / 100.0;
    float sU = float(CLOUD_SMOOTH_UP) / 100.0;
    
    vec4 res1 = vec4(0.0); float t1 = 1e6;
    vec4 res2 = vec4(0.0); float t2 = 1e6;
    vec4 res3 = vec4(0.0); float t3 = 1e6;

    if (sD > 0.0 && sU > 0.0) {
        // --- LAYER 1 ---
        float tN1, tF1;
        if (raySlabIntersect(camPos, dir, baseAltitude, baseAltitude + thick, tN1, tF1)) {
            res1 = traceLayer(camPos, dir, tN1, tF1, baseAltitude, baseAltitude + thick, float(CLOUD_L1_COVERAGE), pixelSize, narrowness, time, cloudRenderDist, sD, sU, 1, zenithCol, horizonCol, maxDist);
            t1 = tN1;
        }
        
        // --- LAYER 2 ---
        float tN2, tF2;
        float l2Bot = baseAltitude + thick + spread;
        if (raySlabIntersect(camPos, dir, l2Bot, l2Bot + thick, tN2, tF2)) {
            res2 = traceLayer(camPos, dir, tN2, tF2, l2Bot, l2Bot + thick, float(CLOUD_L2_COVERAGE), pixelSize, narrowness, time, cloudRenderDist, sD, sU, 2, zenithCol, horizonCol, maxDist);
            t2 = tN2;
        }

        // --- LAYER 3 ---
        float tN3, tF3;
        float l3Bot = baseAltitude + (thick + spread) * 2.0;
        if (raySlabIntersect(camPos, dir, l3Bot, l3Bot + thick, tN3, tF3)) {
            res3 = traceLayer(camPos, dir, tN3, tF3, l3Bot, l3Bot + thick, float(CLOUD_L3_COVERAGE), pixelSize, narrowness, time, cloudRenderDist, sD, sU, 3, zenithCol, horizonCol, maxDist);
            t3 = tN3;
        }
    }

    #if CLOUD_SUN_GLOW == 1
        float sunHeight = worldSunDir.y;
        vec3 lightDir = sunHeight > 0.0 ? worldSunDir : -worldSunDir;
        
        float nightFactor = smoothstep(0.05, -0.05, sunHeight);
        vec3 glowColor = mix(vec3(SUN_R, SUN_G, SUN_B), vec3(MOON_R, MOON_G, MOON_B), nightFactor);
        float currentPower = mix(float(CLOUD_SUN_GLOW_POWER), float(CLOUD_SUN_GLOW_POWER) * 0.4, nightFactor);

        float lightDot = max(dot(dir, lightDir), 0.0);
        
        // Base scatter calculation.
        float baseScatter = pow(lightDot, float(CLOUD_SUN_GLOW_SPREAD)) * currentPower;
        
        float skyDim = mix(1.0, 1.0 - float(SKY_RAIN_COVERAGE), rainStrength);
        baseScatter *= skyDim;

        // Interpolate layer brightness based on smoothness.
        float smoothFactor = float(CLOUD_SUN_GLOW_SMOOTHNESS);
        float multL3 = 1.0;
        float multL2 = mix(0.5, 0.85, smoothFactor);
        float multL1 = mix(0.0, 0.70, smoothFactor);
        
        // Apply light with self-shadowing.
        if (res3.a > 0.0) {
            float transmissionL3 = 1.0 - (res3.a * 0.5); 
            res3.rgb += glowColor * baseScatter * transmissionL3 * res3.a * multL3;
        }
        
        // Apply light with self-shadowing.
        if (res2.a > 0.0) {
            float transmissionL2 = 1.0 - (res2.a * 0.5); 
            res2.rgb += glowColor * baseScatter * transmissionL2 * res2.a * multL2;
        }
        
        // Apply light with self-shadowing.
        if (res1.a > 0.0) {
            float transmissionL1 = 1.0 - (res1.a * 0.5);
            res1.rgb += glowColor * baseScatter * transmissionL1 * res1.a * multL1;
        }
    #endif

    vec4 layers[3] = vec4[3](vec4(0), vec4(0), vec4(0));
    float dists[3] = float[3](1e6, 1e6, 1e6);
    int count = 0;

    if (res1.a > 0.0) { layers[count] = res1; dists[count] = t1; count++; }
    if (res2.a > 0.0) { layers[count] = res2; dists[count] = t2; count++; }
    if (res3.a > 0.0) { layers[count] = res3; dists[count] = t3; count++; }

    // Sort back-to-front.
    for (int i = 0; i < count - 1; i++) {
        for (int j = 0; j < count - i - 1; j++) {
            if (dists[j] < dists[j + 1]) {
                float tempD = dists[j];
                dists[j] = dists[j + 1]; dists[j + 1] = tempD;
                vec4 tempL = layers[j]; layers[j] = layers[j + 1];
                layers[j + 1] = tempL;
            }
        }
    }
    
    vec4 finalColor = vec4(0.0);
    
    // Composite all layers.
    for (int i = 0; i < count; i++) {
        finalColor.rgb = finalColor.rgb * (1.0 - layers[i].a) + layers[i].rgb;
        finalColor.a   = finalColor.a   * (1.0 - layers[i].a) + layers[i].a;
    }

    return finalColor;
}
#endif