/* Refrax — lib/atmosphere.glsl */
#ifndef REFRAX_ATMOSPHERE
#define REFRAX_ATMOSPHERE

#include "/lib/settings.glsl"
#include "/lib/common.glsl"
#include "/lib/noise.glsl"
#include "/lib/biome.glsl"

#ifdef PHYSICAL_SKY
  #if !defined WORLD_NETHER && !defined WORLD_END && !defined REFRAX_NO_ATMOS_LUT
    #define ATMOS_LUT_ACTIVE
    #define ATMOS_LUT_READ
  #endif
#endif
#include "/lib/atmosphere_lut.glsl"

#ifdef AERIAL_PERSPECTIVE
  #ifdef ATMOS_LUT_ACTIVE
    #define ATMOS_AERIAL_ACTIVE
  #endif
#endif


float dayFactor(float selev) { return smoothstep(-0.05, 0.10, selev); }

float duskFactor(float selev) { return exp(-selev * selev * 180.0); }


vec3 sunColor(float selev) {
#ifdef ATMOS_LUT_ACTIVE
    return atmosLightTransmittance(selev) * (SUN_BRIGHTNESS * ATMOS_SUN_GAIN)
         * smoothstep(-0.012, 0.012, selev);
#else
    float t = saturate(selev * 5.0);
    vec3 ember = vec3(1.00, 0.22, 0.04);
    vec3 amber = vec3(1.00, 0.55, 0.22);
    vec3 day = vec3(1.00, 0.97, 0.92);
    vec3 c = mix(mix(ember, amber, saturate(t * 2.2)), day, t * t);
    float up = smoothstep(-0.03, 0.02, selev);
    return c * SUN_BRIGHTNESS * up * mix(0.18, 1.0, t);
#endif
}

vec3 moonColor(float melev) {
#ifdef ATMOS_LUT_ACTIVE
    return atmosLightTransmittance(melev) * ATMOS_MOON_TINT
         * (MOON_BRIGHTNESS * ATMOS_MOON_GAIN) * smoothstep(-0.012, 0.012, melev);
#else
    return vec3(0.28, 0.40, 0.68) * MOON_BRIGHTNESS * smoothstep(-0.03, 0.06, melev);
#endif
}

vec3 overcastGrey(vec3 c, float level) {
    return luminance(c) * level * vec3(1.05, 1.00, 0.90);
}

vec3 zenithColor(float selev, float rain) {
    vec3 day = vec3(0.13, 0.32, 0.80);
    vec3 dusk = vec3(0.09, 0.12, 0.30);
    vec3 night = vec3(0.0028, 0.0044, 0.011);
    vec3 c = mix(night, day, dayFactor(selev));
    c = mix(c, dusk, duskFactor(selev) * 0.80);
    c = mix(c, overcastGrey(c, 0.62), rain * 0.92);
    return c * SKY_SATURATION;
}
vec3 horizonColor(float selev, float rain) {
    vec3 day = vec3(0.58, 0.75, 0.93);
    vec3 dusk = vec3(0.48, 0.26, 0.22);
    vec3 night = vec3(0.010, 0.014, 0.030);
    vec3 c = mix(night, day, dayFactor(selev));
    c = mix(c, dusk, duskFactor(selev) * 0.85);
    c = mix(c, overcastGrey(c, 0.50), rain * 0.90);
    return c;
}

vec3 skyGradient(vec3 dir, vec3 sunDir, float rain) {
#ifdef ATMOS_LUT_ACTIVE
    vec3 lookDir = vec3(dir.x, max(dir.y, 0.0), dir.z);
    float ll = length(lookDir);
    lookDir = ll > 1e-4 ? lookDir / ll : vec3(0.0, 1.0, 0.0);

    vec3 sky = atmosSkyRadiance(lookDir, sunDir) * mix(1.0, 0.50, saturate(-dir.y * 4.0));

    sky += ATMOS_NIGHT_FLOOR * (1.0 - dayFactor(sunDir.y));
    sky = mix(sky, overcastGrey(sky, 0.62), rain * 0.92);
    return sky * SKY_SATURATION;
#else
    float selev = sunDir.y;
    vec3 zen = zenithColor(selev, rain);
    vec3 hor = horizonColor(selev, rain);
    float h = pow(1.0 - saturate(dir.y), 3.0);
    vec3 sky = mix(zen, hor, h);
    float clearW = 1.0 - rain * 0.85;



    float dusk = duskFactor(selev);
    if (dusk > 0.001) {
        vec2 dirH = dir.xz / max(length(dir.xz), 1e-4);
        vec2 sunH = sunDir.xz / max(length(sunDir.xz), 1e-4);
        float az = dot(dirH, sunH) * 0.5 + 0.5;
        float az2 = az * az;
        vec3 glowCol = mix(vec3(0.30, 0.16, 0.34), vec3(1.05, 0.36, 0.08), az2);
        float band = pow(1.0 - saturate(dir.y), 2.5);
        sky += glowCol * (dusk * band * (0.25 + 1.05 * az2 * az) * clearW);
    }


    float sd = saturate(dot(dir, sunDir));
    sky += (pow(sd, 9.0) * 0.5 + pow(sd, 120.0)) * sunColor(selev) * 0.16 * (0.4 + h) * clearW;
    float md = saturate(dot(dir, -sunDir));
    sky += pow(md, 20.0) * moonColor(-selev) * 0.6 * clearW;

    if (dir.y < 0.0) sky = mix(sky, hor * 0.5, saturate(-dir.y * 4.0));
    return sky;
#endif
}


#if defined ATMOS_LUT_ACTIVE && !defined REFRAX_SKY_CACHE_GEN
#define SKY_AMBIENT_CACHED
uniform sampler2D refraxSkyCacheTex;
#endif

vec3 skyAmbient(vec3 sunDir, float rain) {
#ifdef SKY_AMBIENT_CACHED
    return texelFetch(refraxSkyCacheTex, ivec2(0), 0).rgb;
#elif defined ATMOS_LUT_ACTIVE
    const float RING_Y = 0.574;
    const float RING_H = 0.819;
    vec3 up = vec3(0.0, 1.0, 0.0);
    vec3 s = vec3(sunDir.x, 0.0, sunDir.z);
    float sl = length(s);
    s = sl > 1e-4 ? s / sl : vec3(1.0, 0.0, 0.0);
    vec3 t = cross(up, s);
    vec3 ringUp = vec3(0.0, RING_Y, 0.0);

    vec3 amb = atmosSkyRadiance(up, sunDir) * 0.50
             + atmosSkyRadiance(ringUp + s * RING_H, sunDir) * 0.175
             + atmosSkyRadiance(ringUp - s * RING_H, sunDir) * 0.175
             + atmosSkyRadiance(ringUp + t * RING_H, sunDir) * 0.075
             + atmosSkyRadiance(ringUp - t * RING_H, sunDir) * 0.075;

    amb = mix(amb, overcastGrey(amb, 0.62), rain * 0.90);
    return max(amb * SKY_SATURATION, vec3(0.0030, 0.0042, 0.0080));
#else
    float selev = sunDir.y;
    vec3 amb = zenithColor(selev, rain) * 0.60 + horizonColor(selev, rain) * 0.40;

    amb += vec3(0.45, 0.18, 0.05) * duskFactor(selev) * 0.18 * (1.0 - rain * 0.85);

    return max(amb * 0.85, vec3(0.0030, 0.0042, 0.0080));
#endif
}




vec3 skyAmbientDirectional(vec3 N, vec3 sunDir, float rain) {
    float up = saturate(N.y * 0.5 + 0.5);
    vec3 tint = mix(vec3(1.06, 0.97, 0.90), vec3(0.90, 0.97, 1.14), up);
    return skyAmbient(sunDir, rain) * tint * (0.75 + 0.30 * up);
}

const float STAR_SCALE = 60.0;
const float STAR_CUTOFF = 0.914;

vec3 starColor(float t) {
    vec3 c = mix(vec3(0.52, 0.66, 1.00), vec3(0.95, 0.96, 1.00), smoothstep(0.0, 0.55, t));
    return mix(c, vec3(1.00, 0.70, 0.38), smoothstep(0.70, 1.00, t));
}

vec3 starField(vec3 dir, float time, float airMass) {
    vec3 a = abs(dir);
    float m = max(a.x, max(a.y, a.z));
    vec3 nd = dir / max(m, 1e-6);

    vec2 uv;
    float face;
    if (a.x == m) { uv = nd.yz; face = nd.x < 0.0 ? 1.0 : 0.0; }
    else if (a.y == m) { uv = nd.zx; face = nd.y < 0.0 ? 3.0 : 2.0; }
    else { uv = nd.xy; face = nd.z < 0.0 ? 5.0 : 4.0; }

    vec2 p = uv * (1.2408 - 0.2408 * uv * uv) * STAR_SCALE;
    vec2 cell = floor(p);
    vec3 id = vec3(cell, face);

    float h = hash13(id);
    if (h < STAR_CUTOFF) return vec3(0.0);

    float mag = (h - STAR_CUTOFF) * (1.0 / (1.0 - STAR_CUTOFF));
    float t = hash13(id + vec3(113.0, 57.0, 19.0));
    float bright = 0.22 + 3.30 * mag * mag * mag;

    vec2 jit = vec2(hash13(id + 7.0), hash13(id + 23.0)) - 0.5;
    vec2 off = p - cell - 0.5 - jit * 0.20;
    float radius = 0.092 + 0.112 * mag;
    float e = saturate(1.0 - dot(off, off) / (radius * radius));
    float disc = e * e * (e * e + 0.30) * 0.77;

    float air = max(airMass - 1.0, 0.0);
    float depth = hash13(id + 41.0);
    float ph = hash13(id + 73.0) * 60.0;
    float tw = 1.0 + depth * (0.78 * sin(time * 1.90 + ph) + 0.22 * sin(time * 1.17 + ph * 1.7));

    vec3 ext = exp(-vec3(0.10, 0.17, 0.28) * air);
    return starColor(t) * (bright * disc * tw) * ext;
}

#ifdef MILKY_WAY_ACTIVE
const vec3 GALACTIC_AXIS = normalize(vec3(0.36, 0.58, -0.73));

vec3 milkyWay(vec3 dir, float airMass) {
    float axial = dot(dir, GALACTIC_AXIS);
    float band = exp(-axial * axial * MILKY_WAY_WIDTH);
    if (band < 0.003) return vec3(0.0);

    float cloud = fbm3(dir * 4.3, 4);
    float fine = fbm3(dir * 11.7 + 27.3, 3);
    float lane = fbm3(dir * 6.1 - 51.7, 3);

    float dust = smoothstep(0.36, 0.78, cloud) * (0.55 + 0.45 * fine);
    dust *= 1.0 - smoothstep(0.44, 0.74, lane) * 0.78;

    vec3 core = mix(vec3(0.66, 0.71, 0.88), vec3(0.94, 0.87, 0.74), smoothstep(0.28, 0.82, fine));
    float ext = exp(-0.46 * max(airMass - 1.0, 0.0));
    return core * ((0.055 + 1.30 * dust) * band * ext * MILKY_WAY_STRENGTH);
}
#endif

#ifdef AURORA_ACTIVE
vec3 auroraCurtain(vec3 dir, vec3 camPos, float time) {
    float up = dir.y;
    if (up < 0.045) return vec3(0.0);

    int layers = PERF_SCALED_COUNT(AURORA_LAYERS, 3);
    float span = 1.0 / float(max(layers - 1, 1));
    float t = time * AURORA_SPEED;
    vec3 acc = vec3(0.0);

    for (int i = 0; i < layers; i++) {
        float fi = float(i) * span;
        float h = mix(115.0, 260.0, fi);
        vec2 p = (camPos.xz + dir.xz * (h / up)) * 0.0017;
        vec2 w = vec2(fbm3(vec3(p * 1.7, t * 0.031), 3),
                      fbm3(vec3(p * 1.7 + 31.4, t * 0.027), 3)) - 0.5;
        p += w * 0.62;
        float sheet = smoothstep(0.50, 0.88, fbm3(vec3(p * 3.1, t * 0.017), 4));
        sheet *= 1.0 - fi * 0.58;
        acc += mix(vec3(0.16, 0.94, 0.42), vec3(0.66, 0.24, 0.90), smoothstep(0.12, 0.95, fi)) * sheet;
    }
    return acc * (smoothstep(0.045, 0.30, up) * AURORA_STRENGTH / float(layers));
}

vec3 auroraAmbientWash(float weight, vec3 albedo) {
    vec3 wash = vec3(0.0025, 0.0092, 0.0052) * (weight * AURORA_STRENGTH * AURORA_AMBIENT);
    float lum = luminance(albedo);
    float chroma = max(albedo.r, max(albedo.g, albedo.b)) - min(albedo.r, min(albedo.g, albedo.b));
    float pale = smoothstep(0.42, 0.78, lum) * (1.0 - smoothstep(0.10, 0.30, chroma));
    return mix(wash, vec3(luminance(wash)), pale * AURORA_SNOW_SUPPRESS);
}
#endif

#ifdef RAINBOW_ACTIVE
vec3 rainbowSpectrum(float t) {
    return vec3(smoothstep(0.46, 0.88, t) + 0.38 * (1.0 - smoothstep(0.00, 0.18, t)),
                smoothstep(0.14, 0.50, t) * (1.0 - smoothstep(0.64, 1.00, t)),
                1.0 - smoothstep(0.10, 0.46, t));
}

float rainbowBandMask(float p) {
    return smoothstep(0.00, 0.16, p) * smoothstep(1.00, 0.86, p);
}

vec3 rainbowArc(vec3 dir, vec3 sunDir, float wet) {
    float amt = wet * smoothstep(0.010, 0.170, sunDir.y) * smoothstep(-0.08, 0.10, dir.y);
    if (amt < 0.002) return vec3(0.0);

    float ang = degrees(acos(clamp(dot(dir, -sunDir), -1.0, 1.0)));

    float p = (ang - 40.1) * (1.0 / 2.3);
    vec3 bow = rainbowSpectrum(saturate(p)) * rainbowBandMask(saturate(p));

    float s = saturate((ang - 50.4) * (1.0 / 3.5));
    bow += rainbowSpectrum(1.0 - s) * (rainbowBandMask(s) * 0.30);

    return bow * (amt * RAINBOW_STRENGTH);
}
#endif

vec3 celestial(vec3 dir, vec3 sunDir, vec3 camPos, float time, float rain, float wet, float auroraW) {
    float selev = sunDir.y;
    vec3 c = vec3(0.0);

    float sd = dot(dir, sunDir);
    c += smoothstep(0.99955, 0.99985, sd) * sunColor(selev) * 24.0;
#ifdef ATMOS_LUT_ACTIVE
    c += pow(saturate(sd), 90.0) * sunColor(selev) * 0.55 * (1.0 - rain * 0.90);
#endif

    float md = dot(dir, -sunDir);
#ifdef ATMOS_LUT_ACTIVE
    c += smoothstep(0.99965, 0.99990, md) * vec3(0.75, 0.80, 0.95) * 1.6
       * smoothstep(-0.06, 0.06, -selev) * atmosLightTransmittance(-selev);
#else
    c += smoothstep(0.99965, 0.99990, md) * vec3(0.75, 0.80, 0.95) * 1.6 * smoothstep(-0.06, 0.06, -selev);
#endif

    float night = smoothstep(0.02, -0.08, selev);
    float airMass = 1.0 / max(dir.y, 0.030);
    float above = smoothstep(-0.005, 0.020, dir.y);
    c += starField(dir, time, airMass) * (night * above);
#ifdef MILKY_WAY_ACTIVE
    c += milkyWay(dir, airMass) * (night * above);
#endif
#ifdef AURORA_ACTIVE
    if (auroraW > 0.002) c += auroraCurtain(dir, camPos, time) * auroraW;
#endif
    c *= 1.0 - rain * 0.95;
#ifdef RAINBOW_ACTIVE
    c += rainbowArc(dir, sunDir, wet);
#endif
    return c;
}


#ifdef ATMOS_AERIAL_ACTIVE
struct AerialResult {
    vec3 transmittance;
    vec3 inscatter;
};

AerialResult aerialPerspective(vec3 dirW, float dist, float midY, vec3 sunDir, vec3 lightDir, vec3 lightCol, float rain, float snow, float lightVis, float maxOpacity, BiomeAtmos biome) {
    float hFall = exp(-max(midY - 64.0, 0.0) * FOG_HEIGHT_FALLOFF);
    float wetFog = rain * FOG_RAIN_DENSITY + snow * SNOW_FOG * 0.90;
    float density = FOG_BASE * FOG_DENSITY * (mix(biome.density, 1.0, saturate(rain + snow)) + wetFog) * hFall;
    float k = density / ATMOS_SIGMA_E_REF;
    float haze = ATMOS_HAZE_E * mix(biome.haze, 1.0, saturate(rain + snow));

    vec3 sigmaR = ATMOS_RAYLEIGH_AERIAL * k;
    float sigmaM = ATMOS_MIE_S * haze * k;
    vec3 sigmaE = max((ATMOS_RAYLEIGH_AERIAL + vec3((ATMOS_MIE_S + ATMOS_MIE_A) * haze)) * k, vec3(1e-9));

    float cosT = dot(dirW, lightDir);
    vec3 direct = (sigmaR * atmosRayleighPhase(cosT) + vec3(sigmaM * atmosMiePhase(cosT))) * lightCol * lightVis * AERIAL_STRENGTH;

    vec3 ambDir = vec3(dirW.x, max(dirW.y, 0.02), dirW.z);

    vec3 ambient = (sigmaR + vec3(sigmaM)) * atmosSkyRadiance(ambDir, sunDir) * (ATMOS_AERIAL_AMBIENT / HORIZON_BRIGHTNESS_E);
    if (rain > 0.001) {
        vec3 grey = overcastGrey(ambient, 0.62);
        ambient = mix(ambient, grey, rain * 0.90);
        direct *= 1.0 - rain * 0.85;
    }
    if (snow > 0.001) {
        float s = saturate(snow * SNOW_FOG);
        ambient = mix(ambient, luminance(ambient) * vec3(0.92, 0.99, 1.14) * 1.22, s * 0.80);
        direct *= 1.0 - s * 0.55;
    }
    ambient *= biome.tint;
    direct *= mix(vec3(1.0), biome.tint, 0.5);

    AerialResult r;
    r.transmittance = max(exp(-sigmaE * dist), vec3(1.0 - maxOpacity));
    r.inscatter = (direct + ambient) * (1.0 - r.transmittance) / sigmaE;
    return r;
}
#endif


vec3 netherFogColor(vec3 dir, vec3 fogCol) {
    vec3 f = srgbToLinear(fogCol) * 1.2;
    float h = saturate(dir.y * 0.5 + 0.5);
    vec3 fog = mix(f * 1.15, f * 0.25, h);
    fog += vec3(0.30, 0.06, 0.008) * pow(1.0 - h, 3.0) * 0.75;
    return fog;
}

vec3 netherSky(vec3 dir, vec3 fogCol, float time) {
    float h = saturate(dir.y * 0.5 + 0.5);
    float smoke = fbm3(vec3(dir.xz / max(abs(dir.y), 0.45) * 1.6, time * 0.02), 3);
    vec3 sky = netherFogColor(dir, fogCol) * (0.8 + 0.4 * smoke);
    sky += vec3(0.30, 0.06, 0.008) * pow(1.0 - h, 3.0) * (0.5 * smoke - 0.375);
    return max(sky, vec3(0.0));
}



vec3 endFogColor() { return vec3(0.034, 0.014, 0.058) * END_AMBIENT; }


vec3 endSky(vec3 dir, float time) {
    vec3 sky = vec3(0.012, 0.007, 0.022);
    vec3 axis = normalize(vec3(0.42, 1.0, 0.18));
    float band = exp(-pow(dot(dir, axis), 2.0) * 4.5);
    float neb = fbm3(dir * 3.1 + vec3(0.0, time * 0.004, 0.0), 4);
    float hue = fbm3(dir * 7.7 + 13.1, 3);
    vec3 nebCol = mix(vec3(0.14, 0.04, 0.26), vec3(0.04, 0.17, 0.21), hue);
    sky += band * (0.10 + 1.8 * smoothstep(0.45, 0.80, neb)) * nebCol;
    sky += band * vec3(0.08, 0.05, 0.13);
    vec3 stars = starField(dir, time, 1.0);
    sky += stars * mix(vec3(0.58, 0.48, 0.80), vec3(0.95, 0.72, 1.30), band);
    sky *= mix(1.0, 0.35, smoothstep(-0.10, -0.60, dir.y));

    float haze = pow(1.0 - abs(dir.y), 3.0);
    return mix(sky, endFogColor() * 1.6, haze * 0.75);
}


vec3 dimensionSky(vec3 dir, vec3 sunDir, vec3 fogCol, float time, float rain, vec3 camPos, float wet, float auroraW) {
#if defined WORLD_NETHER
    return netherSky(dir, fogCol, time);
#elif defined WORLD_END
    return endSky(dir, time);
#else
    return skyGradient(dir, sunDir, rain) + celestial(dir, sunDir, camPos, time, rain, wet, auroraW);
#endif
}

vec3 skyReflection(vec3 dir, vec3 sunDir, float rain) {
    return skyGradient(dir, sunDir, rain) * (1.0 - rain * REFLECTION_RAIN_DIM);
}

vec3 dimensionSkyReflection(vec3 dir, vec3 sunDir, vec3 fogCol, float time, float rain) {
#if defined WORLD_NETHER
    return netherSky(dir, fogCol, time);
#elif defined WORLD_END
    return endSky(dir, time);
#else
    return skyReflection(dir, sunDir, rain);
#endif
}

float netherFacing(vec3 N) {
    return (0.85 + 0.15 * N.x) * (0.72 + 0.28 * abs(N.y));
}

vec3 netherAmbient(vec3 N, vec3 fogCol) {
    vec3 f = srgbToLinear(fogCol);
    vec3 hue = f / max(luminance(f), 1e-4);
    hue = mix(vec3(1.0), hue, 0.60);
    float up = saturate(N.y * 0.5 + 0.5);
    vec3 amb = hue * mix(0.115, 0.065, up);
    amb += vec3(0.055, 0.016, 0.004) * (1.0 - up);
    return amb * NETHER_AMBIENT;
}


vec3 endAmbient(vec3 N) {
    vec3 above = vec3(0.034, 0.014, 0.058);
    vec3 below = vec3(0.012, 0.006, 0.022);
    return mix(below, above, saturate(N.y * 0.5 + 0.5)) * END_AMBIENT;
}

vec3 endLightColor() { return vec3(1.00, 0.55, 0.30) * 0.55 * END_LIGHT; }

#endif
