use std::cmp::Ordering;

use clap_lex::OsStrExt as _;

use crate::builder::OsStr;
use crate::builder::ValueRange;
use crate::mkeymap::KeyType;
use crate::util::FlatSet;
use crate::util::Id;
use crate::ArgAction;
use crate::INTERNAL_ERROR_MSG;
use crate::{Arg, Command, ValueHint};

pub(crate) fn assert_app(cmd: &Command) {
    debug!("Command::_debug_asserts");

    let mut short_flags = vec![];
    let mut long_flags = vec![];

    // Invalid version flag settings
    if cmd.get_version().is_none() && cmd.get_long_version().is_none() {
        // PropagateVersion is meaningless if there is no version
        assert!(
            !cmd.is_propagate_version_set(),
            "Command {}: No version information via Command::version or Command::long_version to propagate",
            cmd.get_name(),
        );

        // Used `Command::mut_arg("version", ..) but did not provide any version information to display
        let version_needed = cmd
            .get_arguments()
            .filter(|x| matches!(x.get_action(), ArgAction::Version))
            .map(|x| x.get_id())
            .collect::<Vec<_>>();

        assert_eq!(version_needed, Vec::<&str>::new(), "Command {}: `ArgAction::Version` used without providing Command::version or Command::long_version"
            ,cmd.get_name()
        );
    }

    for sc in cmd.get_subcommands() {
        if let Some(s) = sc.get_short_flag().as_ref() {
            short_flags.push(Flag::Command(format!("-{s}"), sc.get_name()));
        }

        for short_alias in sc.get_all_short_flag_aliases() {
            short_flags.push(Flag::Command(format!("-{short_alias}"), sc.get_name()));
        }

        if let Some(l) = sc.get_long_flag().as_ref() {
            assert!(!l.starts_with('-'), "Command {}: long_flag {:?} must not start with a `-`, that will be handled by the parser", sc.get_name(), l);
            long_flags.push(Flag::Command(format!("--{l}"), sc.get_name()));
        }

        for long_alias in sc.get_all_long_flag_aliases() {
            long_flags.push(Flag::Command(format!("--{long_alias}"), sc.get_name()));
        }
    }

    for arg in cmd.get_arguments() {
        assert_arg(arg);

        assert!(
            !cmd.is_multicall_set(),
            "Command {}: Arguments like {} cannot be set on a multicall command",
            cmd.get_name(),
            arg.get_id()
        );

        if let Some(s) = arg.get_short() {
            short_flags.push(Flag::Arg(format!("-{s}"), arg.get_id().as_str()));
        }

        for (short_alias, _) in &arg.short_aliases {
            short_flags.push(Flag::Arg(format!("-{short_alias}"), arg.get_id().as_str()));
        }

        if let Some(l) = arg.get_long() {
            assert!(!l.starts_with('-'), "Argument {}: long {:?} must not start with a `-`, that will be handled by the parser", arg.get_id(), l);
            long_flags.push(Flag::Arg(format!("--{l}"), arg.get_id().as_str()));
        }

        for (long_alias, _) in &arg.aliases {
            long_flags.push(Flag::Arg(format!("--{long_alias}"), arg.get_id().as_str()));
        }

        // Name conflicts
        if let Some((first, second)) = cmd.two_args_of(|x| x.get_id() == arg.get_id()) {
            panic!(
            "Command {}: Argument names must be unique, but '{}' is in use by more than one argument or group{}",
            cmd.get_name(),
            arg.get_id(),
            duplicate_tip(cmd, first, second),
        );
        }

        // Long conflicts
        if let Some(l) = arg.get_long() {
            if let Some((first, second)) = cmd.two_args_of(|x| x.get_long() == Some(l)) {
                panic!(
                    "Command {}: Long option names must be unique for each argument, \
                            but '--{}' is in use by both '{}' and '{}'{}",
                    cmd.get_name(),
                    l,
                    first.get_id(),
                    second.get_id(),
                    duplicate_tip(cmd, first, second)
                )
            }
        }

        // Short conflicts
        if let Some(s) = arg.get_short() {
            if let Some((first, second)) = cmd.two_args_of(|x| x.get_short() == Some(s)) {
                panic!(
                    "Command {}: Short option names must be unique for each argument, \
                            but '-{}' is in use by both '{}' and '{}'{}",
                    cmd.get_name(),
                    s,
                    first.get_id(),
                    second.get_id(),
                    duplicate_tip(cmd, first, second),
                )
            }
        }

        // Index conflicts
        if let Some(idx) = arg.index {
            if let Some((first, second)) =
                cmd.two_args_of(|x| x.is_positional() && x.get_index() == Some(idx))
            {
                panic!(
                    "Command {}: Argument '{}' has the same index as '{}' \
                    and they are both positional arguments\n\n\t \
                    Use `Arg::num_args(1..)` to allow one \
                    positional argument to take multiple values",
                    cmd.get_name(),
                    first.get_id(),
                    second.get_id()
                )
            }
        }

        // requires, r_if, r_unless
        for req in &arg.requires {
            assert!(
                cmd.id_exists(&req.1),
                "Command {}: Argument or group '{}' specified in 'requires*' for '{}' does not exist",
                cmd.get_name(),
                req.1,
                arg.get_id(),
            );
        }

        for req in &arg.r_ifs {
            assert!(
                !arg.is_required_set(),
                "Argument {}: `required` conflicts with `required_if_eq*`",
                arg.get_id()
            );
            assert!(
                cmd.id_exists(&req.0),
                "Command {}: Argument or group '{}' specified in 'required_if_eq*' for '{}' does not exist",
                    cmd.get_name(),
                req.0,
                arg.get_id()
            );
        }

        for req in &arg.r_ifs_all {
            assert!(
                !arg.is_required_set(),
                "Argument {}: `required` conflicts with `required_if_eq_all`",
                arg.get_id()
            );
            assert!(
                cmd.id_exists(&req.0),
                "Command {}: Argument or group '{}' specified in 'required_if_eq_all' for '{}' does not exist",
                    cmd.get_name(),
                req.0,
                arg.get_id()
            );
        }

        for req in &arg.r_unless {
            assert!(
                !arg.is_required_set(),
                "Argument {}: `required` conflicts with `required_unless*`",
                arg.get_id()
            );
            assert!(
                cmd.id_exists(req),
                "Command {}: Argument or group '{}' specified in 'required_unless*' for '{}' does not exist",
                    cmd.get_name(),
                req,
                arg.get_id(),
            );
        }

        for req in &arg.r_unless_all {
            assert!(
                !arg.is_required_set(),
                "Argument {}: `required` conflicts with `required_unless*`",
                arg.get_id()
            );
            assert!(
                cmd.id_exists(req),
                "Command {}: Argument or group '{}' specified in 'required_unless*' for '{}' does not exist",
                    cmd.get_name(),
                req,
                arg.get_id(),
            );
        }

        // blacklist
        for req in &arg.blacklist {
            assert!(
                cmd.id_exists(req),
                "Command {}: Argument or group '{}' specified in 'conflicts_with*' for '{}' does not exist",
                    cmd.get_name(),
                req,
                arg.get_id(),
            );
        }

        // overrides
        for req in &arg.overrides {
            assert!(
                cmd.id_exists(req),
                "Command {}: Argument or group '{}' specified in 'overrides_with*' for '{}' does not exist",
                    cmd.get_name(),
                req,
                arg.get_id(),
            );
        }

        if arg.is_last_set() {
            assert!(
                arg.get_long().is_none(),
                "Command {}: Flags or Options cannot have last(true) set. '{}' has both a long and last(true) set.",
                    cmd.get_name(),
                arg.get_id()
            );
            assert!(
                arg.get_short().is_none(),
                "Command {}: Flags or Options cannot have last(true) set. '{}' has both a short and last(true) set.",
                    cmd.get_name(),
                arg.get_id()
            );
        }

        assert!(
            !(arg.is_required_set() && arg.is_global_set()),
            "Command {}: Global arguments cannot be required.\n\n\t'{}' is marked as both global and required",
                    cmd.get_name(),
            arg.get_id()
        );

        if arg.get_value_hint() == ValueHint::CommandWithArguments {
            assert!(
                arg.is_positional(),
                "Command {}: Argument '{}' has hint CommandWithArguments and must be positional.",
                cmd.get_name(),
                arg.get_id()
            );

            assert!(
                arg.is_trailing_var_arg_set() || arg.is_last_set(),
                "Command {}: Positional argument '{}' has hint CommandWithArguments, so Command must have `trailing_var_arg(true)` or `last(true)` set.",
                    cmd.get_name(),
                arg.get_id()
            );
        }
    }

    for group in cmd.get_groups() {
        // Name conflicts
        assert!(
            cmd.get_groups().filter(|x| x.id == group.id).count() < 2,
            "Command {}: Argument group name must be unique\n\n\t'{}' is already in use",
            cmd.get_name(),
            group.get_id(),
        );

        // Groups should not have naming conflicts with Args
        assert!(
            !cmd.get_arguments().any(|x| x.get_id() == group.get_id()),
            "Command {}: Argument group name '{}' must not conflict with argument name",
            cmd.get_name(),
            group.get_id(),
        );

        for arg in &group.args {
            // Args listed inside groups should exist
            assert!(
                cmd.get_arguments().any(|x| x.get_id() == arg),
                "Command {}: Argument group '{}' contains non-existent argument '{}'",
                cmd.get_name(),
                group.get_id(),
                arg
            );
        }
    }

    // Conflicts between flags and subcommands

    long_flags.sort_unstable();
    short_flags.sort_unstable();

    detect_duplicate_flags(&long_flags, "long");
    detect_duplicate_flags(&short_flags, "short");

    let mut subs = FlatSet::new();
    for sc in cmd.get_subcommands() {
        assert!(
            subs.insert(sc.get_name()),
            "Command {}: command name `{}` is duplicated",
            cmd.get_name(),
            sc.get_name()
        );
        for alias in sc.get_all_aliases() {
            assert!(
                subs.insert(alias),
                "Command {}: command `{}` alias `{}` is duplicated",
                cmd.get_name(),
                sc.get_name(),
                alias
            );
        }
    }

    _verify_positionals(cmd);

    #[cfg(feature = "help")]
    if let Some(help_template) = cmd.get_help_template() {
        assert!(
            !help_template.to_string().contains("{flags}"),
            "Command {}: {}",
                    cmd.get_name(),
            "`{flags}` template variable was removed in clap3, they are now included in `{options}`",
        );
        assert!(
            !help_template.to_string().contains("{unified}"),
            "Command {}: {}",
            cmd.get_name(),
            "`{unified}` template variable was removed in clap3, use `{options}` instead"
        );
        #[cfg(feature = "unstable-v5")]
        assert!(
            !help_template.to_string().contains("{bin}"),
            "Command {}: {}",
            cmd.get_name(),
            "`{bin}` template variable was removed in clap5, use `{name}` instead"
        )
    }

    cmd._panic_on_missing_help(cmd.is_help_expected_set());
    assert_app_flags(cmd);
}

fn duplicate_tip(cmd: &Command, first: &Arg, second: &Arg) -> &'static str {
    if !cmd.is_disable_help_flag_set()
        && (first.get_id() == Id::HELP || second.get_id() == Id::HELP)
    {
        " (call `cmd.disable_help_flag(true)` to remove the auto-generated `--help`)"
    } else if !cmd.is_disable_version_flag_set()
        && (first.get_id() == Id::VERSION || second.get_id() == Id::VERSION)
    {
        " (call `cmd.disable_version_flag(true)` to remove the auto-generated `--version`)"
    } else {
        ""
    }
}

#[derive(Eq)]
enum Flag<'a> {
    Command(String, &'a str),
    Arg(String, &'a str),
}

impl PartialEq for Flag<'_> {
    fn eq(&self, other: &Flag) -> bool {
        self.cmp(other) == Ordering::Equal
    }
}

impl PartialOrd for Flag<'_> {
    fn partial_cmp(&self, other: &Flag) -> Option<Ordering> {
        use Flag::*;

        match (self, other) {
            (Command(s1, _), Command(s2, _))
            | (Arg(s1, _), Arg(s2, _))
            | (Command(s1, _), Arg(s2, _))
            | (Arg(s1, _), Command(s2, _)) => {
                if s1 == s2 {
                    Some(Ordering::Equal)
                } else {
                    s1.partial_cmp(s2)
                }
            }
        }
    }
}

impl Ord for Flag<'_> {
    fn cmp(&self, other: &Self) -> Ordering {
        self.partial_cmp(other).unwrap()
    }
}

fn detect_duplicate_flags(flags: &[Flag], short_or_long: &str) {
    use Flag::*;

    for (one, two) in find_duplicates(flags) {
        match (one, two) {
            (Command(flag, one), Command(_, another)) if one != another => panic!(
                "the '{flag}' {short_or_long} flag is specified for both '{one}' and '{another}' subcommands"
            ),

            (Arg(flag, one), Arg(_, another)) if one != another => panic!(
                "{short_or_long} option names must be unique, but '{flag}' is in use by both '{one}' and '{another}'"
            ),

            (Arg(flag, arg), Command(_, sub)) | (Command(flag, sub), Arg(_, arg)) => panic!(
                "the '{flag}' {short_or_long} flag for the '{arg}' argument conflicts with the short flag \
                     for '{sub}' subcommand"
            ),

            _ => {}
        }
    }
}

/// Find duplicates in a sorted array.
///
/// The algorithm is simple: the array is sorted, duplicates
/// must be placed next to each other, we can check only adjacent elements.
fn find_duplicates<T: PartialEq>(slice: &[T]) -> impl Iterator<Item = (&T, &T)> {
    slice.windows(2).filter_map(|w| {
        if w[0] == w[1] {
            Some((&w[0], &w[1]))
        } else {
            None
        }
    })
}

fn assert_app_flags(cmd: &Command) {
    macro_rules! checker {
        ($a:ident requires $($b:ident)|+) => {
            if cmd.$a() {
                let mut s = String::new();

                $(
                    if !cmd.$b() {
                        use std::fmt::Write;
                        write!(&mut s, "  AppSettings::{} is required when AppSettings::{} is set.\n", std::stringify!($b), std::stringify!($a)).unwrap();
                    }
                )+

                if !s.is_empty() {
                    panic!("{}", s)
                }
            }
        };
        ($a:ident conflicts $($b:ident)|+) => {
            if cmd.$a() {
                let mut s = String::new();

                $(
                    if cmd.$b() {
                        use std::fmt::Write;
                        write!(&mut s, "  AppSettings::{} conflicts with AppSettings::{}.\n", std::stringify!($b), std::stringify!($a)).unwrap();
                    }
                )+

                if !s.is_empty() {
                    panic!("{}\n{}", cmd.get_name(), s)
                }
            }
        };
    }

    checker!(is_multicall_set conflicts is_no_binary_name_set);
}

#[cfg(debug_assertions)]
fn _verify_positionals(cmd: &Command) -> bool {
    debug!("Command::_verify_positionals");
    // Because you must wait until all arguments have been supplied, this is the first chance
    // to make assertions on positional argument indexes
    //
    // First we verify that the index highest supplied index, is equal to the number of
    // positional arguments to verify there are no gaps (i.e. supplying an index of 1 and 3
    // but no 2)

    let highest_idx = cmd
        .get_keymap()
        .keys()
        .filter_map(|x| {
            if let KeyType::Position(n) = x {
                Some(*n)
            } else {
                None
            }
        })
        .max()
        .unwrap_or(0);

    let num_p = cmd.get_keymap().keys().filter(|x| x.is_position()).count();

    assert!(
        highest_idx == num_p,
        "Found positional argument whose index is {highest_idx} but there \
             are only {num_p} positional arguments defined",
    );

    for arg in cmd.get_arguments() {
        if arg.index.unwrap_or(0) == highest_idx {
            assert!(
                !arg.is_trailing_var_arg_set() || !arg.is_last_set(),
                "{}:{}: `Arg::trailing_var_arg` and `Arg::last` cannot be used together",
                cmd.get_name(),
                arg.get_id()
            );

            if arg.is_trailing_var_arg_set() {
                assert!(
                    arg.is_multiple(),
                    "{}:{}: `Arg::trailing_var_arg` must accept multiple values",
                    cmd.get_name(),
                    arg.get_id()
                );
            }
        } else {
            assert!(
                !arg.is_trailing_var_arg_set(),
                "{}:{}: `Arg::trailing_var_arg` can only apply to last positional",
                cmd.get_name(),
                arg.get_id()
            );
        }
    }

    // Next we verify that only the highest index has takes multiple arguments (if any)
    let only_highest = |a: &Arg| a.is_multiple() && (a.get_index().unwrap_or(0) != highest_idx);
    if cmd.get_positionals().any(only_highest) {
        // First we make sure if there is a positional that allows multiple values
        // the one before it (second to last) has one of these:
        //  * a value terminator
        //  * ArgSettings::Last
        //  * The last arg is Required

        // We can't pass the closure (it.next()) to the macro directly because each call to
        // find() (iterator, not macro) gets called repeatedly.
        let last = &cmd.get_keymap()[&KeyType::Position(highest_idx)];
        let second_to_last = &cmd.get_keymap()[&KeyType::Position(highest_idx - 1)];

        // Either the final positional is required
        // Or the second to last has a terminator or .last(true) set
        let ok = last.is_required_set()
            || (second_to_last.terminator.is_some() || second_to_last.is_last_set())
            || last.is_last_set();
        assert!(
            ok,
            "When using a positional argument with `.num_args(1..)` that is *not the \
                 last* positional argument, the last positional argument (i.e. the one \
                 with the highest index) *must* have .required(true) or .last(true) set."
        );

        // We make sure if the second to last is Multiple the last is ArgSettings::Last
        let ok = second_to_last.is_multiple() || last.is_last_set();
        assert!(
            ok,
            "Only the last positional argument, or second to last positional \
                 argument may be set to `.num_args(1..)`"
        );

        // Next we check how many have both Multiple and not a specific number of values set
        let count = cmd
            .get_positionals()
            .filter(|p| {
                p.is_multiple_values_set()
                    && !p.get_num_args().expect(INTERNAL_ERROR_MSG).is_fixed()
            })
            .count();
        let ok = count <= 1
            || (last.is_last_set()
                && last.is_multiple()
                && second_to_last.is_multiple()
                && count == 2);
        assert!(
            ok,
            "Only one positional argument with `.num_args(1..)` set is allowed per \
                 command, unless the second one also has .last(true) set"
        );
    }

    let mut found = false;

    if cmd.is_allow_missing_positional_set() {
        // Check that if a required positional argument is found, all positions with a lower
        // index are also required.
        let mut foundx2 = false;

        for p in cmd.get_positionals() {
            if foundx2 && !p.is_required_set() {
                assert!(
                    p.is_required_set(),
                    "Found non-required positional argument with a lower \
                         index than a required positional argument by two or more: {:?} \
                         index {:?}",
                    p.get_id(),
                    p.get_index()
                );
            } else if p.is_required_set() && !p.is_last_set() {
                // Args that .last(true) don't count since they can be required and have
                // positionals with a lower index that aren't required
                // Imagine: prog <req1> [opt1] -- <req2>
                // Both of these are valid invocations:
                //      $ prog r1 -- r2
                //      $ prog r1 o1 -- r2
                if found {
                    foundx2 = true;
                    continue;
                }
                found = true;
                continue;
            } else {
                found = false;
            }
        }
    } else {
        // Check that if a required positional argument is found, all positions with a lower
        // index are also required
        for p in (1..=num_p).rev().filter_map(|n| cmd.get_keymap().get(&n)) {
            if found {
                assert!(
                    p.is_required_set(),
                    "Found non-required positional argument with a lower \
                         index than a required positional argument: {:?} index {:?}",
                    p.get_id(),
                    p.get_index()
                );
            } else if p.is_required_set() && !p.is_last_set() {
                // Args that .last(true) don't count since they can be required and have
                // positionals with a lower index that aren't required
                // Imagine: prog <req1> [opt1] -- <req2>
                // Both of these are valid invocations:
                //      $ prog r1 -- r2
                //      $ prog r1 o1 -- r2
                found = true;
                continue;
            }
        }
    }
    assert!(
        cmd.get_positionals().filter(|p| p.is_last_set()).count() < 2,
        "Only one positional argument may have last(true) set. Found two."
    );
    if cmd
        .get_positionals()
        .any(|p| p.is_last_set() && p.is_required_set())
        && cmd.has_subcommands()
        && !cmd.is_subcommand_negates_reqs_set()
    {
        panic!(
            "Having a required positional argument with .last(true) set *and* child \
                 subcommands without setting SubcommandsNegateReqs isn't compatible."
        );
    }

    true
}

fn assert_arg(arg: &Arg) {
    debug!("Arg::_debug_asserts:{}", arg.get_id());

    // Self conflict
    // TODO: this check should be recursive
    assert!(
        !arg.blacklist.iter().any(|x| x == arg.get_id()),
        "Argument '{}' cannot conflict with itself",
        arg.get_id(),
    );

    assert_eq!(
        arg.get_action().takes_values(),
        arg.is_takes_value_set(),
        "Argument `{}`'s selected action {:?} contradicts `takes_value`",
        arg.get_id(),
        arg.get_action()
    );
    if let Some(action_type_id) = arg.get_action().value_type_id() {
        assert_eq!(
            action_type_id,
            arg.get_value_parser().type_id(),
            "Argument `{}`'s selected action {:?} contradicts `value_parser` ({:?})",
            arg.get_id(),
            arg.get_action(),
            arg.get_value_parser()
        );
    }

    if arg.get_value_hint() != ValueHint::Unknown {
        assert!(
            arg.is_takes_value_set(),
            "Argument '{}' has value hint but takes no value",
            arg.get_id()
        );

        if arg.get_value_hint() == ValueHint::CommandWithArguments {
            assert!(
                arg.is_multiple_values_set(),
                "Argument '{}' uses hint CommandWithArguments and must accept multiple values",
                arg.get_id()
            )
        }
    }

    if arg.index.is_some() {
        assert!(
            arg.is_positional(),
            "Argument '{}' is a positional argument and can't have short or long name versions",
            arg.get_id()
        );
        assert!(
            arg.is_takes_value_set(),
            "Argument '{}` is positional, it must take a value{}",
            arg.get_id(),
            if arg.get_id() == Id::HELP {
                " (`mut_arg` no longer works with implicit `--help`)"
            } else if arg.get_id() == Id::VERSION {
                " (`mut_arg` no longer works with implicit `--version`)"
            } else {
                ""
            }
        );
    }

    let num_vals = arg.get_num_args().expect(INTERNAL_ERROR_MSG);
    // This can be the cause of later asserts, so put this first
    if num_vals != ValueRange::EMPTY {
        // HACK: Don't check for flags to make the derive easier
        let num_val_names = arg.get_value_names().unwrap_or(&[]).len();
        if num_vals.max_values() < num_val_names {
            panic!(
                "Argument {}: Too many value names ({}) compared to `num_args` ({})",
                arg.get_id(),
                num_val_names,
                num_vals
            );
        }
    }

    assert_eq!(
        num_vals.takes_values(),
        arg.is_takes_value_set(),
        "Argument {}: mismatch between `num_args` ({}) and `takes_value`",
        arg.get_id(),
        num_vals,
    );
    assert_eq!(
        num_vals.is_multiple(),
        arg.is_multiple_values_set(),
        "Argument {}: mismatch between `num_args` ({}) and `multiple_values`",
        arg.get_id(),
        num_vals,
    );

    if 1 < num_vals.min_values() {
        assert!(
            !arg.is_require_equals_set(),
            "Argument {}: cannot accept more than 1 arg (num_args={}) with require_equals",
            arg.get_id(),
            num_vals
        );
    }

    if num_vals == ValueRange::SINGLE {
        assert!(
            !arg.is_multiple_values_set(),
            "Argument {}: mismatch between `num_args` and `multiple_values`",
            arg.get_id()
        );
    }

    assert_arg_flags(arg);

    assert_defaults(arg, "default_value", arg.default_vals.iter());
    assert_defaults(
        arg,
        "default_missing_value",
        arg.default_missing_vals.iter(),
    );
    assert_defaults(
        arg,
        "default_value_if",
        arg.default_vals_ifs
            .iter()
            .filter_map(|(_, _, default)| default.as_ref()),
    );
}

fn assert_arg_flags(arg: &Arg) {
    macro_rules! checker {
        ($a:ident requires $($b:ident)|+) => {
            if arg.$a() {
                let mut s = String::new();

                $(
                    if !arg.$b() {
                        use std::fmt::Write;
                        write!(&mut s, "  Arg::{} is required when Arg::{} is set.\n", std::stringify!($b), std::stringify!($a)).unwrap();
                    }
                )+

                if !s.is_empty() {
                    panic!("Argument {:?}\n{}", arg.get_id(), s)
                }
            }
        }
    }

    checker!(is_hide_possible_values_set requires is_takes_value_set);
    checker!(is_allow_hyphen_values_set requires is_takes_value_set);
    checker!(is_allow_negative_numbers_set requires is_takes_value_set);
    checker!(is_require_equals_set requires is_takes_value_set);
    checker!(is_last_set requires is_takes_value_set);
    checker!(is_hide_default_value_set requires is_takes_value_set);
    checker!(is_multiple_values_set requires is_takes_value_set);
    checker!(is_ignore_case_set requires is_takes_value_set);
}

fn assert_defaults<'d>(
    arg: &Arg,
    field: &'static str,
    defaults: impl IntoIterator<Item = &'d OsStr>,
) {
    for default_os in defaults {
        let value_parser = arg.get_value_parser();
        let assert_cmd = Command::new("assert");
        if let Some(val_delim) = arg.get_value_delimiter() {
            let mut val_delim_buffer = [0; 4];
            let val_delim = val_delim.encode_utf8(&mut val_delim_buffer);
            for part in default_os.split(val_delim) {
                if let Err(err) = value_parser.parse_ref(&assert_cmd, Some(arg), part) {
                    panic!(
                        "Argument `{}`'s {}={:?} failed validation: {}",
                        arg.get_id(),
                        field,
                        part.to_string_lossy(),
                        err
                    );
                }
            }
        } else if let Err(err) = value_parser.parse_ref(&assert_cmd, Some(arg), default_os) {
            panic!(
                "Argument `{}`'s {}={:?} failed validation: {}",
                arg.get_id(),
                field,
                default_os,
                err
            );
        }
    }
}
