struct TerrainProduceUniforms {
    continental_spline: array<vec4<f32>, 4>,
    erosion_spline: array<vec4<f32>, 4>,
    frequencies: vec4<f32>,
    warp_detail: vec4<f32>,
    erosion_params: vec4<f32>,
    seeds: vec4<u32>,
};

struct TerrainJob {
    data: vec4<i32>,
};

@group(0) @binding(0) var<uniform> uniforms: TerrainProduceUniforms;
@group(0) @binding(1) var<storage, read> jobs: array<TerrainJob>;
@group(0) @binding(2) var cache: texture_storage_2d_array<r32float, write>;

fn terrain_height_world(world_xz: vec2<f32>) -> f32 {
    let seed = uniforms.seeds.x;
    let continental = clamp(
        terrain_fbm(world_xz * uniforms.frequencies.x, 5u, seed + 1u) * 2.2,
        -1.0,
        1.0,
    );
    let erosion = clamp(
        terrain_fbm(world_xz * uniforms.frequencies.y + vec2<f32>(311.0, 97.0), 4u, seed + 2u)
            * 2.2,
        -1.0,
        1.0,
    );
    let warp = vec2<f32>(
        terrain_fbm(world_xz * uniforms.frequencies.w, 3u, seed + 3u),
        terrain_fbm(world_xz * uniforms.frequencies.w + vec2<f32>(57.0, 113.0), 3u, seed + 4u),
    ) * uniforms.warp_detail.x;
    let mountain_noise = terrain_fbm_eroded(
        (world_xz + warp) * uniforms.frequencies.z,
        7u,
        seed + 5u,
        uniforms.erosion_params.x,
    );
    let ridge = clamp(mountain_noise + 0.5, 0.0, 1.0);
    let base = terrain_spline(uniforms.continental_spline, continental);
    let mountain_amplitude = terrain_spline(uniforms.erosion_spline, erosion);
    let mountain_scale = max(base, 0.0) + 30.0;
    let detail = terrain_fbm(world_xz * 0.06, 3u, seed + 6u) * uniforms.warp_detail.y;
    let height = base + ridge * mountain_amplitude * mountain_scale + detail;
    let flatten_radius = uniforms.warp_detail.w;
    if flatten_radius > 0.0 {
        let falloff = smoothstep(flatten_radius, flatten_radius * 2.5, length(world_xz));
        return mix(-0.05, height, falloff);
    }
    return height;
}

@compute @workgroup_size(8, 8)
fn produce_tiles(
    @builtin(workgroup_id) workgroup: vec3<u32>,
    @builtin(local_invocation_id) local: vec3<u32>,
) {
    let job = jobs[workgroup.z].data;
    let scale_level = u32(job.z);
    let layer = i32(job.w);
    let texel = vec2<i32>(job.xy) + vec2<i32>(workgroup.xy * 8u + local.xy);
    let texel_size = uniforms.warp_detail.z * f32(1u << scale_level);
    let world_xz = (vec2<f32>(texel) + vec2<f32>(0.5, 0.5)) * texel_size;
    let height = terrain_height_world(world_xz);
    let wrapped = vec2<i32>(
        ((texel.x % TERRAIN_CACHE_SIZE) + TERRAIN_CACHE_SIZE) % TERRAIN_CACHE_SIZE,
        ((texel.y % TERRAIN_CACHE_SIZE) + TERRAIN_CACHE_SIZE) % TERRAIN_CACHE_SIZE,
    );
    textureStore(cache, wrapped, layer, vec4<f32>(height, 0.0, 0.0, 0.0));
}
