use cord_trig::{NodeId, TrigGraph, TrigOp};
use std::fmt::Write;

/// Generate a complete WGSL raymarcher directly from a TrigGraph.
pub fn generate_wgsl_from_trig(graph: &TrigGraph) -> String {
    let mut out = String::with_capacity(4096);
    write_preamble(&mut out);
    write_sdf_from_trig(&mut out, graph);
    write_raymarcher(&mut out);
    out
}

fn var_name(id: NodeId) -> String {
    format!("v{id}")
}

fn write_sdf_from_trig(out: &mut String, graph: &TrigGraph) {
    out.push_str("fn scene_sdf(p: vec3<f32>) -> f32 {\n");

    for (i, op) in graph.nodes.iter().enumerate() {
        let v = var_name(i as NodeId);
        match op {
            TrigOp::InputX => writeln!(out, "    let {v} = p.x;").unwrap(),
            TrigOp::InputY => writeln!(out, "    let {v} = p.y;").unwrap(),
            TrigOp::InputZ => writeln!(out, "    let {v} = p.z;").unwrap(),
            TrigOp::Const(c) => {
                let f = *c as f32;
                if f.is_nan() || f.is_infinite() {
                    writeln!(out, "    let {v} = 0.0;").unwrap()
                } else {
                    writeln!(out, "    let {v} = {f:.8};").unwrap()
                }
            }

            TrigOp::Add(a, b) => writeln!(out, "    let {v} = {} + {};", var_name(*a), var_name(*b)).unwrap(),
            TrigOp::Sub(a, b) => writeln!(out, "    let {v} = {} - {};", var_name(*a), var_name(*b)).unwrap(),
            TrigOp::Mul(a, b) => writeln!(out, "    let {v} = {} * {};", var_name(*a), var_name(*b)).unwrap(),
            TrigOp::Div(a, b) => writeln!(out, "    let {v} = {} / {};", var_name(*a), var_name(*b)).unwrap(),
            TrigOp::Neg(a) => writeln!(out, "    let {v} = -{};", var_name(*a)).unwrap(),
            TrigOp::Abs(a) => writeln!(out, "    let {v} = abs({});", var_name(*a)).unwrap(),

            TrigOp::Sin(a) => writeln!(out, "    let {v} = sin({});", var_name(*a)).unwrap(),
            TrigOp::Cos(a) => writeln!(out, "    let {v} = cos({});", var_name(*a)).unwrap(),
            TrigOp::Tan(a) => writeln!(out, "    let {v} = tan({});", var_name(*a)).unwrap(),
            TrigOp::Asin(a) => writeln!(out, "    let {v} = asin({});", var_name(*a)).unwrap(),
            TrigOp::Acos(a) => writeln!(out, "    let {v} = acos({});", var_name(*a)).unwrap(),
            TrigOp::Atan(a) => writeln!(out, "    let {v} = atan({});", var_name(*a)).unwrap(),
            TrigOp::Sinh(a) => writeln!(out, "    let {v} = sinh({});", var_name(*a)).unwrap(),
            TrigOp::Cosh(a) => writeln!(out, "    let {v} = cosh({});", var_name(*a)).unwrap(),
            TrigOp::Tanh(a) => writeln!(out, "    let {v} = tanh({});", var_name(*a)).unwrap(),
            TrigOp::Asinh(a) => writeln!(out, "    let {v} = asinh({});", var_name(*a)).unwrap(),
            TrigOp::Acosh(a) => writeln!(out, "    let {v} = acosh({});", var_name(*a)).unwrap(),
            TrigOp::Atanh(a) => writeln!(out, "    let {v} = atanh({});", var_name(*a)).unwrap(),
            TrigOp::Sqrt(a) => writeln!(out, "    let {v} = sqrt({});", var_name(*a)).unwrap(),
            TrigOp::Exp(a) => writeln!(out, "    let {v} = exp({});", var_name(*a)).unwrap(),
            TrigOp::Ln(a) => writeln!(out, "    let {v} = log({});", var_name(*a)).unwrap(),

            TrigOp::Hypot(a, b) => {
                writeln!(out, "    let {v} = sqrt({a_v} * {a_v} + {b_v} * {b_v});",
                    a_v = var_name(*a), b_v = var_name(*b)).unwrap()
            }
            TrigOp::Atan2(a, b) => {
                writeln!(out, "    let {v} = atan2({}, {});", var_name(*a), var_name(*b)).unwrap()
            }

            TrigOp::Min(a, b) => writeln!(out, "    let {v} = min({}, {});", var_name(*a), var_name(*b)).unwrap(),
            TrigOp::Max(a, b) => writeln!(out, "    let {v} = max({}, {});", var_name(*a), var_name(*b)).unwrap(),
            TrigOp::Clamp { val, lo, hi } => {
                writeln!(out, "    let {v} = clamp({}, {}, {});",
                    var_name(*val), var_name(*lo), var_name(*hi)).unwrap()
            }
        }
    }

    writeln!(out, "    return {};", var_name(graph.output)).unwrap();
    out.push_str("}\n\n");
}

fn write_preamble(out: &mut String) {
    out.push_str(
r#"struct Uniforms {
    resolution: vec2<f32>,
    viewport_offset: vec2<f32>,
    camera_pos: vec3<f32>,
    time: f32,
    camera_target: vec3<f32>,
    fov: f32,
    render_flags: vec4<f32>,
    scene_scale: f32,
    _pad1: f32,
    _pad2: f32,
    _pad3: f32,
};

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

"#);
}

fn write_raymarcher(out: &mut String) {
    out.push_str(
r#"fn calc_normal(p: vec3<f32>) -> vec3<f32> {
    let e = vec2<f32>(0.0005 * u.scene_scale, -0.0005 * u.scene_scale);
    return normalize(
        e.xyy * scene_sdf(p + e.xyy) +
        e.yyx * scene_sdf(p + e.yyx) +
        e.yxy * scene_sdf(p + e.yxy) +
        e.xxx * scene_sdf(p + e.xxx)
    );
}

fn soft_shadow(ro: vec3<f32>, rd: vec3<f32>, mint: f32, maxt: f32, k: f32) -> f32 {
    let eps = 0.0002 * u.scene_scale;
    let step_lo = 0.001 * u.scene_scale;
    let step_hi = 0.5 * u.scene_scale;
    var res = 1.0;
    var t = mint;
    var prev_d = 1e10;
    for (var i = 0; i < 64; i++) {
        let d = scene_sdf(ro + rd * t);
        if d < eps { return 0.0; }
        let y = d * d / (2.0 * prev_d);
        let x = sqrt(max(d * d - y * y, 0.0));
        res = min(res, k * x / max(t - y, 0.0001));
        prev_d = d;
        t += clamp(d, step_lo, step_hi);
        if t > maxt { break; }
    }
    return clamp(res, 0.0, 1.0);
}

fn ao(p: vec3<f32>, n: vec3<f32>) -> f32 {
    let s = u.scene_scale;
    var occ = 0.0;
    var w = 1.0;
    for (var i = 0; i < 5; i++) {
        let h = (0.01 + 0.12 * f32(i)) * s;
        let d = scene_sdf(p + n * h);
        occ += (h - d) * w;
        w *= 0.7;
    }
    return clamp(1.0 - 1.5 * occ / s, 0.0, 1.0);
}

fn grid_aa(x: f32, line_w: f32) -> f32 {
    let d = abs(fract(x) - 0.5);
    let fw = fwidth(x);
    return smoothstep(0.0, max(fw * 1.5, 0.001), d - line_w);
}

fn ground_plane(ro: vec3<f32>, rd: vec3<f32>) -> vec4<f32> {
    if rd.z >= 0.0 { return vec4<f32>(0.0); }
    let t = -ro.z / rd.z;
    let max_ground = u.scene_scale * 50.0;
    if t < 0.0 || t > max_ground { return vec4<f32>(0.0); }
    let p = ro + rd * t;
    let gs = max(u.scene_scale * 0.5, 1.0);
    let gp = p.xy / gs;

    // Minor grid (every cell)
    let minor = grid_aa(gp.x, 0.02) * grid_aa(gp.y, 0.02);
    // Major grid (every 5 cells)
    let major = grid_aa(gp.x / 5.0, 0.04) * grid_aa(gp.y / 5.0, 0.04);

    // Axis lines at world origin
    let aw = gs * 0.08;
    let afw_x = fwidth(p.x);
    let afw_y = fwidth(p.y);
    let ax = 1.0 - smoothstep(aw, aw + max(afw_y * 1.5, 0.001), abs(p.y));
    let ay = 1.0 - smoothstep(aw, aw + max(afw_x * 1.5, 0.001), abs(p.x));

    let fade_k = 0.3 / (u.scene_scale * u.scene_scale);
    let fade = exp(-fade_k * t * t);

    let base = vec3<f32>(0.22, 0.24, 0.28);
    var col = mix(vec3<f32>(0.30, 0.32, 0.36), base, minor);
    col = mix(vec3<f32>(0.38, 0.40, 0.44), col, major);
    col = mix(vec3<f32>(0.55, 0.18, 0.18), col, ax * fade);
    col = mix(vec3<f32>(0.18, 0.45, 0.18), col, ay * fade);

    let sky_horizon = vec3<f32>(0.25, 0.35, 0.50);
    col = mix(sky_horizon, col, fade);

    let shad_max = u.scene_scale * 10.0;
    let shad = soft_shadow(vec3<f32>(p.x, p.y, 0.001 * u.scene_scale), normalize(vec3<f32>(0.5, 0.8, 1.0)), 0.1 * u.scene_scale, shad_max, 8.0);
    let shad_faded = mix(1.0, 0.5 + 0.5 * shad, fade);
    return vec4<f32>(col * shad_faded, t);
}

fn get_camera_ray(uv: vec2<f32>) -> vec3<f32> {
    let forward = normalize(u.camera_target - u.camera_pos);
    let right = normalize(cross(forward, vec3<f32>(0.0, 0.0, 1.0)));
    let up = cross(right, forward);
    return normalize(forward * u.fov + right * uv.x - up * uv.y);
}

fn shade_ray(ro: vec3<f32>, rd: vec3<f32>) -> vec3<f32> {
    let hit_eps = 0.0005 * u.scene_scale;
    let max_t = u.scene_scale * 20.0;
    var t = 0.0;
    var min_d = 1e10;
    var t_min = 0.0;
    var hit = false;
    for (var i = 0; i < 128; i++) {
        let p = ro + rd * t;
        let d = scene_sdf(p);
        if d < min_d {
            min_d = d;
            t_min = t;
        }
        if d < hit_eps {
            hit = true;
            break;
        }
        t += d;
        if t > max_t { break; }
    }

    var bg = vec3<f32>(0.0);
    let gp = ground_plane(ro, rd);
    if gp.w > 0.0 && u.render_flags.z > 0.5 {
        bg = gp.xyz;
    } else {
        bg = mix(vec3<f32>(0.25, 0.35, 0.50), vec3<f32>(0.05, 0.12, 0.35), clamp(rd.z * 1.5, 0.0, 1.0));
    }
    bg = pow(bg, vec3<f32>(0.4545));

    // SDF-derived coverage — analytical AA from the distance field
    let pix = max(t_min, 0.001) / u.resolution.y;
    var coverage: f32;
    if hit {
        coverage = 1.0;
    } else {
        coverage = 1.0 - smoothstep(0.0, pix * 2.0, min_d);
    }
    if coverage < 0.005 { return bg; }

    let shade_t = select(t_min, t, hit);
    let p = ro + rd * shade_t;
    let n = calc_normal(p);

    let light_dir = normalize(vec3<f32>(0.5, 0.8, 1.0));
    let diff = max(dot(n, light_dir), 0.0);

    let shad = mix(1.0, soft_shadow(p + n * 0.002 * u.scene_scale, light_dir, 0.02 * u.scene_scale, 15.0 * u.scene_scale, 8.0), u.render_flags.x);
    let occ = mix(1.0, ao(p, n), u.render_flags.y);

    let half_v = normalize(light_dir - rd);
    let spec = pow(max(dot(n, half_v), 0.0), 48.0) * 0.5;

    let sky_light = clamp(0.5 + 0.5 * n.z, 0.0, 1.0);
    let bounce = clamp(0.5 - 0.5 * n.z, 0.0, 1.0);

    let base = vec3<f32>(0.65, 0.67, 0.72);
    var lin = vec3<f32>(0.0);
    lin += diff * shad * vec3<f32>(1.0, 0.97, 0.9) * 1.5;
    lin += sky_light * occ * vec3<f32>(0.30, 0.40, 0.60) * 0.7;
    lin += bounce * occ * vec3<f32>(0.15, 0.12, 0.1) * 0.5;
    var color = base * lin + spec * shad;

    color = color / (color + vec3<f32>(1.0));
    color = pow(color, vec3<f32>(0.4545));

    return mix(bg, color, coverage);
}

@fragment
fn fs_main(@builtin(position) frag_coord: vec4<f32>) -> @location(0) vec4<f32> {
    let ro = u.camera_pos;
    let px = 1.0 / u.resolution.y;
    let uv = (frag_coord.xy - u.viewport_offset - u.resolution * 0.5) * px;
    let rd = get_camera_ray(uv);
    return vec4<f32>(shade_ray(ro, rd), 1.0);
}

struct VsOutput {
    @builtin(position) position: vec4<f32>,
};

@vertex
fn vs_main(@builtin(vertex_index) idx: u32) -> VsOutput {
    var pos = array<vec2<f32>, 3>(
        vec2<f32>(-1.0, -1.0),
        vec2<f32>(3.0, -1.0),
        vec2<f32>(-1.0, 3.0),
    );
    var out: VsOutput;
    out.position = vec4<f32>(pos[idx], 0.0, 1.0);
    return out;
}
"#);
}
