use crate::{Necessist, Warning, framework};
use clap::{ArgAction, Parser, ValueEnum, crate_version};
use std::path::PathBuf;

#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Parser)]
#[clap(version = crate_version!())]
#[remain::sorted]
pub struct Opts<Identifier: Clone + Send + Sync + ValueEnum + 'static> {
    #[clap(
        long,
        action = ArgAction::Append,
        hide_possible_values = true,
        value_name = "WARNING",
        help = "Silence <WARNING>; `--allow all` silences all warnings"
    )]
    allow: Vec<Warning>,
    #[clap(
        long,
        help = "Create a default necessist.toml file in the project's root directory"
    )]
    default_config: bool,
    #[clap(
        long,
        action = ArgAction::Append,
        hide_possible_values = true,
        value_name = "WARNING",
        help = "Treat <WARNING> as an error; `--deny all` treats all warnings as errors"
    )]
    deny: Vec<Warning>,
    #[clap(long, help = "Dump sqlite database contents to the console")]
    dump: bool,
    #[clap(long, help = "Dump number of removal candidates in each file and exit")]
    dump_candidate_counts: bool,
    #[clap(long, help = "Dump removal candidates and exit (for debugging)")]
    dump_candidates: bool,
    #[clap(long, help = "Assume testing framework is <FRAMEWORK>")]
    framework: Option<framework::Auto<Identifier>>,
    #[clap(long, help = "Do not output line or column information (experimental)")]
    no_lines_or_columns: bool,
    #[clap(long, hide = true)]
    no_local_functions: bool,
    #[clap(long, help = "Do not output to an sqlite database")]
    no_sqlite: bool,
    #[clap(long, help = "Do not output to the console")]
    quiet: bool,
    #[clap(long, help = "Discard sqlite database contents")]
    reset: bool,
    #[clap(long, help = "Resume from the sqlite database")]
    resume: bool,
    #[clap(long, help = "Root directory of the project under test")]
    root: Option<String>,
    #[clap(
        long,
        help = "Maximum number of seconds to run any test; 60 is the default, 0 means no timeout"
    )]
    timeout: Option<u64>,
    #[clap(long, help = "Show test outcomes besides `passed`")]
    verbose: bool,
    #[clap(
        value_name = "TEST_FILES_OR_DIRS",
        help = "Test files or directories to mutilate; if `--root <ROOT>` is passed, relative \
                paths are interpreted relative to <ROOT>"
    )]
    zsource_files: Vec<String>,
    #[clap(
        last = true,
        name = "ARGS",
        help = "Additional arguments to pass to each test command"
    )]
    zzargs: Vec<String>,
}

impl<Identifier: Clone + Send + Sync + ValueEnum> From<Opts<Identifier>>
    for (Necessist, framework::Auto<Identifier>)
{
    fn from(opts: Opts<Identifier>) -> Self {
        let Opts {
            allow,
            default_config,
            deny,
            dump,
            dump_candidate_counts,
            dump_candidates,
            framework,
            no_lines_or_columns,
            no_local_functions,
            no_sqlite,
            quiet,
            reset,
            resume,
            root,
            timeout,
            verbose,
            zsource_files,
            zzargs,
        } = opts;
        let framework = framework.unwrap_or_default();
        let root = root.map(PathBuf::from);
        let source_files = zsource_files.iter().map(PathBuf::from).collect::<Vec<_>>();
        let args = zzargs;
        (
            Necessist {
                allow,
                default_config,
                deny,
                dump,
                dump_candidate_counts,
                dump_candidates,
                no_lines_or_columns,
                no_local_functions,
                no_sqlite,
                quiet,
                reset,
                resume,
                root,
                timeout,
                verbose,
                source_files,
                args,
            },
            framework,
        )
    }
}
