use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use std::path::PathBuf;

#[derive(Parser)]
#[command(name = "cord", version, about = "3D geometry: source → trig → CORDIC")]
struct Cli {
    #[command(subcommand)]
    command: Command,
}

#[derive(Subcommand)]
enum Command {
    /// Parse and render a .scad or .crd file
    View {
        /// Input .scad, .crd, or .cord file
        input: PathBuf,
    },
    /// Compile a .scad or .crd file to .zcd (all layers)
    Build {
        /// Input .scad or .crd file
        input: PathBuf,
        /// Output .cord file
        #[arg(short, long)]
        output: Option<PathBuf>,
        /// CORDIC word size in bits
        #[arg(long, default_value = "32")]
        word_bits: u8,
    },
    /// Dump the generated WGSL shader to stdout
    Shader {
        /// Input .scad or .crd file
        input: PathBuf,
    },
    /// Decompile an STL/OBJ/3MF mesh into a .cord file
    Decompile {
        /// Input mesh file (.stl, .obj, .3mf)
        input: PathBuf,
        /// Output .cord file
        #[arg(short, long)]
        output: Option<PathBuf>,
        /// Octree depth for sparse grid
        #[arg(long, default_value = "7")]
        depth: u8,
        /// CORDIC word size in bits
        #[arg(long, default_value = "32")]
        word_bits: u8,
    },
    /// Evaluate the trig IR: build a test scene, compare f64 vs CORDIC
    Verify {
        /// CORDIC word size in bits
        #[arg(long, default_value = "32")]
        word_bits: u8,
    },
    /// Run Riesz monogenic signal analysis on a trig IR scene
    Analyze {
        /// Grid resolution (N×N×N)
        #[arg(long, default_value = "32")]
        resolution: usize,
    },
    /// Test all three traversal modes on a test scene
    Traverse {
        /// Radial steps for spherical convergence
        #[arg(long, default_value = "50")]
        radial_steps: usize,
    },
    /// Convert a .scad file to Cordial (.crd)
    Convert {
        /// Input .scad file
        input: PathBuf,
        /// Output .crd file
        #[arg(short, long)]
        output: Option<PathBuf>,
    },
    /// Reconstruct an SDF tree from a mesh file (STL, OBJ, or 3MF)
    Reconstruct {
        /// Input mesh file
        input: PathBuf,
        /// Output .crd file (Cordial source)
        #[arg(short, long)]
        output: Option<PathBuf>,
        /// Octree depth for sparse grid
        #[arg(long, default_value = "7")]
        depth: u8,
    },
}

fn main() -> Result<()> {
    let cli = Cli::parse();

    match cli.command {
        Command::View { input } => cmd_view(&input),
        Command::Build { input, output, word_bits } => cmd_build(&input, output, word_bits),
        Command::Shader { input } => cmd_shader(&input),
        Command::Decompile { input, output, depth, word_bits } => {
            cmd_decompile(&input, output, depth, word_bits)
        }
        Command::Verify { word_bits } => cmd_verify(word_bits),
        Command::Analyze { resolution } => cmd_analyze(resolution),
        Command::Traverse { radial_steps } => cmd_traverse(radial_steps),
        Command::Convert { input, output } => cmd_convert(&input, output),
        Command::Reconstruct { input, output, depth } => {
            cmd_reconstruct(&input, output, depth)
        }
    }
}

fn format_primitive_desc(kind: &cord_decompile::fit::PrimitiveKind) -> String {
    match kind {
        cord_decompile::fit::PrimitiveKind::Plane { normal, .. } =>
            format!("plane (n=[{:.2},{:.2},{:.2}])", normal.x, normal.y, normal.z),
        cord_decompile::fit::PrimitiveKind::Sphere { radius, .. } =>
            format!("sphere (r={:.3})", radius),
        cord_decompile::fit::PrimitiveKind::Cylinder { radius, .. } =>
            format!("cylinder (r={:.3})", radius),
        cord_decompile::fit::PrimitiveKind::Box { half_extents, .. } =>
            format!("box (h=[{:.3},{:.3},{:.3}])", half_extents[0], half_extents[1], half_extents[2]),
    }
}

fn is_crd(path: &std::path::Path) -> bool {
    path.extension()
        .and_then(|e| e.to_str())
        .map(|e| e.eq_ignore_ascii_case("crd"))
        .unwrap_or(false)
}

fn load_trig_graph(path: &std::path::Path) -> Result<(String, cord_trig::TrigGraph)> {
    let source = std::fs::read_to_string(path)
        .with_context(|| format!("reading {}", path.display()))?;
    if is_crd(path) {
        let scene = cord_expr::parse_expr_scene(&source)
            .map_err(|e| anyhow::anyhow!("{e}"))?;
        let graph = cord_expr::resolve_scene(scene);
        Ok((source, graph))
    } else {
        let program = cord_parse::parse(&source)
            .map_err(|e| anyhow::anyhow!("{e}"))?;
        let sdf = cord_sdf::lower::lower_program(&program)
            .map_err(|e| anyhow::anyhow!("{e}"))?;
        let graph = cord_sdf::sdf_to_trig(&sdf);
        Ok((source, graph))
    }
}

fn cmd_view(input: &std::path::Path) -> Result<()> {
    if is_crd(input) {
        let (_, graph) = load_trig_graph(input)?;
        let wgsl = cord_shader::generate_wgsl_from_trig(&graph);
        cord_render::run(wgsl, 20.0)
    } else {
        let source = std::fs::read_to_string(input)
            .with_context(|| format!("reading {}", input.display()))?;
        let program = cord_parse::parse(&source)
            .map_err(|e| anyhow::anyhow!("{e}"))?;
        let sdf = cord_sdf::lower::lower_program(&program)
            .map_err(|e| anyhow::anyhow!("{e}"))?;
        let graph = cord_sdf::sdf_to_trig(&sdf);
        let wgsl = cord_shader::generate_wgsl_from_trig(&graph);
        let radius = sdf.bounding_radius();
        cord_render::run(wgsl, radius)
    }
}

fn cmd_build(input: &std::path::Path, output: Option<PathBuf>, word_bits: u8) -> Result<()> {
    let out_path = output.unwrap_or_else(|| input.with_extension("zcd"));

    let (source, graph) = load_trig_graph(input)?;
    let wgsl = cord_shader::generate_wgsl_from_trig(&graph);

    // Auto-generate CORDIC config by scanning the graph
    let cordic_cfg = cord_cordic::CordicConfig::scan(&graph);
    let config = cord_cordic::compiler::CompileConfig {
        word_bits,
        cordic: Some(cordic_cfg.clone()),
    };
    let cordic = cord_cordic::CORDICProgram::compile(&graph, &config);
    let cordic_bytes = cordic.to_bytes();
    let config_toml = cordic_cfg.to_toml();

    let file = std::fs::File::create(&out_path)
        .with_context(|| format!("creating {}", out_path.display()))?;
    let mut writer = cord_format::write::ZcdWriter::new(std::io::BufWriter::new(file));
    writer.set_name(
        input.file_stem()
            .and_then(|s| s.to_str())
            .unwrap_or("untitled"),
    );
    if input.extension().and_then(|e| e.to_str()) == Some("crd") {
        writer.write_source_crd(&source)?;
    } else {
        writer.write_source_scad(&source)?;
    }
    writer.write_trig(&graph.to_bytes())?;
    writer.write_shader(&wgsl)?;
    writer.write_cordic(&cordic_bytes, word_bits)?;
    writer.write_config(&config_toml)?;
    writer.finish()?;

    println!("wrote {}", out_path.display());
    Ok(())
}

fn cmd_shader(input: &std::path::Path) -> Result<()> {
    let (_, graph) = load_trig_graph(input)?;
    let wgsl = cord_shader::generate_wgsl_from_trig(&graph);
    print!("{wgsl}");
    Ok(())
}

fn cmd_decompile(
    input: &std::path::Path,
    output: Option<PathBuf>,
    depth: u8,
    word_bits: u8,
) -> Result<()> {
    let out_path = output.unwrap_or_else(|| input.with_extension("zcd"));

    eprintln!("loading mesh...");
    let mesh = cord_decompile::mesh::TriangleMesh::load(input)?;
    eprintln!("  {} triangles, bounds: [{:.2},{:.2},{:.2}] to [{:.2},{:.2},{:.2}]",
        mesh.triangles.len(),
        mesh.bounds.min.x, mesh.bounds.min.y, mesh.bounds.min.z,
        mesh.bounds.max.x, mesh.bounds.max.y, mesh.bounds.max.z,
    );

    let mut config = cord_decompile::DecompileConfig::default();
    config.grid_depth = depth;
    let cell_size = mesh.bounds.diagonal() / (1u32 << depth) as f64;
    config.distance_threshold = cell_size * 0.75;

    eprintln!("decompiling (depth={depth})...");
    let result = cord_decompile::decompile(&mesh, &config)?;

    eprintln!("  grid cells: {}", result.grid.cells.len());
    eprintln!("  surface cells: {}", result.grid.surface_cell_count());
    eprintln!("  detected primitives: {}", result.primitives.len());
    for (i, prim) in result.primitives.iter().enumerate() {
        let desc = format_primitive_desc(&prim.kind);
        eprintln!("    {i}: {desc}, {} support points, error={:.4}",
            prim.support.len(), prim.fit_error);
    }

    let graph = cord_sdf::sdf_to_trig(&result.sdf);
    let wgsl = cord_shader::generate_wgsl_from_trig(&graph);

    let cordic_cfg = cord_cordic::CordicConfig::scan(&graph);
    let cordic_config = cord_cordic::compiler::CompileConfig {
        word_bits,
        cordic: Some(cordic_cfg),
    };
    let cordic = cord_cordic::CORDICProgram::compile(&graph, &cordic_config);
    let cordic_bytes = cordic.to_bytes();

    let file = std::fs::File::create(&out_path)
        .with_context(|| format!("creating {}", out_path.display()))?;
    let mut writer = cord_format::write::ZcdWriter::new(std::io::BufWriter::new(file));
    writer.set_name(
        input.file_stem()
            .and_then(|s| s.to_str())
            .unwrap_or("untitled"),
    );
    writer.write_shader(&wgsl)?;
    writer.write_cordic(&cordic_bytes, word_bits)?;
    writer.finish()?;

    eprintln!("wrote {}", out_path.display());
    Ok(())
}

/// Build a test scene via the trig IR builder, evaluate with both
/// f64 and CORDIC, compare results, print WGSL, report CORDIC cost.
fn cmd_verify(word_bits: u8) -> Result<()> {
    use cord_trig::lower::SdfBuilder;

    let mut b = SdfBuilder::new();
    let p = b.root_point();

    // Scene: sphere(5) with a cube(4) cut out, plus a cylinder cross
    let sph = b.sphere(p, 5.0);
    let bx = b.box_sdf(p, [4.0, 4.0, 4.0]);
    let diff = b.difference(sph, bx);

    let p2 = b.translate(p, [15.0, 0.0, 0.0]);
    let cyl_z = b.cylinder(p2, 3.0, 12.0);
    let p2_rx = b.rotate_x(p2, std::f64::consts::FRAC_PI_2);
    let cyl_x = b.cylinder(p2_rx, 3.0, 12.0);
    let cross = b.union(cyl_z, cyl_x);

    let scene = b.union(diff, cross);
    let graph = b.finish(scene);

    let cost = graph.cordic_cost();
    eprintln!("trig IR: {} nodes", graph.node_count());
    eprintln!("CORDIC cost: {} rotation, {} vectoring, {} linear, {} binary",
        cost.rotation, cost.vectoring, cost.linear, cost.binary);
    eprintln!("total CORDIC passes per evaluation: {}", cost.total_cordic_passes());

    // Compare f64 vs CORDIC at several test points
    let cordic_eval = cord_cordic::CORDICEvaluator::new(word_bits);
    let test_points = [
        (5.0, 0.0, 0.0, "sphere surface"),
        (0.0, 0.0, 0.0, "origin (inside sphere)"),
        (10.0, 0.0, 0.0, "between objects"),
        (15.0, 3.0, 0.0, "cylinder surface"),
        (15.0, 0.0, 6.0, "cylinder cap"),
    ];

    eprintln!("\nf64 vs CORDIC-{word_bits} comparison:");
    let mut max_err = 0.0f64;
    for (x, y, z, label) in &test_points {
        let f64_val = cord_trig::eval::evaluate(&graph, *x, *y, *z);
        let cordic_val = cordic_eval.evaluate(&graph, *x, *y, *z);
        let err = (f64_val - cordic_val).abs();
        max_err = max_err.max(err);
        eprintln!("  {label:25} f64={f64_val:+.6}  CORDIC={cordic_val:+.6}  err={err:.6}");
    }
    eprintln!("  max error: {max_err:.6}");

    // Generate WGSL from trig IR
    let wgsl = cord_shader::generate_wgsl_from_trig(&graph);
    eprintln!("\nWGSL shader: {} bytes", wgsl.len());

    Ok(())
}

/// Evaluate a test scene on a 3D grid and run Riesz monogenic analysis.
fn cmd_analyze(resolution: usize) -> Result<()> {
    use cord_trig::lower::SdfBuilder;

    let mut b = SdfBuilder::new();
    let p = b.root_point();

    // Simple scene: sphere with a box cut
    let sph = b.sphere(p, 5.0);
    let bx = b.box_sdf(p, [3.5, 3.5, 3.5]);
    let diff = b.difference(sph, bx);
    let graph = b.finish(diff);

    let extent = 8.0;
    let min = [-extent, -extent, -extent];
    let max = [extent, extent, extent];

    eprintln!("evaluating {0}x{0}x{0} grid...", resolution);
    let field = cord_trig::eval::evaluate_grid(&graph, min, max, resolution);

    eprintln!("computing monogenic signal...");
    let mono = cord_riesz::MonogenicField::compute(&field, resolution);

    // Statistics
    let mut edge_count = 0usize;
    let mut ridge_count = 0usize;
    let mut blob_count = 0usize;
    let mut max_amplitude = 0.0f64;

    let amp_threshold = 0.5;
    for s in &mono.samples {
        max_amplitude = max_amplitude.max(s.amplitude);
        if s.amplitude > amp_threshold {
            let phase_deg = s.phase.to_degrees();
            if phase_deg > 60.0 && phase_deg < 120.0 {
                edge_count += 1;
            } else if phase_deg < 30.0 || phase_deg > 150.0 {
                if phase_deg < 30.0 { ridge_count += 1; }
                else { blob_count += 1; }
            }
        }
    }

    eprintln!("monogenic signal analysis:");
    eprintln!("  max amplitude: {max_amplitude:.4}");
    eprintln!("  significant samples (amp > {amp_threshold}):");
    eprintln!("    edges (phase ~90deg):  {edge_count}");
    eprintln!("    ridges (phase ~0deg):  {ridge_count}");
    eprintln!("    blobs (phase ~180deg): {blob_count}");

    // Run cepstrum
    eprintln!("computing spatial cepstrum...");
    let cep = cord_riesz::cepstrum::Cepstrum::compute(&field, resolution);
    let periodicities = cep.detect_periodicities(5);

    if periodicities.is_empty() {
        eprintln!("  no strong periodicities detected");
    } else {
        eprintln!("  top periodicities:");
        for p in &periodicities {
            let period_len = ((p.dx * p.dx + p.dy * p.dy + p.dz * p.dz) as f64).sqrt();
            eprintln!("    [{},{},{}] period={:.2} cells, strength={:.4}",
                p.dx, p.dy, p.dz, period_len, p.strength);
        }
    }

    Ok(())
}

/// Compare all three traversal modes on the same scene.
fn cmd_traverse(radial_steps: usize) -> Result<()> {
    use cord_trig::lower::SdfBuilder;
    use cord_trig::traverse::*;

    let mut b = SdfBuilder::new();
    let p = b.root_point();

    let sph = b.sphere(p, 5.0);
    let bx = b.box_sdf(p, [3.5, 3.5, 3.5]);
    let diff = b.difference(sph, bx);
    let graph = b.finish(diff);

    let bounds = EvalBounds {
        min: [-8.0, -8.0, -8.0],
        max: [8.0, 8.0, 8.0],
        resolution: 16,
    };

    // Mode 1: Sequential
    eprintln!("=== Mode 1: Sequential ===");
    let r1 = traverse(&graph, &TraversalMode::Sequential, &bounds);
    eprintln!("  samples: {}", r1.total_samples);
    eprintln!("  converged: {}", r1.converged);

    // Mode 2: Parallel Mesh
    eprintln!("\n=== Mode 2: Parallel Mesh (8 divisions, 10% overlap) ===");
    let r2 = traverse(
        &graph,
        &TraversalMode::ParallelMesh { divisions: 8, overlap: 0.1 },
        &bounds,
    );
    eprintln!("  samples: {}", r2.total_samples);
    eprintln!("  converged: {}", r2.converged);

    // Mode 3: Spherical Convergence
    eprintln!("\n=== Mode 3: Spherical Convergence ===");
    let r3 = traverse(
        &graph,
        &TraversalMode::SphericalConvergence {
            origin: [0.0, 0.0, 0.0],
            max_radius: 8.0,
            radial_steps,
            base_angular_samples: 200,
        },
        &bounds,
    );
    eprintln!("  samples: {}", r3.total_samples);
    eprintln!("  converged: {}", r3.converged);
    if r3.converged {
        eprintln!("  convergence radius: {:.3}", r3.convergence_radius);
        eprintln!("  efficiency: {:.1}% of sequential",
            100.0 * r3.total_samples as f64 / r1.total_samples as f64);
    }

    Ok(())
}

fn cmd_reconstruct(
    input: &std::path::Path,
    output: Option<PathBuf>,
    depth: u8,
) -> Result<()> {
    eprintln!("loading mesh...");
    let mesh = cord_decompile::mesh::TriangleMesh::load(input)?;
    eprintln!("  {} triangles, bounds: [{:.2},{:.2},{:.2}] to [{:.2},{:.2},{:.2}]",
        mesh.triangles.len(),
        mesh.bounds.min.x, mesh.bounds.min.y, mesh.bounds.min.z,
        mesh.bounds.max.x, mesh.bounds.max.y, mesh.bounds.max.z,
    );

    let mut config = cord_decompile::DecompileConfig::default();
    config.grid_depth = depth;
    let cell_size = mesh.bounds.diagonal() / (1u32 << depth) as f64;
    config.distance_threshold = cell_size * 0.75;

    eprintln!("reconstructing (depth={depth}, threshold={:.4})...", config.distance_threshold);
    let result = cord_decompile::decompile(&mesh, &config)?;

    eprintln!("  grid cells: {}", result.grid.cells.len());
    eprintln!("  surface cells: {}", result.grid.surface_cell_count());
    eprintln!("  detected primitives: {}", result.primitives.len());
    for (i, prim) in result.primitives.iter().enumerate() {
        let desc = format_primitive_desc(&prim.kind);
        eprintln!("    {i}: {desc}, {} support points, error={:.4}",
            prim.support.len(), prim.fit_error);
    }

    let mut sdf = result.sdf;
    cord_sdf::simplify(&mut sdf);
    let cordial = cord_sdf::sdf_to_cordial(&sdf);

    if let Some(out_path) = output {
        std::fs::write(&out_path, &cordial)
            .with_context(|| format!("writing {}", out_path.display()))?;
        eprintln!("wrote {}", out_path.display());
    } else {
        print!("{cordial}");
    }

    Ok(())
}

fn cmd_convert(input: &std::path::Path, output: Option<PathBuf>) -> Result<()> {
    let out_path = output.unwrap_or_else(|| input.with_extension("crd"));
    let source = std::fs::read_to_string(input)
        .with_context(|| format!("reading {}", input.display()))?;
    let program = cord_parse::parse(&source)
        .map_err(|e| anyhow::anyhow!("{e}"))?;
    let mut sdf = cord_sdf::lower::lower_program(&program)
        .map_err(|e| anyhow::anyhow!("{e}"))?;
    cord_sdf::simplify(&mut sdf);
    let cordial = cord_sdf::sdf_to_cordial(&sdf);
    std::fs::write(&out_path, &cordial)
        .with_context(|| format!("writing {}", out_path.display()))?;
    eprintln!("converted {} -> {}", input.display(), out_path.display());
    Ok(())
}
