#version 330

in vec2 fragTexCoord;
in vec4 fragColor;

uniform vec2  iResolution;
uniform float iTime;
uniform float iAlpha;
uniform sampler2D texture0;

out vec4 finalColor;

#define PI     3.14159265359
#define TWO_PI 6.28318530718

vec3 makeRay(float fovDeg) {
    vec2 xy = gl_FragCoord.xy - iResolution * 0.5;
    float z = (iResolution.y * 0.5) / tan(radians(fovDeg) * 0.5);
    return normalize(vec3(xy, -z));
}

mat3 lookAt(vec3 target, vec3 worldUp) {
    vec3 fwd   = normalize(target);
    vec3 right = normalize(cross(fwd, worldUp));
    vec3 up    = cross(right, fwd);
    return mat3(right, up, -fwd);
}

float hash(float p) {
    p = fract(p * 0.1031);
    p *= p + 33.33;
    return fract(p * (p + p));
}
float hash2(vec2 p) {
    return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
}

float glow(float dist, float radius, float sharpness) {
    return pow(radius / max(dist, 1e-6), sharpness);
}

float starField(vec3 dir) {
    dir = dir.xzy;  // Y-up to Z-up
    float theta = acos(clamp(dir.z, -1.0, 1.0));
    float bandWidth = PI / 20000.0;
    float band      = floor(theta / PI * 20000.0);
    float brightness = 0.0;
    for (float offset = -10.0; offset <= 10.0; offset++) {
        float b  = clamp(band + offset, 0.0, 19999.0);
        float th = (b + 0.5) * bandWidth;
        if (sin(th) <= hash2(vec2(th, 0.0))) continue;
        float size = hash(PI + th);
        float phi  = TWO_PI * hash(b);
        vec3 starDir = normalize(vec3(sin(th)*cos(phi), sin(th)*sin(phi), cos(th)));
        float dist    = 0.5 + 0.5 * dot(starDir, dir);
        float twinkle = 0.55 + 0.45 * sin(iTime * (1.5 + hash(b) * 4.5) + hash(size) * TWO_PI);
        brightness += glow(1.0 - dist, size * 8e-7, 4.0) * twinkle;
    }
    return brightness * 0.22;
}

// Sphere intersection — avoids the ellipse deformation of a flat disk at oblique angles.
vec3 moonColour(vec3 rayDir, vec3 moonDir, float moonDist, float moonRadius, out bool hit) {
    hit = false;
    vec3 centre   = moonDir * moonDist;
    float tClosest = dot(centre, rayDir);
    if (tClosest <= 0.0) return vec3(0.0);
    vec3 closest  = rayDir * tClosest;
    float dist2   = dot(closest - centre, closest - centre);
    float r2      = moonRadius * moonRadius;
    if (dist2 > r2) return vec3(0.0);
    hit = true;
    vec3 hitPoint = rayDir * (tClosest - sqrt(r2 - dist2));
    vec3 normal   = normalize(hitPoint - centre);

    vec3 moonRight = normalize(cross(moonDir, vec3(0.0, 1.0, 0.0)));
    vec3 moonUp    = normalize(cross(moonRight, moonDir));
    vec2 uv        = vec2(dot(normal, moonRight), dot(normal, moonUp)) * 0.5 + 0.5;

    vec3 texColor    = texture(texture0, uv).rgb;
    float brightness = abs(dot(normal, moonDir));
    return texColor * (0.4 + 0.6 * brightness);
}

vec3 tonemap(vec3 x) {
    return clamp((x * (2.51*x + 0.03)) / (x * (2.43*x + 0.59) + 0.14), 0.0, 1.0);
}

// Moon position: azimuth (radians around horizon), elevation (0=horizon)
const float MOON_AZIMUTH   = 1.0;
const float MOON_ELEVATION = 0.35;

void main() {
    vec3 rayDir = lookAt(normalize(vec3(0.0, 0.1, 1.0)), vec3(0.0, 1.0, 0.0))
                  * makeRay(55.0);

    vec3 moonDir    = normalize(vec3(cos(MOON_AZIMUTH), MOON_ELEVATION, sin(MOON_AZIMUTH)));
    float alignment = 0.5 + 0.5 * dot(rayDir, moonDir);

    const vec3 skyBase = 0.06 * vec3(0.6, 0.6, 0.65);
    vec3 colour = mix(0.2 * skyBase, 0.5 * skyBase, pow(alignment, 8.0));

    colour += starField(rayDir);

    const float MOON_DIST   = 100.0;
    const float MOON_RADIUS = 8.0;

    bool moonHit = false;
    if (alignment > 0.80) {
        vec3 mc = moonColour(rayDir, moonDir, MOON_DIST, MOON_RADIUS, moonHit);
        if (moonHit) colour = mc;
    }

    float moonAngularRadius = atan(MOON_RADIUS / MOON_DIST);
    colour += vec3(0.75, 0.78, 0.82) * 0.5
              * clamp(glow(1.0 - alignment, 0.5 * (1.0 - cos(moonAngularRadius)), 2.0), 0.0, 1.0);

    colour = tonemap(colour);
    colour = pow(colour, vec3(0.4545));

    finalColor = vec4(colour, iAlpha);
}
