pub mod api;
pub mod globals;
pub mod vm;

use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use owo_colors::{OwoColorize, XtermColors};

use crate::globals::{CALLS, TESTS};

const DEFAULT_FILE: &str = include_str!("default.py");

/// CLI Arguments using Clap
#[derive(Clone, Parser)]
#[clap(version)]
struct Args {
    #[clap(long, short, default_value = "ncurl.py")]
    file: String,

    #[clap(subcommand)]
    command: Commands,
}

/// Commands for the CLI
#[derive(Subcommand, Clone, Eq, PartialEq, Debug)]
enum Commands {
    Init,
    Repl,
    Run { name: String },
    List,
    Test,
}

/// Main function to run the CLI
pub fn run() -> Result<()> {
    let span = tracing::info_span!("run");
    let _enter = span.enter();

    let args = Args::parse();

    if args.command == Commands::Init {
        if std::path::Path::new(&args.file).exists() {
            tracing::warn!(
                "File {} already exists, skipping initialization",
                &args.file
            );
            return Ok(());
        }

        let default_file = DEFAULT_FILE
            .to_string()
            .replace("${VERSION}", env!("CARGO_PKG_VERSION"));

        std::fs::write(&args.file, default_file)
            .context(format!("Failed to write default file to {}", &args.file))?;
        println!("Initialized successfully at {}.", &args.file);

        return Ok(());
    }

    let vm = vm::Vm::builder()
        .load(args.file)
        .context("Failed to load source to VM")?
        .build()
        .context("Failed to build VM")?;

    vm.init().context("Failed to run VM")?;

    match args.command {
        Commands::List => {
            println!("Available definitions:");
            for (i, def) in vm.list_definitions().iter().enumerate() {
                println!("{}: {}", i, def);
            }
        }
        Commands::Run { name } => {
            vm.run_definition(name, false)?;

            let (calls_passed, calls_failed) = *CALLS.lock().unwrap();
            println!(
                "{} {}{}{}",
                "Call results:".color(XtermColors::DarkGray),
                calls_passed.green(),
                "/".color(XtermColors::DarkGray),
                calls_failed.red()
            );

            vm.cleanup().context("Failed to cleanup VM")?;

            if calls_failed > 0 {
                std::process::exit(1);
            }
        }
        Commands::Repl => {
            unimplemented!("REPL mode is not implemented yet");
            // repl::repl(&mut runtime)?;
        }
        Commands::Test => {
            vm.run_tests()?;

            let (tests_passed, tests_failed) = *TESTS.lock().unwrap();
            println!(
                "{} {}{}{}",
                "Test results:".color(XtermColors::DarkGray),
                tests_passed.green(),
                "/".color(XtermColors::DarkGray),
                tests_failed.red()
            );

            if tests_failed > 0 {
                std::process::exit(1);
            }
        }
        _ => {
            tracing::error!("Unknown command: {:?}", args.command);
            return Err(anyhow::anyhow!("Unknown command"));
        }
    }

    Ok(())
}
