use clap::{value_parser, Arg, ArgAction, Command, ValueHint};
use std::{env, io, process};

/// Open the given uri in an appropriate program
pub fn edit(file: &str) -> Result<(), io::Error> {
    if let Ok(ed) = env::var("EDITOR") {
        run(&ed, file)
    } else {
        run("vi", file)
            .or_else(|_| run("vim", file))
            .or_else(|_| run("emacs", file))
            .or_else(|_| run("nano", file))
            .or_else(|_| run("ee", file))
    }?;
    Ok(())
}

fn run(handler: &str, arg: &str) -> Result<(), io::Error> {
    process::Command::new(handler).arg(arg).status()?;
    Ok(())
}

pub fn cli() -> Command {
    Command::new("hpk")
        .about("A package manager for HitchHiker Linux")
        .author("Nathan Fisher")
        .version(env!("CARGO_PKG_VERSION"))
        .propagate_version(true)
        .arg_required_else_help(true)
        .subcommands([
            create(),
            init(),
            search(),
            info(),
            install(),
            remove(),
            upgrade(),
        ])
}

pub fn create() -> Command {
    Command::new("create")
        .about("Create a new package from a directory of files")
        .args([
            Arg::new("directory")
                .help("the directory where the files are located")
                .value_hint(ValueHint::DirPath)
                .required(true)
                .num_args(1),
            Arg::new("output")
                .help("the location to save the package archive and package.ron [default: current working directory]")
                .short('o')
                .long("output")
                .value_name("directory")
                .value_hint(ValueHint::DirPath)
                .num_args(1),
            Arg::new("specs")
                .help("path to the package specs file in `ron` format")
                .short('s')
                .long("specs")
                .value_hint(ValueHint::FilePath)
                .conflicts_with_all(["name", "package-version", "release", "dependencies"])
                .num_args(1),
            Arg::new("name")
                .help("package name")
                .short('n')
                .long("name")
                .num_args(1)
                .required_unless_present("specs"),
            Arg::new("package-version")
                .help("package version")
                .short('v')
                .long("package-version")
                .num_args(1)
                .required_unless_present("specs"),
            Arg::new("release")
                .help("release number")
                .short('r')
                .long("release")
                .default_value("1")
                .value_parser(value_parser!(u8)),
            Arg::new("dependencies")
                .help("a comma separated list of dependencies")
                .short('d')
                .long("dependencies")
                .value_delimiter(',')
                .num_args(1..),
        ])
}

pub fn init() -> Command {
    Command::new("init")
        .about("Initialize a package `specs.ron` file in the current directory")
        .args([
            Arg::new("name")
                .help("package name")
                .short('n')
                .long("name")
                .num_args(1),
            Arg::new("package-version")
                .help("package version")
                .short('v')
                .long("package-version")
                .num_args(1),
            Arg::new("release")
                .help("release number")
                .short('r')
                .long("release")
                .default_value("1")
                .value_parser(value_parser!(usize)),
            Arg::new("dependencies")
                .help("a comma separated list of dependencies")
                .short('d')
                .long("dependencies")
                .value_delimiter(',')
                .num_args(1..),
            Arg::new("edit")
                .help("open specs file in an editor")
                .short('e')
                .long("edit")
                .action(ArgAction::SetTrue),
        ])
}

pub fn search() -> Command {
    Command::new("search")
        .about("Search for packages")
        .visible_alias("se")
        .args([
            Arg::new("installed")
                .help("only list installed packages")
                .short('i')
                .long("installed")
                .action(ArgAction::SetTrue),
            Arg::new("remote")
                .help("do not list installed packages")
                .short('r')
                .long("remote")
                .conflicts_with("installed")
                .action(ArgAction::SetTrue),
            Arg::new("names")
                .help("only search in package names, not descriptions")
                .short('n')
                .long("names")
                .action(ArgAction::SetTrue),
            Arg::new("verbose")
                .help("display long descriptions for results")
                .short('v')
                .long("verbose")
                .action(ArgAction::SetTrue),
            Arg::new("query").use_value_delimiter(false).required(true),
        ])
}

pub fn info() -> Command {
    Command::new("info")
        .about("Display information about a specific package")
        .arg(Arg::new("name").required(true))
}

pub fn install() -> Command {
    Command::new("install")
        .about("Install packages")
        .visible_aliases(["in", "add"])
        .args([
            Arg::new("package")
                .required_unless_present("local")
                .conflicts_with("local")
                .num_args(1..),
            Arg::new("local")
                .help("install a package from the filesystem")
                .short('l')
                .long("local")
                .value_name("package")
                .value_hint(ValueHint::FilePath)
                .num_args(1..),
            Arg::new("root")
                .help("install packages into a different root")
                .short('r')
                .long("root")
                .default_value("/")
                .value_hint(ValueHint::DirPath)
                .num_args(1),
        ])
}

pub fn remove() -> Command {
    Command::new("remove")
        .about("Remove packages")
        .visible_aliases(["rm", "del"])
        .args([
            Arg::new("package").required(true).num_args(1..),
            Arg::new("root")
                .help("remove packages from a different root")
                .short('r')
                .long("root")
                .value_hint(ValueHint::DirPath)
                .num_args(1),
        ])
}

pub fn upgrade() -> Command {
    Command::new("upgrade")
        .about("Upgrade packages")
        .visible_aliases(["update", "up", "sync"])
}
