//! Combines the values we care about from the clap CLI and the modifiers module into something
//! serializable by serde.
use std::collections::HashMap;

use anyhow::Context;
use log::error;
use serde::Serialize;

use crate::modifiers;

#[derive(Debug, Serialize)]
pub struct Command<'a> {
    subcommands: Vec<Subcommand<'a>>,
}

impl Command<'_> {
    pub fn new(command: &clap::Command) -> Command<'_> {
        let subcommand_map: HashMap<&str, &clap::Command> = command
            .get_subcommands()
            .map(|s| (s.get_name(), s))
            .collect();

        Command {
            // this resulting vector contains the subcommands that are found in both
            // command.get_subcommands() and modifiers::subcommand_modifiers() in the order defined
            // by subcommand_modifiers()
            subcommands: modifiers::subcommand_modifiers()
                .iter()
                .filter_map(|modifier| match subcommand_map.get(modifier.command) {
                    Some(clap_command) => Some(Subcommand::new(clap_command, modifier)),
                    None => {
                        error!(
                            "failed to find clap command for subcommand {}",
                            modifier.command
                        );
                        None
                    }
                })
                .collect(),
        }
    }
}

#[derive(Debug, Serialize)]
struct Argument<'a> {
    advanced: bool,
    flag: String,
    label: &'a str,
    takes_values: bool,
}

#[derive(Debug, Serialize)]
struct Subcommand<'a> {
    arguments: Vec<Argument<'a>>,
    command: &'a str,
    label: &'a str,
}

impl Argument<'_> {
    fn try_new<'a>(
        argument: &'a clap::Arg,
        modifier: &modifiers::ArgumentModifier<'static>,
    ) -> anyhow::Result<Argument<'a>> {
        let partial_flag = argument.get_long().with_context(|| {
            format!(
                "Missing long form command line flag for {}",
                argument.get_id().as_str(),
            )
        })?;
        Ok(Argument {
            advanced: modifier.advanced,
            flag: format!("--{}", partial_flag),
            label: modifier.gui_label,
            takes_values: argument.get_action().takes_values(),
        })
    }
}

impl Subcommand<'_> {
    fn new<'a>(
        command: &'a clap::Command,
        modifier: &modifiers::SubcommandModifier<'static>,
    ) -> Subcommand<'a> {
        let argument_map: HashMap<&str, &clap::Arg> = command
            .get_arguments()
            .map(|a| (a.get_id().as_str(), a))
            .collect();

        Subcommand {
            // this resulting vector contains the arguments that are found in both
            // command.get_arguments() and modifier.arg_modifiers in the order defined by by
            // arg_modifiers
            arguments: modifier
                .arg_modifiers
                .iter()
                .filter_map(|arg_modifier| {
                    let Some(arg) = argument_map.get(arg_modifier.clap_id) else {
                        error!(
                            "failed to find clap argument with id {}",
                            arg_modifier.clap_id
                        );
                        return None;
                    };
                    match Argument::try_new(arg, arg_modifier) {
                        Ok(modified_arg) => Some(modified_arg),
                        Err(err) => {
                            error!("failed to create modified argument: {:?}", err);
                            None
                        }
                    }
                })
                .collect(),
            command: modifier.command,
            label: modifier.gui_label,
        }
    }
}
