mod args;
mod dump;
mod execve;
mod help;
mod query;
mod resolve;
mod spinner;
#[cfg(test)]
mod tests;
mod which;
mod x;

use execve::execve;
use libpkgx::{config::Config, sync};
use spinner::Spinner;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args::Args {
        plus,
        mut args,
        mode,
        flags,
        find_program,
    } = args::parse();

    if let Some(dir) = &flags.chdir {
        std::env::set_current_dir(dir)?;
    }

    if flags.version_n_continue {
        eprintln!("{}", get_version_string(flags.json.is_some()));
    }

    match mode {
        args::Mode::Help => {
            println!("{}", help::usage());
            Ok(())
        }
        args::Mode::Version => {
            println!("{}", get_version_string(flags.json.is_some()));
            Ok(())
        }
        args::Mode::Query => {
            let (conn, _, config, _) = setup(&flags).await?;
            query::query(&args, &flags, &conn, &config).await
        }
        args::Mode::X => {
            let (mut conn, did_sync, config, mut spinner) = setup(&flags).await?;
            let (installations, graph) = resolve::resolve(
                &mut args,
                &plus,
                find_program,
                &config,
                &mut conn,
                did_sync,
                &mut spinner,
            )
            .await?;

            if !args.is_empty() {
                let env = libpkgx::env::map(&installations);
                let (cmd, args, env) =
                    x::exec(find_program, args, installations, env, flags, conn, graph).await?;
                spinner.finish_and_clear();
                execve(cmd, args, env)?;
                Ok(())
            } else if !plus.is_empty() {
                spinner.finish_and_clear();
                dump::dump(conn, installations, &flags)?;
                Ok(())
            } else if flags.version_n_continue || flags.sync {
                Ok(())
            } else {
                spinner.finish_and_clear();
                eprintln!("{}", help::usage());
                std::process::exit(2);
            }
        }
    }
}

async fn setup(
    flags: &args::Flags,
) -> Result<(rusqlite::Connection, bool, Config, Spinner), Box<dyn std::error::Error>> {
    let config = Config::new()?;

    std::fs::create_dir_all(config.pantry_db_file.parent().unwrap())?;
    let mut conn = rusqlite::Connection::open(&config.pantry_db_file)?;

    let mut spinner = Spinner::new(flags.quiet, flags.silent);

    let did_sync = if flags.sync || sync::should(&config)? {
        spinner.set_message("syncing pkg-db…");
        sync::ensure(&config, &mut conn).await?;
        true
    } else {
        false
    };

    Ok((conn, did_sync, config, spinner))
}

fn get_version_string(json: bool) -> String {
    if !json {
        format!("pkgx {}", env!("CARGO_PKG_VERSION"))
    } else {
        format!(
            "{{\"program\": \"pkgx\", \"version\": \"{}\"}}",
            env!("CARGO_PKG_VERSION")
        )
    }
}