mod cli;
mod config;
mod fsx;
mod merge;
mod ops;
mod project;
mod registry;
mod state;
#[cfg(test)]
mod test_util;
mod tools;
mod ui;

use std::ffi::OsString;

use anyhow::Result;
use clap::Parser;

use crate::cli::{Cli, Command};

fn main() -> Result<()> {
    ops::runner::install_cancel_handler()?;
    let cli = Cli::parse_from(rewrite_trailing_help(std::env::args_os()));
    match cli.command {
        None => ui::pick(),
        Some(Command::Init) => ops::init::run(),
        Some(Command::Use {
            name,
            scope,
            method,
            yes,
            permissions,
        }) => {
            ops::yes::set(yes);
            ops::use_package::run(name.as_deref(), scope, method, permissions)
        }
        Some(Command::Unuse { name, scope }) => ops::unuse::run(&name, scope),
        Some(Command::Sync {
            all,
            global,
            force,
            yes,
        }) => {
            ops::yes::set(yes);
            ops::sync::run_verb(all, global, force)
        }
        Some(Command::Up { name }) => ops::up::run(name),
        Some(Command::Purge { name, yes }) => ops::purge::run(&name, yes),
        Some(Command::Status) => ops::status::run(),
        Some(Command::Ls) => ops::ls::run(),
        Some(Command::Registry) => ops::registry::run(),
        Some(Command::Info { name }) => ops::info::run(&name),
        Some(Command::Install { name, all, yes }) => {
            ops::yes::set(yes);
            ops::install::run(name.as_deref(), all)
        }
        Some(Command::Uninstall { name, yes }) => ops::uninstall::run(&name, yes),
        Some(Command::Edit { name }) => ops::edit::run(name.as_deref()),
        Some(Command::Implode { yes }) => ops::implode::run(yes),
        Some(Command::Deinit { yes }) => ops::deinit::run(yes),
    }
}

/// Rewrite a trailing bare `help` into `--help` so `aimgr <cmd> help` behaves
/// like `aimgr <cmd> --help`. Leaves `aimgr help [cmd]` (clap's built-in)
/// untouched.
fn rewrite_trailing_help<I>(args: I) -> Vec<OsString>
where
    I: IntoIterator<Item = OsString>,
{
    let mut args: Vec<OsString> = args.into_iter().collect();
    if args.len() >= 3
        && args.last().map(|s| s == "help").unwrap_or(false)
        && args.get(1).map(|s| s != "help").unwrap_or(false)
    {
        *args.last_mut().unwrap() = OsString::from("--help");
    }
    args
}

#[cfg(test)]
mod tests {
    use super::rewrite_trailing_help;
    use std::ffi::OsString;

    fn run(args: &[&str]) -> Vec<String> {
        rewrite_trailing_help(args.iter().map(OsString::from))
            .into_iter()
            .map(|s| s.into_string().unwrap())
            .collect()
    }

    #[test]
    fn rewrites_trailing_help_on_subcommand() {
        assert_eq!(
            run(&["aimgr", "sync", "help"]),
            vec!["aimgr", "sync", "--help"]
        );
    }

    #[test]
    fn leaves_top_level_help_subcommand_alone() {
        assert_eq!(run(&["aimgr", "help"]), vec!["aimgr", "help"]);
        assert_eq!(
            run(&["aimgr", "help", "sync"]),
            vec!["aimgr", "help", "sync"]
        );
    }

    #[test]
    fn leaves_non_help_args_alone() {
        assert_eq!(
            run(&["aimgr", "sync", "--all"]),
            vec!["aimgr", "sync", "--all"]
        );
        assert_eq!(run(&["aimgr"]), vec!["aimgr"]);
    }
}
