use crate::SdfNode;
use std::collections::BTreeMap;
use std::fmt::Write;

pub fn sdf_to_cordial(node: &SdfNode) -> String {
    let mut coll = ParamCollector::new(); coll.walk(node, ""); let params = coll.finish();
    let mut ctx = EmitCtx { counter: 0, buf: String::new(), params: &params, indent: "  " };
    let root = emit_node(&mut ctx, node); let body = ctx.buf;
    let mut out = String::new();
    let pn: Vec<&str> = params.iter().map(|(_, (n, _))| n.as_str()).collect();
    if pn.is_empty() { let _ = writeln!(out, "let scene: Obj = {root}"); let _ = writeln!(out, "cast(scene)"); return out; }
    let _ = write!(out, "sch Part(\n");
    for (i, n) in pn.iter().enumerate() { let _ = write!(out, "    {n}"); if i+1 < pn.len() { out.push(','); } out.push('\n'); }
    let _ = writeln!(out, ") {{"); let _ = write!(out, "{body}"); let _ = writeln!(out, "  {root}"); let _ = writeln!(out, "}}"); out.push('\n');
    let _ = writeln!(out, "// dimensions");
    for (_, (n, v)) in &params { let _ = writeln!(out, "let {n} = {}", fv(*v)); } out.push('\n');
    let _ = writeln!(out, "let scene: Obj = Part(");
    for (i, n) in pn.iter().enumerate() { let _ = write!(out, "    {n}"); if i+1 < pn.len() { out.push(','); } out.push('\n'); }
    let _ = writeln!(out, ")"); let _ = writeln!(out, "cast(scene)"); out
}

fn fv(v: f64) -> String { if v == v.round() && v.abs() < 1e9 { format!("{:.1}", v) } else { format!("{:.4}", v) } }

struct ParamCollector { seen: BTreeMap<u64, (String, f64)>, counter: usize }
impl ParamCollector {
    fn new() -> Self { ParamCollector { seen: BTreeMap::new(), counter: 0 } }
    fn key(v: f64) -> u64 { v.to_bits() }
    fn register(&mut self, val: f64, hint: &str) {
        if val == 0.0 || val == 1.0 || val == -1.0 || val == 0.5 || val == -0.5 { return; }
        let k = Self::key(val); if self.seen.contains_key(&k) { return; }
        let name = if hint.is_empty() { let n = format!("k{}", self.counter); self.counter += 1; n }
        else { let base = hint.replace('-', "_").replace(' ', "_");
            if self.seen.values().any(|(n, _)| n == &base) { let n = format!("{base}_{}", self.counter); self.counter += 1; n } else { base } };
        self.seen.insert(k, (name, val));
    }
    fn walk(&mut self, node: &SdfNode, ctx: &str) {
        match node {
            SdfNode::Sphere { radius } => { self.register(*radius, &if ctx.is_empty() { "radius".into() } else { format!("{ctx}_r") }); }
            SdfNode::Box { half_extents: h } => { let p = if ctx.is_empty() { "half" } else { ctx }; self.register(h[0], &format!("{p}_x")); self.register(h[1], &format!("{p}_y")); self.register(h[2], &format!("{p}_z")); }
            SdfNode::Cylinder { radius, height } => { let p = if ctx.is_empty() { "cyl" } else { ctx }; self.register(*radius, &format!("{p}_r")); self.register(height / 2.0, &format!("{p}_hh")); }
            SdfNode::Translate { offset, child } => { self.register(offset[0], &format!("{ctx}tx")); self.register(offset[1], &format!("{ctx}ty")); self.register(offset[2], &format!("{ctx}tz")); self.walk(child, ctx); }
            SdfNode::Rotate { angle_deg, child, .. } => { self.register(angle_deg.to_radians(), &format!("{ctx}angle")); self.walk(child, ctx); }
            SdfNode::Scale { factor, child } => { self.register(factor[0], &format!("{ctx}sx")); self.register(factor[1], &format!("{ctx}sy")); self.register(factor[2], &format!("{ctx}sz")); self.walk(child, ctx); }
            SdfNode::Union(ch) | SdfNode::Intersection(ch) => { for c in ch { self.walk(c, ctx); } }
            SdfNode::SmoothUnion { children, k } => { self.register(*k, &format!("{ctx}smooth_k")); for c in children { self.walk(c, ctx); } }
            SdfNode::Difference { base, subtract } => { self.walk(base, ctx); for s in subtract { self.walk(s, ctx); } }
        }
    }
    fn finish(self) -> BTreeMap<u64, (String, f64)> { self.seen }
}

struct EmitCtx<'a> { counter: usize, buf: String, params: &'a BTreeMap<u64, (String, f64)>, indent: &'static str }
impl<'a> EmitCtx<'a> {
    fn fresh(&mut self, pfx: &str) -> String { let n = format!("{pfx}{}", self.counter); self.counter += 1; n }
    fn val(&self, v: f64) -> String { if let Some((n, _)) = self.params.get(&ParamCollector::key(v)) { n.clone() } else { fv(v) } }
}

fn emit_node(ctx: &mut EmitCtx, node: &SdfNode) -> String {
    match node {
        SdfNode::Sphere { radius } => format!("sphere({})", ctx.val(*radius)),
        SdfNode::Box { half_extents: h } => format!("box({}, {}, {})", ctx.val(h[0]), ctx.val(h[1]), ctx.val(h[2])),
        SdfNode::Cylinder { radius, height } => format!("cylinder({}, {})", ctx.val(*radius), ctx.val(height / 2.0)),
        SdfNode::Translate { offset, child } => {
            let mut t = *offset; let mut inner = child.as_ref();
            while let SdfNode::Translate { offset: o2, child: c2 } = inner { t[0] += o2[0]; t[1] += o2[1]; t[2] += o2[2]; inner = c2.as_ref(); }
            let c = emit_child(ctx, inner, "t"); format!("translate({c}, {}, {}, {})", ctx.val(t[0]), ctx.val(t[1]), ctx.val(t[2]))
        }
        SdfNode::Rotate { axis, angle_deg, child } => {
            let c = emit_child(ctx, child, "r"); let a = ctx.val(angle_deg.to_radians());
            if axis[0].abs() > 0.9 { format!("rotate_x({c}, {a})") } else if axis[1].abs() > 0.9 { format!("rotate_y({c}, {a})") } else { format!("rotate_z({c}, {a})") }
        }
        SdfNode::Scale { factor, child } => { let c = emit_child(ctx, child, "s"); format!("scale({c}, {}, {}, {})", ctx.val(factor[0]), ctx.val(factor[1]), ctx.val(factor[2])) }
        SdfNode::Union(ch) => emit_chain(ctx, ch, "union"),
        SdfNode::Intersection(ch) => emit_chain(ctx, ch, "intersect"),
        SdfNode::Difference { base, subtract } => {
            let b = emit_child(ctx, base, "d"); let ind = ctx.indent; let mut r = b;
            for sub in subtract { let s = emit_child_node(ctx, sub, "d"); let n = ctx.fresh("d"); let _ = writeln!(ctx.buf, "{ind}let {n} = diff({r}, {s})"); r = n; } r
        }
        SdfNode::SmoothUnion { children, k } => emit_smooth_chain(ctx, children, *k),
    }
}
fn emit_child(ctx: &mut EmitCtx, node: &SdfNode, pfx: &str) -> String { emit_child_node(ctx, node, pfx) }
fn emit_child_node(ctx: &mut EmitCtx, node: &SdfNode, pfx: &str) -> String {
    let expr = emit_node(ctx, node);
    if !expr.contains('\n') && expr.len() < 60 && expr.chars().filter(|&c| c == '(').count() <= 1 { return expr; }
    let n = ctx.fresh(pfx); let ind = ctx.indent; let _ = writeln!(ctx.buf, "{ind}let {n} = {expr}"); n
}
fn emit_chain(ctx: &mut EmitCtx, ch: &[SdfNode], op: &str) -> String {
    if ch.len() == 1 { return emit_node(ctx, &ch[0]); }
    let first = emit_child_node(ctx, &ch[0], &op[..1]); let ind = ctx.indent; let mut r = first;
    for c in &ch[1..] { let x = emit_child_node(ctx, c, &op[..1]); let n = ctx.fresh(&op[..1]); let _ = writeln!(ctx.buf, "{ind}let {n} = {op}({r}, {x})"); r = n; } r
}
fn emit_smooth_chain(ctx: &mut EmitCtx, ch: &[SdfNode], k: f64) -> String {
    if ch.len() == 1 { return emit_node(ctx, &ch[0]); }
    let kv = ctx.val(k); let first = emit_child_node(ctx, &ch[0], "s"); let ind = ctx.indent; let mut r = first;
    for c in &ch[1..] { let x = emit_child_node(ctx, c, "s"); let n = ctx.fresh("s"); let _ = writeln!(ctx.buf, "{ind}let {n} = smooth_union({r}, {x}, {kv})"); r = n; } r
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn sphere_cordial() { assert!(sdf_to_cordial(&SdfNode::Sphere { radius: 5.0 }).contains("sphere(")); }

    #[test]
    fn box_cordial() {
        let src = sdf_to_cordial(&SdfNode::Box { half_extents: [1.0, 2.0, 3.0] });
        assert!(src.contains("box("));
    }

    #[test]
    fn translate_cordial() {
        let node = SdfNode::Translate {
            offset: [1.0, 2.0, 3.0],
            child: Box::new(SdfNode::Sphere { radius: 1.0 }),
        };
        assert!(sdf_to_cordial(&node).contains("translate("));
    }

    #[test]
    fn rotate_x_emitted() {
        let node = SdfNode::Rotate { axis: [1.0, 0.0, 0.0], angle_deg: 90.0, child: Box::new(SdfNode::Sphere { radius: 1.0 }) };
        let src = sdf_to_cordial(&node);
        assert!(src.contains("rotate_x("), "expected rotate_x in: {src}");
    }

    #[test]
    fn rotate_y_emitted() {
        let node = SdfNode::Rotate { axis: [0.0, 1.0, 0.0], angle_deg: 45.0, child: Box::new(SdfNode::Sphere { radius: 1.0 }) };
        let src = sdf_to_cordial(&node);
        assert!(src.contains("rotate_y("), "expected rotate_y in: {src}");
    }

    #[test]
    fn rotate_z_emitted() {
        let node = SdfNode::Rotate { axis: [0.0, 0.0, 1.0], angle_deg: 30.0, child: Box::new(SdfNode::Sphere { radius: 1.0 }) };
        let src = sdf_to_cordial(&node);
        assert!(src.contains("rotate_z("), "expected rotate_z in: {src}");
    }

    #[test]
    fn non_uniform_scale_emitted() {
        let node = SdfNode::Scale { factor: [2.0, 3.0, 4.0], child: Box::new(SdfNode::Sphere { radius: 1.0 }) };
        let src = sdf_to_cordial(&node);
        assert!(src.contains("scale("), "expected scale in: {src}");
    }

    #[test]
    fn smooth_union_preserves_k() {
        let node = SdfNode::SmoothUnion {
            children: vec![SdfNode::Sphere { radius: 1.0 }, SdfNode::Sphere { radius: 2.0 }],
            k: 0.5,
        };
        let src = sdf_to_cordial(&node);
        assert!(src.contains("smooth_union("), "expected smooth_union in: {src}");
    }

    #[test]
    fn smooth_union_nontrivial_k() {
        let node = SdfNode::SmoothUnion {
            children: vec![SdfNode::Sphere { radius: 3.0 }, SdfNode::Box { half_extents: [2.0, 2.0, 2.0] }],
            k: 1.5,
        };
        let src = sdf_to_cordial(&node);
        assert!(src.contains("smooth_union("), "expected smooth_union in: {src}");
        assert!(src.contains("smooth_k") || src.contains("1.5"), "k parameter missing in: {src}");
    }

    #[test]
    fn difference_cordial() {
        let node = SdfNode::Difference {
            base: Box::new(SdfNode::Box { half_extents: [5.0, 5.0, 5.0] }),
            subtract: vec![SdfNode::Sphere { radius: 4.0 }],
        };
        assert!(sdf_to_cordial(&node).contains("diff("));
    }

    #[test]
    fn cordial_parse_roundtrip() {
        let node = SdfNode::Difference { base: Box::new(SdfNode::Box { half_extents: [5.0, 5.0, 5.0] }), subtract: vec![SdfNode::Sphere { radius: 4.0 }] };
        let src = sdf_to_cordial(&node); assert!(cord_expr::parse_expr_scene(&src).is_ok(), "parse failed:\n{src}");
    }

    #[test]
    fn cordial_smooth_union_parse_roundtrip() {
        let node = SdfNode::SmoothUnion {
            children: vec![
                SdfNode::Sphere { radius: 3.0 },
                SdfNode::Translate { offset: [4.0, 0.0, 0.0], child: Box::new(SdfNode::Sphere { radius: 2.0 }) },
            ],
            k: 1.5,
        };
        let src = sdf_to_cordial(&node);
        assert!(cord_expr::parse_expr_scene(&src).is_ok(), "smooth_union cordial parse failed:\n{src}");
    }

    fn scad_eval(scad: &str) -> Vec<((f64,f64,f64), f64, f64)> {
        let prog = cord_parse::parse(scad).unwrap(); let mut sdf = crate::lower::lower_program(&prog).unwrap();
        crate::simplify::simplify(&mut sdf); let g1 = crate::sdf_to_trig(&sdf); let src = sdf_to_cordial(&sdf);
        let scene = cord_expr::parse_expr_scene(&src).unwrap_or_else(|e| panic!("{e}\n{src}"));
        let g2 = cord_expr::resolve_scene(scene);
        [(0.0,0.0,0.0),(5.0,0.0,0.0),(0.0,5.0,0.0),(0.0,0.0,5.0),(3.0,3.0,3.0),(10.0,0.0,0.0),(-5.0,-5.0,0.0)]
            .iter().map(|&(x,y,z)| ((x,y,z), cord_trig::eval::evaluate(&g1,x,y,z), cord_trig::eval::evaluate(&g2,x,y,z))).collect()
    }

    #[test]
    fn scad_sphere_roundtrip() { for ((x,y,z),v1,v2) in scad_eval("sphere(r=5);") { assert!((v1-v2).abs() < 1e-4, "at ({x},{y},{z}): {v1} vs {v2}"); } }

    #[test]
    fn scad_cube_roundtrip() { for ((x,y,z),v1,v2) in scad_eval("cube([10,10,10], center=true);") { assert!((v1-v2).abs() < 1e-4, "at ({x},{y},{z}): {v1} vs {v2}"); } }

    #[test]
    fn scad_complex_roundtrip() {
        let scad = "difference() { sphere(r=10); translate([0,0,5]) cube([15,15,15], center=true); }\ntranslate([25,0,0]) { union() { cylinder(h=20, r=5, center=true); rotate([90,0,0]) cylinder(h=20, r=5, center=true); rotate([0,90,0]) cylinder(h=20, r=5, center=true); }}";
        for ((x,y,z),v1,v2) in scad_eval(scad) { assert!((v1-v2).abs() < 1e-4, "at ({x},{y},{z}): {v1} vs {v2}"); }
    }
}
