struct Uniform {
    proj: mat4x4<f32>,
    proj_inv: mat4x4<f32>,
    view: mat4x4<f32>,
    cam_pos: vec4<f32>,
    time: f32,
    _pad0: f32,
    _pad1: f32,
    _pad2: f32,
};

@group(0) @binding(0)
var<uniform> u: Uniform;

struct VertexOutput {
    @builtin(position) position: vec4<f32>,
    @location(0) world_dir: vec3<f32>,
};

@vertex
fn vs_sky(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
    let tmp1 = i32(vertex_index) / 2;
    let tmp2 = i32(vertex_index) & 1;
    let pos = vec4<f32>(
        f32(tmp1) * 4.0 - 1.0,
        f32(tmp2) * 4.0 - 1.0,
        1.0,
        1.0
    );
    let inv_model_view = transpose(mat3x3<f32>(u.view[0].xyz, u.view[1].xyz, u.view[2].xyz));
    let unprojected = u.proj_inv * pos;
    var result: VertexOutput;
    result.world_dir = inv_model_view * unprojected.xyz;
    result.position = pos;
    return result;
}

fn hash(p: vec2<f32>) -> f32 {
    var p3 = fract(vec3<f32>(p.x, p.y, p.x) * 0.1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.x + p3.y) * p3.z);
}

fn hash2(p: vec2<f32>) -> vec2<f32> {
    let k = vec2<f32>(0.3183099, 0.3678794);
    var n = p * k + k.yx;
    return fract(16.0 * k * fract(n.x * n.y * (n.x + n.y))) * 2.0 - 1.0;
}

fn hash3(p: vec2<f32>) -> vec3<f32> {
    var p3 = fract(vec3<f32>(p.x, p.y, p.x) * vec3<f32>(0.1031, 0.1030, 0.0973));
    p3 += dot(p3, p3.yxz + 33.33);
    return fract((p3.xxy + p3.yxx) * p3.zyx);
}

fn mod289_3(x: vec3<f32>) -> vec3<f32> {
    return x - floor(x * (1.0 / 289.0)) * 289.0;
}

fn mod289_4(x: vec4<f32>) -> vec4<f32> {
    return x - floor(x * (1.0 / 289.0)) * 289.0;
}

fn permute(x: vec4<f32>) -> vec4<f32> {
    return mod289_4(((x * 34.0) + 1.0) * x);
}

fn taylor_inv_sqrt(r: vec4<f32>) -> vec4<f32> {
    return 1.79284291400159 - 0.85373472095314 * r;
}

fn snoise(v: vec3<f32>) -> f32 {
    let C = vec2<f32>(1.0 / 6.0, 1.0 / 3.0);
    let D = vec4<f32>(0.0, 0.5, 1.0, 2.0);

    var i = floor(v + dot(v, vec3<f32>(C.y, C.y, C.y)));
    let x0 = v - i + dot(i, vec3<f32>(C.x, C.x, C.x));

    let g = step(x0.yzx, x0.xyz);
    let l = 1.0 - g;
    let i1 = min(g.xyz, l.zxy);
    let i2 = max(g.xyz, l.zxy);

    let x1 = x0 - i1 + C.x;
    let x2 = x0 - i2 + C.y;
    let x3 = x0 - D.yyy;

    i = mod289_3(i);
    let p = permute(permute(permute(
        i.z + vec4<f32>(0.0, i1.z, i2.z, 1.0))
        + i.y + vec4<f32>(0.0, i1.y, i2.y, 1.0))
        + i.x + vec4<f32>(0.0, i1.x, i2.x, 1.0));

    let n_ = 0.142857142857;
    let ns = n_ * D.wyz - D.xzx;

    let j = p - 49.0 * floor(p * ns.z * ns.z);

    let x_ = floor(j * ns.z);
    let y_ = floor(j - 7.0 * x_);

    let x = x_ * ns.x + ns.y;
    let y = y_ * ns.x + ns.y;
    let h = 1.0 - abs(x) - abs(y);

    let b0 = vec4<f32>(x.xy, y.xy);
    let b1 = vec4<f32>(x.zw, y.zw);

    let s0 = floor(b0) * 2.0 + 1.0;
    let s1 = floor(b1) * 2.0 + 1.0;
    let sh = -step(h, vec4<f32>(0.0, 0.0, 0.0, 0.0));

    let a0 = b0.xzyw + s0.xzyw * sh.xxyy;
    let a1 = b1.xzyw + s1.xzyw * sh.zzww;

    var p0 = vec3<f32>(a0.xy, h.x);
    var p1 = vec3<f32>(a0.zw, h.y);
    var p2 = vec3<f32>(a1.xy, h.z);
    var p3 = vec3<f32>(a1.zw, h.w);

    let norm = taylor_inv_sqrt(vec4<f32>(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));
    p0 *= norm.x;
    p1 *= norm.y;
    p2 *= norm.z;
    p3 *= norm.w;

    var m = max(0.6 - vec4<f32>(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), vec4<f32>(0.0));
    m = m * m;
    return 42.0 * dot(m * m, vec4<f32>(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
}

fn fbm(p: vec3<f32>, octaves: i32) -> f32 {
    var value = 0.0;
    var amplitude = 0.5;
    var frequency = 1.0;

    for (var index = 0; index < octaves; index++) {
        value += amplitude * (snoise(p * frequency) * 0.5 + 0.5);
        amplitude *= 0.5;
        frequency *= 2.0;
    }

    return value;
}

fn fbm_detailed(p: vec3<f32>, octaves: i32, lacunarity: f32, gain: f32) -> f32 {
    var value = 0.0;
    var amplitude = 0.5;
    var frequency = 1.0;

    for (var index = 0; index < octaves; index++) {
        value += amplitude * (snoise(p * frequency) * 0.5 + 0.5);
        amplitude *= gain;
        frequency *= lacunarity;
    }

    return value;
}

fn dir_to_uv(dir: vec3<f32>) -> vec2<f32> {
    let phi = atan2(dir.z, dir.x);
    let theta = asin(clamp(dir.y, -1.0, 1.0));
    return vec2<f32>(phi / 6.283185 + 0.5, theta / 3.141593 + 0.5);
}

fn stellar_color(temperature: f32) -> vec3<f32> {
    var color: vec3<f32>;
    if temperature < 0.15 {
        color = vec3<f32>(1.0, 0.7, 0.5);
    } else if temperature < 0.3 {
        color = vec3<f32>(1.0, 0.85, 0.7);
    } else if temperature < 0.5 {
        color = vec3<f32>(1.0, 0.97, 0.92);
    } else if temperature < 0.7 {
        color = vec3<f32>(0.95, 0.97, 1.0);
    } else if temperature < 0.85 {
        color = vec3<f32>(0.8, 0.88, 1.0);
    } else {
        color = vec3<f32>(0.7, 0.8, 1.0);
    }
    return color;
}

fn star_field(uv: vec2<f32>, scale: f32, seed: f32, time: f32, threshold: f32) -> vec4<f32> {
    let grid_uv = uv * scale;
    let grid_id = floor(grid_uv);
    let grid_fract = fract(grid_uv) - 0.5;

    let rand_vals = hash3(grid_id + seed);
    let star_prob = rand_vals.x;

    if star_prob > threshold {
        let offset = hash2(grid_id + seed + 100.0) * 0.45;
        let dist = length(grid_fract - offset);

        let magnitude = rand_vals.y;
        let base_size = 0.008 + magnitude * magnitude * 0.025;

        let temperature = rand_vals.z;
        let star_col = stellar_color(temperature);

        let core_brightness = 1.0 - smoothstep(base_size * 0.1, base_size, dist);
        let glow_brightness = (1.0 - smoothstep(0.0, base_size * 3.0, dist)) * 0.3;
        let total_brightness = core_brightness + glow_brightness;

        let twinkle_speed = 0.8 + hash(grid_id + seed + 300.0) * 2.5;
        let twinkle_phase = hash(grid_id + seed + 400.0) * 6.283;
        let twinkle_amount = 0.15 + hash(grid_id + seed + 500.0) * 0.2;
        let twinkle = 1.0 - twinkle_amount + twinkle_amount * (0.5 + 0.5 * sin(time * twinkle_speed + twinkle_phase));

        let final_brightness = total_brightness * twinkle * (0.3 + magnitude * 0.7);

        return vec4<f32>(star_col, final_brightness);
    }

    return vec4<f32>(0.0, 0.0, 0.0, 0.0);
}

fn bright_star_with_spikes(uv: vec2<f32>, scale: f32, seed: f32, time: f32) -> vec4<f32> {
    let grid_uv = uv * scale;
    let grid_id = floor(grid_uv);
    let grid_fract = fract(grid_uv) - 0.5;

    let rand_vals = hash3(grid_id + seed);

    if rand_vals.x > 0.992 {
        let offset = hash2(grid_id + seed + 100.0) * 0.35;
        let pos = grid_fract - offset;
        let dist = length(pos);

        let temperature = rand_vals.z;
        let star_col = stellar_color(temperature);

        let core = 1.0 - smoothstep(0.0, 0.025, dist);
        let inner_glow = (1.0 - smoothstep(0.0, 0.08, dist)) * 0.5;
        let outer_glow = (1.0 - smoothstep(0.0, 0.15, dist)) * 0.15;

        let angle = atan2(pos.y, pos.x);
        let spike_pattern = pow(abs(cos(angle * 2.0)), 32.0) + pow(abs(sin(angle * 2.0)), 32.0);
        let spike_falloff = 1.0 - smoothstep(0.01, 0.12, dist);
        let spikes = spike_pattern * spike_falloff * 0.6;

        let twinkle = 0.85 + 0.15 * sin(time * 1.5 + rand_vals.y * 6.283);

        let total = (core + inner_glow + outer_glow + spikes) * twinkle;

        return vec4<f32>(star_col, total);
    }

    return vec4<f32>(0.0, 0.0, 0.0, 0.0);
}

fn milky_way(dir: vec3<f32>) -> vec3<f32> {
    let galactic_pole = normalize(vec3<f32>(0.2, 0.9, 0.1));
    let galactic_dist = abs(dot(dir, galactic_pole));

    let band_width = 1.0 - smoothstep(0.0, 0.35, galactic_dist);

    let galactic_center = normalize(vec3<f32>(-0.8, -0.2, 0.5));
    let center_brightness = pow(max(dot(dir, galactic_center), 0.0), 3.0);

    var detail = 0.0;
    detail += snoise(dir * 8.0) * 0.5;
    detail += snoise(dir * 16.0) * 0.25;
    detail += snoise(dir * 32.0) * 0.125;
    detail = detail * 0.5 + 0.5;

    let dust_lanes = snoise(dir * 12.0 + vec3<f32>(0.0, 5.0, 0.0));
    let dust = smoothstep(0.3, 0.6, dust_lanes) * 0.4;

    let base_brightness = band_width * (0.4 + center_brightness * 0.6) * detail;
    let final_brightness = max(base_brightness - dust * band_width, 0.0);

    let warm_color = vec3<f32>(0.06, 0.05, 0.045);
    let cool_color = vec3<f32>(0.04, 0.045, 0.06);
    let milky_color = mix(cool_color, warm_color, center_brightness);

    return milky_color * final_brightness;
}

fn moon_surface(local_pos: vec2<f32>, moon_dir: vec3<f32>) -> vec3<f32> {
    let maria_noise1 = snoise(vec3<f32>(local_pos * 3.0, 0.0));
    let maria_noise2 = snoise(vec3<f32>(local_pos * 1.5 + vec2<f32>(10.0, 5.0), 0.0));
    let maria = smoothstep(0.1, 0.5, maria_noise1 * 0.6 + maria_noise2 * 0.4);

    var crater_detail = 0.0;
    crater_detail += snoise(vec3<f32>(local_pos * 25.0, 1.0)) * 0.3;
    crater_detail += snoise(vec3<f32>(local_pos * 50.0, 2.0)) * 0.15;
    crater_detail += snoise(vec3<f32>(local_pos * 100.0, 3.0)) * 0.08;

    let highland_color = vec3<f32>(0.85, 0.83, 0.78);
    let maria_color = vec3<f32>(0.45, 0.43, 0.42);

    var surface_color = mix(highland_color, maria_color, maria);
    surface_color *= 1.0 + crater_detail * 0.3;

    return surface_color;
}

fn atmospheric_extinction(height: f32) -> f32 {
    let zenith_extinction = 0.12;
    let horizon_extinction = 0.85;
    let extinction_curve = pow(1.0 - max(height, 0.0), 4.0);
    return mix(zenith_extinction, horizon_extinction, extinction_curve);
}

@fragment
fn fs_sky(in: VertexOutput) -> @location(0) vec4<f32> {
    let dir = normalize(in.world_dir);
    let time = u.time;
    let height = dir.y;
    let abs_height = abs(height);

    let moon_dir = normalize(vec3<f32>(0.4, 0.55, -0.75));
    let moon_dot = dot(dir, moon_dir);

    let zenith_color = vec3<f32>(0.008, 0.012, 0.035);
    let mid_color = vec3<f32>(0.015, 0.025, 0.065);
    let horizon_color = vec3<f32>(0.035, 0.045, 0.095);
    let horizon_glow_color = vec3<f32>(0.055, 0.055, 0.11);

    var sky_color: vec3<f32>;

    if abs_height < 0.02 {
        let t = abs_height / 0.02;
        sky_color = mix(horizon_glow_color, horizon_color, t);
    } else if abs_height < 0.1 {
        let t = (abs_height - 0.02) / 0.08;
        sky_color = mix(horizon_color, mid_color, smoothstep(0.0, 1.0, t));
    } else if abs_height < 0.4 {
        let t = (abs_height - 0.1) / 0.3;
        sky_color = mix(mid_color, zenith_color, smoothstep(0.0, 1.0, t));
    } else {
        sky_color = zenith_color;
    }

    let airglow_height = smoothstep(0.1, 0.35, abs_height) * (1.0 - smoothstep(0.35, 0.7, abs_height));
    let airglow_color = vec3<f32>(0.01, 0.02, 0.008);
    sky_color += airglow_color * airglow_height * 0.5;

    let moon_influence = pow(max(moon_dot, 0.0), 2.0);
    let moon_sky_tint = vec3<f32>(0.02, 0.025, 0.04) * moon_influence;
    sky_color += moon_sky_tint;

    if height < 0.0 {
        let ground_fade = 1.0 - smoothstep(-0.25, 0.0, height);
        sky_color *= mix(1.0, 0.05, ground_fade);
    }

    sky_color += milky_way(dir);

    let moon_angular_radius = 0.9995;
    let in_disk = moon_dot > moon_angular_radius;

    if in_disk {
        let moon_tangent = normalize(cross(moon_dir, vec3<f32>(0.0, 1.0, 0.0001)));
        let moon_bitangent = normalize(cross(moon_tangent, moon_dir));

        let offset_vec = dir - moon_dir * moon_dot;
        let local_x = dot(offset_vec, moon_tangent);
        let local_y = dot(offset_vec, moon_bitangent);
        let local_pos = vec2<f32>(local_x, local_y) * 120.0;

        let moon_radius_angle = acos(moon_angular_radius);
        let pixel_angle = acos(clamp(moon_dot, -1.0, 1.0));
        let r = pixel_angle / moon_radius_angle;

        let limb_darkening = 1.0 - 0.4 * r * r;

        let surface = moon_surface(local_pos, moon_dir);

        let sun_dir = normalize(vec3<f32>(0.6, 0.3, -0.7));

        let theta = atan2(local_y, local_x);

        let surface_normal = normalize(
            moon_dir * sqrt(max(1.0 - r * r, 0.0)) +
            moon_tangent * r * cos(theta) +
            moon_bitangent * r * sin(theta)
        );

        let sun_illumination = dot(surface_normal, sun_dir);
        let lit_side = smoothstep(-0.05, 0.1, sun_illumination);

        let earthshine_color = vec3<f32>(0.012, 0.015, 0.025);
        let lit_color = surface * limb_darkening;
        let moon_final = mix(earthshine_color, lit_color, lit_side * 0.92 + 0.08);

        let edge_softness = smoothstep(moon_angular_radius, moon_angular_radius + 0.0003, moon_dot);
        sky_color = mix(sky_color, moon_final, edge_softness);
    } else {
        let corona_dist = 1.0 - moon_dot;
        let inner_corona = exp(-corona_dist * 3000.0) * 0.25;
        let mid_corona = exp(-corona_dist * 800.0) * 0.12;
        let outer_corona = exp(-corona_dist * 200.0) * 0.05;

        let corona_color = vec3<f32>(0.6, 0.65, 0.75);
        sky_color += corona_color * (inner_corona + mid_corona + outer_corona);
    }

    let atmospheric_halo = pow(max(moon_dot, 0.0), 32.0) * 0.12;
    sky_color += vec3<f32>(0.12, 0.14, 0.2) * atmospheric_halo;

    if height > -0.05 {
        let uv = dir_to_uv(dir);
        let extinction = atmospheric_extinction(height);
        let star_visibility_base = smoothstep(-0.05, 0.2, height);
        let moon_washout = 1.0 - pow(max(moon_dot, 0.0), 4.0) * 0.7;

        var star_accumulator = vec3<f32>(0.0);

        let star1 = star_field(uv, 80.0, 0.0, time, 0.965);
        star_accumulator += star1.rgb * star1.a;

        let star2 = star_field(uv, 150.0, 10.0, time, 0.955);
        star_accumulator += star2.rgb * star2.a;

        let star3 = star_field(uv, 280.0, 20.0, time, 0.945);
        star_accumulator += star3.rgb * star3.a * 0.8;

        let star4 = star_field(uv, 500.0, 30.0, time, 0.935);
        star_accumulator += star4.rgb * star4.a * 0.5;

        let star5 = star_field(uv, 800.0, 40.0, time, 0.92);
        star_accumulator += star5.rgb * star5.a * 0.3;

        let bright_star = bright_star_with_spikes(uv, 40.0, 50.0, time);
        star_accumulator += bright_star.rgb * bright_star.a * 1.5;

        star_accumulator *= (1.0 - extinction) * star_visibility_base * moon_washout;
        sky_color += star_accumulator;
    }

    if height > 0.0 && height < 0.75 {
        let cloud_drift = vec3<f32>(time * 0.004, time * 0.001, time * 0.003);
        let cloud_pos = dir * 2.5;

        let cirrus_pos = cloud_pos * 0.4 + cloud_drift * 0.5;
        let cirrus_noise = fbm_detailed(cirrus_pos, 5, 2.2, 0.45);
        let cirrus_shape = smoothstep(0.45, 0.7, cirrus_noise);
        let cirrus_height_fade = smoothstep(0.3, 0.5, height) * (1.0 - smoothstep(0.6, 0.75, height));
        let cirrus_amount = cirrus_shape * cirrus_height_fade;

        let lower_pos = cloud_pos * 0.7 + cloud_drift;
        let lower_base = fbm_detailed(lower_pos, 6, 2.0, 0.5);
        let lower_detail = fbm(lower_pos * 2.5, 4) * 0.35;
        let lower_density = lower_base + lower_detail * 0.4;
        let lower_shape = smoothstep(0.48, 0.72, lower_density);
        let lower_height_fade = smoothstep(0.0, 0.12, height) * (1.0 - smoothstep(0.35, 0.55, height));
        let lower_amount = lower_shape * lower_height_fade;

        let cloud_moon_facing = max(dot(dir, moon_dir), 0.0);
        let light_factor = pow(cloud_moon_facing, 1.2);

        let cirrus_dark = vec3<f32>(0.02, 0.025, 0.045);
        let cirrus_lit = vec3<f32>(0.08, 0.1, 0.15);
        let cirrus_color = mix(cirrus_dark, cirrus_lit, light_factor);
        let cirrus_opacity = cirrus_amount * 0.4;

        let lower_dark = vec3<f32>(0.015, 0.02, 0.04);
        let lower_mid = vec3<f32>(0.045, 0.055, 0.085);
        let lower_lit = vec3<f32>(0.1, 0.12, 0.18);
        let lower_bright = vec3<f32>(0.18, 0.2, 0.28);

        var lower_color = mix(lower_dark, lower_mid, smoothstep(0.0, 0.25, light_factor));
        lower_color = mix(lower_color, lower_lit, smoothstep(0.25, 0.55, light_factor));
        lower_color = mix(lower_color, lower_bright, smoothstep(0.55, 1.0, light_factor) * 0.6);

        let silver_lining_zone = smoothstep(0.48, 0.58, lower_density) * (1.0 - smoothstep(0.58, 0.68, lower_density));
        let backlit = pow(cloud_moon_facing, 2.5);
        let silver_lining = silver_lining_zone * backlit * 0.35;
        lower_color += vec3<f32>(0.15, 0.17, 0.22) * silver_lining;

        let thickness_darken = smoothstep(0.55, 0.85, lower_density) * 0.3;
        lower_color *= 1.0 - thickness_darken;

        let lower_opacity = lower_amount * 0.9;

        let thin_cloud_factor = (1.0 - smoothstep(0.48, 0.65, lower_density)) * lower_amount;
        let star_show_through = thin_cloud_factor * 0.6;

        sky_color = mix(sky_color, cirrus_color, cirrus_opacity * (1.0 - lower_opacity * 0.5));

        let effective_lower_opacity = lower_opacity * (1.0 - star_show_through);
        sky_color = mix(sky_color, lower_color, effective_lower_opacity);
    }

    return vec4<f32>(sky_color, 1.0);
}
