// ============================================================
//  CinematicVanilla V4 - skyRender.glsl
//  Cinematic sky: Rayleigh+Mie atmosphere, improved clouds,
//  twinkling/layered stars + Milky Way + shooting stars,
//  enhanced sunset/dawn palette.
//  V4: getSkyBasic/getFullSkyRender/getSkyFogRender were called
//  by the pipeline but never defined in V3 — added below. Stars
//  were also dead code (function existed, never called) — fixed.
// ============================================================

// ─── Assembled Sky Passes (V4) ──────────────────────────────────────────────
// V3 note: deferred1.glsl and composite.glsl both called getSkyBasic() /
// getFullSkyRender() / getSkyFogRender(), but none of the three were ever
// defined anywhere in the project — the shader could not compile as shipped.
// These assemble the pieces already present below (Rayleigh sky, horizon
// glow, mie scatter, stars, clouds) into what the rest of the pipeline
// actually expects.

// Cheap base sky gradient before any glow/stars/sun/clouds are added.
// `skyCol` is the vertex-stage day/night/twilight-blended color already
// supplied per-dimension via world.glsl's SKY_COLOR_DATA_BLOCK.
vec3 getSkyBasic(in float nEyePlayerPosY, in float skyPosZ){
    #if defined WORLD_LIGHT && !defined FORCE_DISABLE_DAY_CYCLE
        return getRayleighSky(nEyePlayerPosY, skyCol, skyCol * vec3(0.22, 0.26, 0.42));
    #else
        return skyCol;
    #endif
}

// Fog-facing sky color: horizon glow only, no stars/sun disc/clouds, since
// this feeds the distance-fog blend rather than the skybox itself.
vec3 getSkyFogRender(in vec3 nEyePlayerPos, in vec3 skyPos, in vec3 currSkyCol){
    vec3 col = currSkyCol;
    #if defined WORLD_LIGHT && !defined FORCE_DISABLE_DAY_CYCLE
        col += getCinematicHorizonGlow(nEyePlayerPos.y, skyPos.z) * SKYBOX_BRIGHTNESS;
    #endif
    return col;
}

// Overload used by composite.glsl, which only has nEyePlayerPos in scope.
// Rebuilds skyPos/currSkyCol the same way deferred1.glsl does.
vec3 getSkyFogRender(in vec3 nEyePlayerPos){
    vec3 skyPos = mat3(shadowModelView) * nEyePlayerPos;
    #if defined WORLD_LIGHT && !defined FORCE_DISABLE_DAY_CYCLE
        if(dayCycle < 1) skyPos.xz = -skyPos.xz;
    #endif
    vec3 currSkyCol = getSkyBasic(nEyePlayerPos.y, skyPos.z);
    return getSkyFogRender(nEyePlayerPos, skyPos, currSkyCol);
}

// Full skybox render: horizon glow, stars/milky way/shooting stars, the
// procedural sun/moon disc + mie halo (when SUN_MOON_TYPE != 2, i.e. not
// using the vanilla texture), and clouds.
vec3 getFullSkyRender(in vec3 nEyePlayerPos, in vec3 skyPos, in vec3 currSkyCol){
    vec3 sky = currSkyCol * skyBoxIntensitySqrd;

    #if defined WORLD_LIGHT && !defined FORCE_DISABLE_DAY_CYCLE
        sky += getCinematicHorizonGlow(nEyePlayerPos.y, skyPos.z) * SKYBOX_BRIGHTNESS;
    #endif

    #ifdef WORLD_STARS
        float nightMask = 1.0;
        #if defined WORLD_LIGHT && !defined FORCE_DISABLE_DAY_CYCLE
            nightMask = saturate(1.0 - dayCycleAdjust * 2.2);
        #endif
        if(nightMask > 0.0001){
            sky += getStarField(nEyePlayerPos) * (WORLD_STARS) * STAR_BRIGHTNESS * nightMask;
            #ifdef SHOOTING_STARS
                sky += getShootingStar(nEyePlayerPos) * vec3(0.90, 0.95, 1.00) * (WORLD_STARS) * nightMask;
            #endif
        }
    #endif

    #ifdef WORLD_LIGHT
        #if SUN_MOON_TYPE != 2
            float discShape = getSunMoonShape(skyPos.z);
            #ifndef FORCE_DISABLE_DAY_CYCLE
                vec3 discCol = dayCycle > 1 ? sunCol : moonCol;
            #else
                vec3 discCol = lightCol;
            #endif
            sky += discShape * discCol * sunMoonIntensitySqrd;
            sky += getMieScatter(skyPos.z) * lightCol * sunMoonIntensitySqrd;
        #endif
    #endif

    #if CLOUD_TYPE != 0 && !defined FORCE_DISABLE_CLOUDS && defined WORLD_LIGHT
        sky = getSkyClouds(nEyePlayerPos, sky);
    #endif

    return sky;
}

// SSR fallback: when a screen-space ray misses geometry, sample the sky in
// the reflected direction instead. Used by complexShadingDeferred.glsl.
// V4 fix: called in 3 places there but never defined anywhere in V3.
vec3 getSkyReflection(in vec3 reflectViewDir){
    vec3 nEyePlayerPos = normalize(mat3(gbufferModelViewInverse) * reflectViewDir);
    vec3 skyPos = mat3(shadowModelView) * nEyePlayerPos;
    #if defined WORLD_LIGHT && !defined FORCE_DISABLE_DAY_CYCLE
        if(dayCycle < 1) skyPos.xz = -skyPos.xz;
    #endif
    vec3 currSkyCol = getSkyBasic(nEyePlayerPos.y, skyPos.z);
    return getFullSkyRender(nEyePlayerPos, skyPos, currSkyCol);
}

// ─── Sun/Moon Shape ─────────────────────────────────────────────────────────
float getSunMoonShape(in float skyPosZ){
    return min(1.0, exp2((WORLD_SUN_MOON_SIZE - sqrt(1.0 - skyPosZ * skyPosZ)) * 256.0));
}
float getSunMoonShape(in vec2 skyPos){
    return min(1.0, exp2((WORLD_SUN_MOON_SIZE
         - pow(abs(skyPos.x * skyPos.x * skyPos.x)
             + abs(skyPos.y * skyPos.y * skyPos.y), 0.33333333)) * 256.0));
}

// ─── Mie Scattering Halo ────────────────────────────────────────────────────
// Forward-scattering glow around the sun/moon (Henyey-Greenstein)
float getMieScatter(in float cosTheta){
    const float g  = 0.78, g2 = 0.78 * 0.78;
    float denom = max(0.0001, 1.0 + g2 - 2.0 * g * cosTheta);
    return (1.0 - g2) / (4.0 * 3.14159265 * pow(denom, 1.5)) * 0.004;
}

// ─── Rayleigh Sky ──────────────────────────────────────────────────────────
// Cheap Rayleigh-style atmospheric depth tinting at the horizon
vec3 getRayleighSky(in float nEyePlayerPosY, in vec3 dayCol, in vec3 nightCol){
    float h = saturate(nEyePlayerPosY);
    // Deeper blue at zenith, lighter + more grey at horizon
    vec3 zenith  = dayCol * 1.10;
    vec3 horizon = mix(dayCol * vec3(0.85, 0.90, 1.00), vec3(0.68, 0.78, 0.90), 0.35);
    vec3 sky = mix(horizon, zenith, h * h);
    #ifndef FORCE_DISABLE_DAY_CYCLE
        sky = mix(nightCol, sky, saturate(dayCycleAdjust * 3.0 - 0.5));
    #endif
    return sky;
}

// ─── Cinematic Horizon Glow ─────────────────────────────────────────────────
// Multi-band: warm orange low band + red upper band + purple anti-solar
vec3 getCinematicHorizonGlow(in float nEyePlayerPosY, in float cosA){
    #ifndef FORCE_DISABLE_DAY_CYCLE
        float tw = 1.0 - abs(dayCycleAdjust * 2.0 - 1.0);
        tw *= tw;
        float hBand    = exp2(-abs(nEyePlayerPosY) * 6.0);
        float lowBand  = exp2(-abs(nEyePlayerPosY) * 20.0);

        // Solar side: warm orange glow that bleeds up
        vec3 solar   = vec3(1.00, 0.40, 0.08) * max(0.0, cosA);
        // Anti-solar: purple/blue opposite the sun
        vec3 antisol = vec3(0.52, 0.18, 0.48) * max(0.0, -cosA);
        // Deep red strip at horizon (like a real sunset)
        vec3 redLine  = vec3(1.00, 0.18, 0.04) * lowBand;

        vec3 glow = (solar * hBand * 0.60 + antisol * hBand * 0.25 + redLine * 0.35) * tw;
        return glow;
    #else
        return vec3(0.0);
    #endif
}

// ─── Stars (V4) ─────────────────────────────────────────────────────────────
// V3 note: this function existed but was never called anywhere in the
// pipeline, so stars never actually rendered. V4 wires it into
// getFullSkyRender below, and upgrades it with:
//   - two independent hash layers (sparse bright stars + denser dim stars)
//   - per-star twinkle animation (phase offset by hash so stars don't
//     flicker in sync)
//   - subtle warm/cool color variance per star
//   - an optional faint Milky Way band across a fixed sky-dome diagonal
#ifdef WORLD_STARS
vec3 getStarField(in vec3 dir){
    vec3 d = normalize(dir);

    float h1 = fract(sin(dot(d.xz, vec2(127.1, 311.7))) * 43758.5453);
    float h2 = fract(sin(dot(d.xy, vec2(269.5, 183.3))) * 43758.5453);

    // Sparse bright stars + a denser layer of dim ones
    float bright = step(0.9975, h1);
    float dim    = step(0.9800, h2) * 0.35;

    // Twinkle: slow sinusoid, phase offset per-star so they don't flicker
    // in unison. fragmentFrameTime is already available in every program
    // this is included from.
    float twinklePhase = h1 * TAU * 8.0;
    float twinkle = 0.65 + 0.35 * sin(fragmentFrameTime * 3.0 + twinklePhase);

    float density = (bright + dim) * twinkle;
    float horizonFade = saturate(-d.y + 0.05) * 2.0;

    // Warm/cool color variance instead of flat white
    vec3 starCol = mix(vec3(0.80, 0.85, 1.00), vec3(1.00, 0.92, 0.80), h2);

    vec3 result = starCol * density * horizonFade;

    #ifdef MILKY_WAY
        // Faint band across a fixed diagonal on the sky dome
        float band = 1.0 - abs(dot(d, vec3(0.36, 0.20, 0.91)));
        float milkyWay = smoothen(saturate(band * 3.0 - 2.0)) * 0.12;
        result += vec3(0.65, 0.72, 1.00) * milkyWay * MILKY_WAY_STRENGTH * horizonFade;
    #endif

    return result;
}

// ─── Shooting Stars (V4) ────────────────────────────────────────────────────
// Cheap, fully procedural: one rare streak on a repeating cycle, no
// persistent state required. Angular distance from a moving point on the
// sky dome, so cost is a handful of trig ops per sky pixel.
#ifdef SHOOTING_STARS
float getShootingStar(in vec3 dir){
    vec3 d = normalize(dir);

    const float cycleLength = 50.0;   // seconds between attempts
    const float streakLife  = 1.2;    // seconds the streak is visible for

    float cycle = floor(fragmentFrameTime / cycleLength);
    float t     = mod(fragmentFrameTime, cycleLength);

    vec2 rnd = fract(vec2(sin(cycle * 12.9898), sin(cycle * 78.233)) * 43758.5453);

    // Only spawn on roughly 1 in 3 cycles, and only for the first
    // streakLife seconds of that cycle
    if(rnd.x > 0.33 || t > streakLife) return 0.0;

    vec3 startDir = normalize(vec3(rnd.x * 2.0 - 1.0, 0.25 + rnd.y * 0.35, rnd.y * 2.0 - 1.0));
    vec3 endDir   = normalize(startDir + vec3(0.5, -0.2, 0.3));
    vec3 headDir  = normalize(mix(startDir, endDir, saturate(t / streakLife)));

    float ang    = acos(clamp(dot(d, headDir), -1.0, 1.0));
    float streak = exp2(-ang * 400.0) * saturate(1.0 - t / streakLife);
    return streak;
}
#endif
#endif // WORLD_STARS

// ─── Cloud Rendering ────────────────────────────────────────────────────────
#if CLOUD_TYPE != 0 && !defined FORCE_DISABLE_CLOUDS && defined WORLD_LIGHT
    const uint  skyBoxCloudSteps = uint(SKYBOX_CLOUD_STEPS);
    const float cloudStepSize    = 1.0 / skyBoxCloudSteps;
    const float depthSize        = SKYBOX_CLOUD_DEPTH * cloudStepSize;

    vec2 cloudParallaxDynamic(in vec2 start, in vec2 cameraPos){
        vec2 end = start * depthSize;
        start += cameraPos * 0.0625;
        vec2 cloudData = vec2(0);
        for(uint i = 1u; i <= skyBoxCloudSteps; i++){
            vec2 cloudMap = texelFetch(colortex0, ivec2(start) & 255, 0).xy;
            if(cloudMap.x > 0.5) cloudData.x = float(i);
            if(cloudMap.y > 0.5) cloudData.y = float(i);
            start -= end;
        }
        return cloudData;
    }

    vec3 getSkyClouds(in vec3 nEyePlayerPos, in vec3 currSkyCol){
        float cloudHeightFade = nEyePlayerPos.y - 0.1;
        #ifdef FORCE_DISABLE_WEATHER
            cloudHeightFade *= 6.0;
        #else
            cloudHeightFade -= rainStrength * 0.2;
            cloudHeightFade *= 6.0 - rainStrength * 5.0;
        #endif
        if(cloudHeightFade <= 0.0) return currSkyCol;
        cloudHeightFade = min(1.0, cloudHeightFade);

        vec2 planeUv  = nEyePlayerPos.xz * (6.0 / nEyePlayerPos.y);
        vec2 planePos = vec2(cameraPosition.x + fragmentFrameTime * 0.8, cameraPosition.z);
        vec2 cloudData = cloudParallaxDynamic(planeUv, planePos);

        #ifdef DOUBLE_LAYERED_CLOUDS
            cloudData = max(cloudParallaxDynamic(planeUv * 2.0, planePos).yx * 0.25, cloudData);
        #endif

        #ifdef DYNAMIC_CLOUDS
            float fadeTime = saturate(sin(fragmentFrameTime * FADE_SPEED) * 0.8 + 0.5);
            float clouds   = mix(mix(cloudData.x, cloudData.y, fadeTime),
                                 max(cloudData.x, cloudData.y), rainStrength);
        #else
            float clouds = cloudData.x;
        #endif

        clouds = clouds / float(skyBoxCloudSteps);

        // Twilight cloud edge warming
        #ifndef FORCE_DISABLE_DAY_CYCLE
            float tw = 1.0 - abs(dayCycleAdjust * 2.0 - 1.0);
            tw = tw * tw;
            vec3 cloudEdgeTint = mix(vec3(1.0), vec3(1.20, 0.75, 0.50), tw * 0.5);
        #else
            vec3 cloudEdgeTint = vec3(1.0);
        #endif

        vec3 cloudCol = mix(currSkyCol, currSkyCol * 1.6 * cloudEdgeTint, clouds * cloudHeightFade);
        return cloudCol;
    }
#endif
