//! Error reporting
#![allow(deprecated)]

// Std
use std::{
    borrow::Cow,
    convert::From,
    error,
    fmt::{self, Debug, Display, Formatter},
    io::{self, BufRead},
    result::Result as StdResult,
};

// Internal
use crate::{
    build::Arg,
    output::fmt::Colorizer,
    output::fmt::Stream,
    parse::features::suggestions,
    util::{color::ColorChoice, safe_exit, SUCCESS_CODE, USAGE_CODE},
    AppSettings, Command,
};

mod context;
mod kind;

pub use context::ContextKind;
pub use context::ContextValue;
pub use kind::ErrorKind;

/// Short hand for [`Result`] type
///
/// [`Result`]: std::result::Result
pub type Result<T, E = Error> = StdResult<T, E>;

/// Command Line Argument Parser Error
///
/// See [`Command::error`] to create an error.
///
/// [`Command::error`]: crate::Command::error
#[derive(Debug)]
pub struct Error {
    inner: Box<ErrorInner>,
    /// Deprecated, replaced with [`Error::kind()`]
    #[deprecated(since = "3.1.0", note = "Replaced with `Error::kind()`")]
    pub kind: ErrorKind,
    /// Deprecated, replaced with [`Error::context()`]
    #[deprecated(since = "3.1.0", note = "Replaced with `Error::context()`")]
    pub info: Vec<String>,
}

#[derive(Debug)]
struct ErrorInner {
    kind: ErrorKind,
    context: Vec<(ContextKind, ContextValue)>,
    message: Option<Message>,
    source: Option<Box<dyn error::Error + Send + Sync>>,
    help_flag: Option<&'static str>,
    color_when: ColorChoice,
    wait_on_exit: bool,
    backtrace: Option<Backtrace>,
}

impl Error {
    /// Create an unformatted error
    ///
    /// This is for you need to pass the error up to
    /// a place that has access to the `Command` at which point you can call [`Error::format`].
    ///
    /// Prefer [`Command::error`] for generating errors.
    ///
    /// [`Command::error`]: crate::Command::error
    pub fn raw(kind: ErrorKind, message: impl std::fmt::Display) -> Self {
        Self::new(kind).set_message(message.to_string())
    }

    /// Format the existing message with the Command's context
    #[must_use]
    pub fn format(mut self, cmd: &mut Command) -> Self {
        cmd._build_self();
        let usage = cmd.render_usage();
        if let Some(message) = self.inner.message.as_mut() {
            message.format(cmd, usage);
        }
        self.with_cmd(cmd)
    }

    /// Type of error for programmatic processing
    pub fn kind(&self) -> ErrorKind {
        self.inner.kind
    }

    /// Additional information to further qualify the error
    pub fn context(&self) -> impl Iterator<Item = (ContextKind, &ContextValue)> {
        self.inner.context.iter().map(|(k, v)| (*k, v))
    }

    /// Should the message be written to `stdout` or not?
    #[inline]
    pub fn use_stderr(&self) -> bool {
        self.stream() == Stream::Stderr
    }

    pub(crate) fn stream(&self) -> Stream {
        match self.kind() {
            ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => Stream::Stdout,
            _ => Stream::Stderr,
        }
    }

    /// Prints the error and exits.
    ///
    /// Depending on the error kind, this either prints to `stderr` and exits with a status of `2`
    /// or prints to `stdout` and exits with a status of `0`.
    pub fn exit(&self) -> ! {
        if self.use_stderr() {
            // Swallow broken pipe errors
            let _ = self.print();

            if self.inner.wait_on_exit {
                wlnerr!("\nPress [ENTER] / [RETURN] to continue...");
                let mut s = String::new();
                let i = io::stdin();
                i.lock().read_line(&mut s).unwrap();
            }

            safe_exit(USAGE_CODE);
        }

        // Swallow broken pipe errors
        let _ = self.print();
        safe_exit(SUCCESS_CODE)
    }

    /// Prints formatted and colored error to `stdout` or `stderr` according to its error kind
    ///
    /// # Example
    /// ```no_run
    /// use clap::Command;
    ///
    /// match Command::new("Command").try_get_matches() {
    ///     Ok(matches) => {
    ///         // do_something
    ///     },
    ///     Err(err) => {
    ///         err.print().expect("Error writing Error");
    ///         // do_something
    ///     },
    /// };
    /// ```
    pub fn print(&self) -> io::Result<()> {
        self.formatted().print()
    }

    /// Deprecated, replaced with [`Command::error`]
    ///
    /// [`Command::error`]: crate::Command::error
    #[deprecated(since = "3.0.0", note = "Replaced with `Command::error`")]
    #[doc(hidden)]
    pub fn with_description(description: String, kind: ErrorKind) -> Self {
        Error::raw(kind, description)
    }

    fn new(kind: ErrorKind) -> Self {
        Self {
            inner: Box::new(ErrorInner {
                kind,
                context: Vec::new(),
                message: None,
                source: None,
                help_flag: None,
                color_when: ColorChoice::Never,
                wait_on_exit: false,
                backtrace: Backtrace::new(),
            }),
            kind,
            info: vec![],
        }
    }

    #[inline(never)]
    fn for_app(kind: ErrorKind, cmd: &Command, colorizer: Colorizer, info: Vec<String>) -> Self {
        Self::new(kind)
            .set_message(colorizer)
            .with_cmd(cmd)
            .set_info(info)
    }

    pub(crate) fn with_cmd(self, cmd: &Command) -> Self {
        self.set_wait_on_exit(cmd.is_set(AppSettings::WaitOnError))
            .set_color(cmd.get_color())
            .set_help_flag(get_help_flag(cmd))
    }

    pub(crate) fn set_message(mut self, message: impl Into<Message>) -> Self {
        self.inner.message = Some(message.into());
        self
    }

    pub(crate) fn set_info(mut self, info: Vec<String>) -> Self {
        self.info = info;
        self
    }

    pub(crate) fn set_source(mut self, source: Box<dyn error::Error + Send + Sync>) -> Self {
        self.inner.source = Some(source);
        self
    }

    pub(crate) fn set_color(mut self, color_when: ColorChoice) -> Self {
        self.inner.color_when = color_when;
        self
    }

    pub(crate) fn set_help_flag(mut self, help_flag: Option<&'static str>) -> Self {
        self.inner.help_flag = help_flag;
        self
    }

    pub(crate) fn set_wait_on_exit(mut self, yes: bool) -> Self {
        self.inner.wait_on_exit = yes;
        self
    }

    /// Does not verify if `ContextKind` is already present
    #[inline(never)]
    pub(crate) fn insert_context_unchecked(
        mut self,
        kind: ContextKind,
        value: ContextValue,
    ) -> Self {
        self.inner.context.push((kind, value));
        self
    }

    /// Does not verify if `ContextKind` is already present
    #[inline(never)]
    pub(crate) fn extend_context_unchecked<const N: usize>(
        mut self,
        context: [(ContextKind, ContextValue); N],
    ) -> Self {
        self.inner.context.extend(context);
        self
    }

    #[inline(never)]
    fn get_context(&self, kind: ContextKind) -> Option<&ContextValue> {
        self.inner
            .context
            .iter()
            .find_map(|(k, v)| (*k == kind).then(|| v))
    }

    pub(crate) fn display_help(cmd: &Command, colorizer: Colorizer) -> Self {
        Self::for_app(ErrorKind::DisplayHelp, cmd, colorizer, vec![])
    }

    pub(crate) fn display_help_error(cmd: &Command, colorizer: Colorizer) -> Self {
        Self::for_app(
            ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand,
            cmd,
            colorizer,
            vec![],
        )
    }

    pub(crate) fn display_version(cmd: &Command, colorizer: Colorizer) -> Self {
        Self::for_app(ErrorKind::DisplayVersion, cmd, colorizer, vec![])
    }

    pub(crate) fn argument_conflict(
        cmd: &Command,
        arg: &Arg,
        mut others: Vec<String>,
        usage: String,
    ) -> Self {
        let info = others.clone();
        let others = match others.len() {
            0 => ContextValue::None,
            1 => ContextValue::String(others.pop().unwrap()),
            _ => ContextValue::Strings(others),
        };
        Self::new(ErrorKind::ArgumentConflict)
            .with_cmd(cmd)
            .set_info(info)
            .extend_context_unchecked([
                (
                    ContextKind::InvalidArg,
                    ContextValue::String(arg.to_string()),
                ),
                (ContextKind::PriorArg, others),
                (ContextKind::Usage, ContextValue::String(usage)),
            ])
    }

    pub(crate) fn empty_value(cmd: &Command, good_vals: &[&str], arg: &Arg, usage: String) -> Self {
        let info = vec![arg.to_string()];
        let mut err = Self::new(ErrorKind::EmptyValue)
            .with_cmd(cmd)
            .set_info(info)
            .extend_context_unchecked([
                (
                    ContextKind::InvalidArg,
                    ContextValue::String(arg.to_string()),
                ),
                (ContextKind::Usage, ContextValue::String(usage)),
            ]);
        if !good_vals.is_empty() {
            err = err.insert_context_unchecked(
                ContextKind::ValidValue,
                ContextValue::Strings(good_vals.iter().map(|s| (*s).to_owned()).collect()),
            );
        }
        err
    }

    pub(crate) fn no_equals(cmd: &Command, arg: String, usage: String) -> Self {
        let info = vec![arg.to_string()];
        Self::new(ErrorKind::NoEquals)
            .with_cmd(cmd)
            .set_info(info)
            .extend_context_unchecked([
                (ContextKind::InvalidArg, ContextValue::String(arg)),
                (ContextKind::Usage, ContextValue::String(usage)),
            ])
    }

    pub(crate) fn invalid_value(
        cmd: &Command,
        bad_val: String,
        good_vals: &[&str],
        arg: &Arg,
        usage: String,
    ) -> Self {
        let mut info = vec![arg.to_string(), bad_val.clone()];
        info.extend(good_vals.iter().map(|s| (*s).to_owned()));

        let suggestion = suggestions::did_you_mean(&bad_val, good_vals.iter()).pop();
        let mut err = Self::new(ErrorKind::InvalidValue)
            .with_cmd(cmd)
            .set_info(info)
            .extend_context_unchecked([
                (
                    ContextKind::InvalidArg,
                    ContextValue::String(arg.to_string()),
                ),
                (ContextKind::InvalidValue, ContextValue::String(bad_val)),
                (
                    ContextKind::ValidValue,
                    ContextValue::Strings(good_vals.iter().map(|s| (*s).to_owned()).collect()),
                ),
                (ContextKind::Usage, ContextValue::String(usage)),
            ]);
        if let Some(suggestion) = suggestion {
            err = err.insert_context_unchecked(
                ContextKind::SuggestedValue,
                ContextValue::String(suggestion),
            );
        }
        err
    }

    pub(crate) fn invalid_subcommand(
        cmd: &Command,
        subcmd: String,
        did_you_mean: String,
        name: String,
        usage: String,
    ) -> Self {
        let info = vec![subcmd.clone()];
        let suggestion = format!("{} -- {}", name, subcmd);
        Self::new(ErrorKind::InvalidSubcommand)
            .with_cmd(cmd)
            .set_info(info)
            .extend_context_unchecked([
                (ContextKind::InvalidSubcommand, ContextValue::String(subcmd)),
                (
                    ContextKind::SuggestedSubcommand,
                    ContextValue::String(did_you_mean),
                ),
                (
                    ContextKind::SuggestedCommand,
                    ContextValue::String(suggestion),
                ),
                (ContextKind::Usage, ContextValue::String(usage)),
            ])
    }

    pub(crate) fn unrecognized_subcommand(cmd: &Command, subcmd: String, usage: String) -> Self {
        let info = vec![subcmd.clone()];
        Self::new(ErrorKind::UnrecognizedSubcommand)
            .with_cmd(cmd)
            .set_info(info)
            .extend_context_unchecked([
                (ContextKind::InvalidSubcommand, ContextValue::String(subcmd)),
                (ContextKind::Usage, ContextValue::String(usage)),
            ])
    }

    pub(crate) fn missing_required_argument(
        cmd: &Command,
        required: Vec<String>,
        usage: String,
    ) -> Self {
        let info = required.clone();
        Self::new(ErrorKind::MissingRequiredArgument)
            .with_cmd(cmd)
            .set_info(info)
            .extend_context_unchecked([
                (ContextKind::InvalidArg, ContextValue::Strings(required)),
                (ContextKind::Usage, ContextValue::String(usage)),
            ])
    }

    pub(crate) fn missing_subcommand(cmd: &Command, name: String, usage: String) -> Self {
        let info = vec![];
        Self::new(ErrorKind::MissingSubcommand)
            .with_cmd(cmd)
            .set_info(info)
            .extend_context_unchecked([
                (ContextKind::InvalidSubcommand, ContextValue::String(name)),
                (ContextKind::Usage, ContextValue::String(usage)),
            ])
    }

    pub(crate) fn invalid_utf8(cmd: &Command, usage: String) -> Self {
        let info = vec![];
        Self::new(ErrorKind::InvalidUtf8)
            .with_cmd(cmd)
            .set_info(info)
            .extend_context_unchecked([(ContextKind::Usage, ContextValue::String(usage))])
    }

    pub(crate) fn too_many_occurrences(
        cmd: &Command,
        arg: &Arg,
        max_occurs: usize,
        curr_occurs: usize,
        usage: String,
    ) -> Self {
        let info = vec![
            arg.to_string(),
            curr_occurs.to_string(),
            max_occurs.to_string(),
        ];
        Self::new(ErrorKind::TooManyOccurrences)
            .with_cmd(cmd)
            .set_info(info)
            .extend_context_unchecked([
                (
                    ContextKind::InvalidArg,
                    ContextValue::String(arg.to_string()),
                ),
                (
                    ContextKind::MaxOccurrences,
                    ContextValue::Number(max_occurs as isize),
                ),
                (
                    ContextKind::ActualNumValues,
                    ContextValue::Number(curr_occurs as isize),
                ),
                (ContextKind::Usage, ContextValue::String(usage)),
            ])
    }

    pub(crate) fn too_many_values(cmd: &Command, val: String, arg: String, usage: String) -> Self {
        let info = vec![arg.to_string(), val.clone()];
        Self::new(ErrorKind::TooManyValues)
            .with_cmd(cmd)
            .set_info(info)
            .extend_context_unchecked([
                (ContextKind::InvalidArg, ContextValue::String(arg)),
                (ContextKind::InvalidValue, ContextValue::String(val)),
                (ContextKind::Usage, ContextValue::String(usage)),
            ])
    }

    pub(crate) fn too_few_values(
        cmd: &Command,
        arg: &Arg,
        min_vals: usize,
        curr_vals: usize,
        usage: String,
    ) -> Self {
        let info = vec![arg.to_string(), curr_vals.to_string(), min_vals.to_string()];
        Self::new(ErrorKind::TooFewValues)
            .with_cmd(cmd)
            .set_info(info)
            .extend_context_unchecked([
                (
                    ContextKind::InvalidArg,
                    ContextValue::String(arg.to_string()),
                ),
                (
                    ContextKind::MinValues,
                    ContextValue::Number(min_vals as isize),
                ),
                (
                    ContextKind::ActualNumValues,
                    ContextValue::Number(curr_vals as isize),
                ),
                (ContextKind::Usage, ContextValue::String(usage)),
            ])
    }

    pub(crate) fn value_validation(
        arg: String,
        val: String,
        err: Box<dyn error::Error + Send + Sync>,
    ) -> Self {
        let info = vec![arg.to_string(), val.to_string(), err.to_string()];
        Self::new(ErrorKind::ValueValidation)
            .set_info(info)
            .set_source(err)
            .extend_context_unchecked([
                (ContextKind::InvalidArg, ContextValue::String(arg)),
                (ContextKind::InvalidValue, ContextValue::String(val)),
            ])
    }

    pub(crate) fn wrong_number_of_values(
        cmd: &Command,
        arg: &Arg,
        num_vals: usize,
        curr_vals: usize,
        usage: String,
    ) -> Self {
        let info = vec![arg.to_string(), curr_vals.to_string(), num_vals.to_string()];
        Self::new(ErrorKind::WrongNumberOfValues)
            .with_cmd(cmd)
            .set_info(info)
            .extend_context_unchecked([
                (
                    ContextKind::InvalidArg,
                    ContextValue::String(arg.to_string()),
                ),
                (
                    ContextKind::ExpectedNumValues,
                    ContextValue::Number(num_vals as isize),
                ),
                (
                    ContextKind::ActualNumValues,
                    ContextValue::Number(curr_vals as isize),
                ),
                (ContextKind::Usage, ContextValue::String(usage)),
            ])
    }

    pub(crate) fn unexpected_multiple_usage(cmd: &Command, arg: &Arg, usage: String) -> Self {
        let info = vec![arg.to_string()];
        Self::new(ErrorKind::UnexpectedMultipleUsage)
            .with_cmd(cmd)
            .set_info(info)
            .extend_context_unchecked([
                (
                    ContextKind::InvalidArg,
                    ContextValue::String(arg.to_string()),
                ),
                (ContextKind::Usage, ContextValue::String(usage)),
            ])
    }

    pub(crate) fn unknown_argument(
        cmd: &Command,
        arg: String,
        did_you_mean: Option<(String, Option<String>)>,
        usage: String,
    ) -> Self {
        let info = vec![arg.to_string()];
        let mut err = Self::new(ErrorKind::UnknownArgument)
            .with_cmd(cmd)
            .set_info(info)
            .extend_context_unchecked([
                (ContextKind::InvalidArg, ContextValue::String(arg)),
                (ContextKind::Usage, ContextValue::String(usage)),
            ]);
        if let Some((flag, sub)) = did_you_mean {
            err = err.insert_context_unchecked(
                ContextKind::SuggestedArg,
                ContextValue::String(format!("--{}", flag)),
            );
            if let Some(sub) = sub {
                err = err.insert_context_unchecked(
                    ContextKind::SuggestedSubcommand,
                    ContextValue::String(sub),
                );
            }
        }
        err
    }

    pub(crate) fn unnecessary_double_dash(cmd: &Command, arg: String, usage: String) -> Self {
        let info = vec![arg.to_string()];
        Self::new(ErrorKind::UnknownArgument)
            .with_cmd(cmd)
            .set_info(info)
            .extend_context_unchecked([
                (ContextKind::InvalidArg, ContextValue::String(arg)),
                (ContextKind::TrailingArg, ContextValue::Bool(true)),
                (ContextKind::Usage, ContextValue::String(usage)),
            ])
    }

    pub(crate) fn argument_not_found_auto(arg: String) -> Self {
        let info = vec![arg.to_string()];
        Self::new(ErrorKind::ArgumentNotFound)
            .set_info(info)
            .extend_context_unchecked([(ContextKind::InvalidArg, ContextValue::String(arg))])
    }

    fn formatted(&self) -> Cow<'_, Colorizer> {
        if let Some(message) = self.inner.message.as_ref() {
            message.formatted()
        } else {
            let mut c = Colorizer::new(self.stream(), self.inner.color_when);

            start_error(&mut c);

            if !self.write_dynamic_context(&mut c) {
                if let Some(msg) = self.kind().as_str() {
                    c.none(msg.to_owned());
                } else if let Some(source) = self.inner.source.as_ref() {
                    c.none(source.to_string());
                } else {
                    c.none("Unknown cause");
                }
            }

            let usage = self.get_context(ContextKind::Usage);
            if let Some(ContextValue::String(usage)) = usage {
                put_usage(&mut c, usage);
            }

            try_help(&mut c, self.inner.help_flag);

            Cow::Owned(c)
        }
    }

    #[must_use]
    fn write_dynamic_context(&self, c: &mut Colorizer) -> bool {
        match self.kind() {
            ErrorKind::ArgumentConflict => {
                let invalid_arg = self.get_context(ContextKind::InvalidArg);
                let prior_arg = self.get_context(ContextKind::PriorArg);
                if let (Some(ContextValue::String(invalid_arg)), Some(prior_arg)) =
                    (invalid_arg, prior_arg)
                {
                    c.none("The argument '");
                    c.warning(invalid_arg);
                    c.none("' cannot be used with");

                    match prior_arg {
                        ContextValue::Strings(values) => {
                            c.none(":");
                            for v in values {
                                c.none("\n    ");
                                c.warning(&**v);
                            }
                        }
                        ContextValue::String(value) => {
                            c.none(" '");
                            c.warning(value);
                            c.none("'");
                        }
                        _ => {
                            c.none(" one or more of the other specified arguments");
                        }
                    }
                    true
                } else {
                    false
                }
            }
            ErrorKind::EmptyValue => {
                let invalid_arg = self.get_context(ContextKind::InvalidArg);
                if let Some(ContextValue::String(invalid_arg)) = invalid_arg {
                    c.none("The argument '");
                    c.warning(invalid_arg);
                    c.none("' requires a value but none was supplied");

                    let possible_values = self.get_context(ContextKind::ValidValue);
                    if let Some(ContextValue::Strings(possible_values)) = possible_values {
                        c.none("\n\t[possible values: ");
                        if let Some((last, elements)) = possible_values.split_last() {
                            for v in elements {
                                c.good(escape(v));
                                c.none(", ");
                            }
                            c.good(escape(last));
                        }
                        c.none("]");
                    }
                    true
                } else {
                    false
                }
            }
            ErrorKind::NoEquals => {
                let invalid_arg = self.get_context(ContextKind::InvalidArg);
                if let Some(ContextValue::String(invalid_arg)) = invalid_arg {
                    c.none("Equal sign is needed when assigning values to '");
                    c.warning(invalid_arg);
                    c.none("'.");
                    true
                } else {
                    false
                }
            }
            ErrorKind::InvalidValue => {
                let invalid_arg = self.get_context(ContextKind::InvalidArg);
                let invalid_value = self.get_context(ContextKind::InvalidValue);
                if let (
                    Some(ContextValue::String(invalid_arg)),
                    Some(ContextValue::String(invalid_value)),
                ) = (invalid_arg, invalid_value)
                {
                    c.none(quote(invalid_value));
                    c.none(" isn't a valid value for '");
                    c.warning(invalid_arg);
                    c.none("'");

                    let possible_values = self.get_context(ContextKind::ValidValue);
                    if let Some(ContextValue::Strings(possible_values)) = possible_values {
                        c.none("\n\t[possible values: ");
                        if let Some((last, elements)) = possible_values.split_last() {
                            for v in elements {
                                c.good(escape(v));
                                c.none(", ");
                            }
                            c.good(escape(last));
                        }
                        c.none("]");
                    }

                    let suggestion = self.get_context(ContextKind::SuggestedValue);
                    if let Some(ContextValue::String(suggestion)) = suggestion {
                        c.none("\n\n\tDid you mean ");
                        c.good(quote(suggestion));
                        c.none("?");
                    }
                    true
                } else {
                    false
                }
            }
            ErrorKind::InvalidSubcommand => {
                let invalid_sub = self.get_context(ContextKind::InvalidSubcommand);
                if let Some(ContextValue::String(invalid_sub)) = invalid_sub {
                    c.none("The subcommand '");
                    c.warning(invalid_sub);
                    c.none("' wasn't recognized");

                    let valid_sub = self.get_context(ContextKind::SuggestedSubcommand);
                    if let Some(ContextValue::String(valid_sub)) = valid_sub {
                        c.none("\n\n\tDid you mean ");
                        c.good(valid_sub);
                        c.none("?");
                    }

                    let suggestion = self.get_context(ContextKind::SuggestedCommand);
                    if let Some(ContextValue::String(suggestion)) = suggestion {
                        c.none(
            "\n\nIf you believe you received this message in error, try re-running with '",
        );
                        c.good(suggestion);
                        c.none("'");
                    }
                    true
                } else {
                    false
                }
            }
            ErrorKind::UnrecognizedSubcommand => {
                let invalid_sub = self.get_context(ContextKind::InvalidSubcommand);
                if let Some(ContextValue::String(invalid_sub)) = invalid_sub {
                    c.none("The subcommand '");
                    c.warning(invalid_sub);
                    c.none("' wasn't recognized");
                    true
                } else {
                    false
                }
            }
            ErrorKind::MissingRequiredArgument => {
                let invalid_arg = self.get_context(ContextKind::InvalidArg);
                if let Some(ContextValue::Strings(invalid_arg)) = invalid_arg {
                    c.none("The following required arguments were not provided:");
                    for v in invalid_arg {
                        c.none("\n    ");
                        c.good(&**v);
                    }
                    true
                } else {
                    false
                }
            }
            ErrorKind::MissingSubcommand => {
                let invalid_sub = self.get_context(ContextKind::InvalidSubcommand);
                if let Some(ContextValue::String(invalid_sub)) = invalid_sub {
                    c.none("'");
                    c.warning(invalid_sub);
                    c.none("' requires a subcommand but one was not provided");
                    true
                } else {
                    false
                }
            }
            ErrorKind::InvalidUtf8 => false,
            ErrorKind::TooManyOccurrences => {
                let invalid_arg = self.get_context(ContextKind::InvalidArg);
                let actual_num_occurs = self.get_context(ContextKind::ActualNumOccurrences);
                let max_occurs = self.get_context(ContextKind::MaxOccurrences);
                if let (
                    Some(ContextValue::String(invalid_arg)),
                    Some(ContextValue::Number(actual_num_occurs)),
                    Some(ContextValue::Number(max_occurs)),
                ) = (invalid_arg, actual_num_occurs, max_occurs)
                {
                    let were_provided = Error::singular_or_plural(*actual_num_occurs as usize);
                    c.none("The argument '");
                    c.warning(invalid_arg);
                    c.none("' allows at most ");
                    c.warning(max_occurs.to_string());
                    c.none(" occurrences but ");
                    c.warning(actual_num_occurs.to_string());
                    c.none(were_provided);
                    true
                } else {
                    false
                }
            }
            ErrorKind::TooManyValues => {
                let invalid_arg = self.get_context(ContextKind::InvalidArg);
                let invalid_value = self.get_context(ContextKind::InvalidValue);
                if let (
                    Some(ContextValue::String(invalid_arg)),
                    Some(ContextValue::String(invalid_value)),
                ) = (invalid_arg, invalid_value)
                {
                    c.none("The value '");
                    c.warning(invalid_value);
                    c.none("' was provided to '");
                    c.warning(invalid_arg);
                    c.none("' but it wasn't expecting any more values");
                    true
                } else {
                    false
                }
            }
            ErrorKind::TooFewValues => {
                let invalid_arg = self.get_context(ContextKind::InvalidArg);
                let actual_num_values = self.get_context(ContextKind::ActualNumValues);
                let min_values = self.get_context(ContextKind::MinValues);
                if let (
                    Some(ContextValue::String(invalid_arg)),
                    Some(ContextValue::Number(actual_num_values)),
                    Some(ContextValue::Number(min_values)),
                ) = (invalid_arg, actual_num_values, min_values)
                {
                    let were_provided = Error::singular_or_plural(*actual_num_values as usize);
                    c.none("The argument '");
                    c.warning(invalid_arg);
                    c.none("' requires at least ");
                    c.warning(min_values.to_string());
                    c.none(" values but only ");
                    c.warning(actual_num_values.to_string());
                    c.none(were_provided);
                    true
                } else {
                    false
                }
            }
            ErrorKind::ValueValidation => {
                let invalid_arg = self.get_context(ContextKind::InvalidArg);
                let invalid_value = self.get_context(ContextKind::InvalidValue);
                if let (
                    Some(ContextValue::String(invalid_arg)),
                    Some(ContextValue::String(invalid_value)),
                ) = (invalid_arg, invalid_value)
                {
                    c.none("Invalid value ");
                    c.warning(quote(invalid_value));
                    c.none(" for '");
                    c.warning(invalid_arg);
                    if let Some(source) = self.inner.source.as_deref() {
                        c.none("': ");
                        c.none(source.to_string());
                    } else {
                        c.none("'");
                    }
                    true
                } else {
                    false
                }
            }
            ErrorKind::WrongNumberOfValues => {
                let invalid_arg = self.get_context(ContextKind::InvalidArg);
                let actual_num_values = self.get_context(ContextKind::ActualNumValues);
                let num_values = self.get_context(ContextKind::ExpectedNumValues);
                if let (
                    Some(ContextValue::String(invalid_arg)),
                    Some(ContextValue::Number(actual_num_values)),
                    Some(ContextValue::Number(num_values)),
                ) = (invalid_arg, actual_num_values, num_values)
                {
                    let were_provided = Error::singular_or_plural(*actual_num_values as usize);
                    c.none("The argument '");
                    c.warning(invalid_arg);
                    c.none("' requires ");
                    c.warning(num_values.to_string());
                    c.none(" values, but ");
                    c.warning(actual_num_values.to_string());
                    c.none(were_provided);
                    true
                } else {
                    false
                }
            }
            ErrorKind::UnexpectedMultipleUsage => {
                let invalid_arg = self.get_context(ContextKind::InvalidArg);
                if let Some(ContextValue::String(invalid_arg)) = invalid_arg {
                    c.none("The argument '");
                    c.warning(invalid_arg.to_string());
                    c.none("' was provided more than once, but cannot be used multiple times");
                    true
                } else {
                    false
                }
            }
            ErrorKind::UnknownArgument => {
                let invalid_arg = self.get_context(ContextKind::InvalidArg);
                if let Some(ContextValue::String(invalid_arg)) = invalid_arg {
                    c.none("Found argument '");
                    c.warning(invalid_arg.to_string());
                    c.none("' which wasn't expected, or isn't valid in this context");

                    let valid_sub = self.get_context(ContextKind::SuggestedSubcommand);
                    let valid_arg = self.get_context(ContextKind::SuggestedArg);
                    match (valid_sub, valid_arg) {
                        (
                            Some(ContextValue::String(valid_sub)),
                            Some(ContextValue::String(valid_arg)),
                        ) => {
                            c.none("\n\n\tDid you mean ");
                            c.none("to put '");
                            c.good(valid_arg);
                            c.none("' after the subcommand '");
                            c.good(valid_sub);
                            c.none("'?");
                        }
                        (None, Some(ContextValue::String(valid_arg))) => {
                            c.none("\n\n\tDid you mean '");
                            c.good(valid_arg);
                            c.none("'?");
                        }
                        (_, _) => {}
                    }

                    let invalid_arg = self.get_context(ContextKind::InvalidArg);
                    if let Some(ContextValue::String(invalid_arg)) = invalid_arg {
                        if invalid_arg.starts_with('-') {
                            c.none(format!(
                                "\n\n\tIf you tried to supply `{}` as a value rather than a flag, use `-- {}`",
                                invalid_arg, invalid_arg
                            ));
                        }

                        let trailing_arg = self.get_context(ContextKind::TrailingArg);
                        if trailing_arg == Some(&ContextValue::Bool(true)) {
                            c.none(format!(
                            "\n\n\tIf you tried to supply `{}` as a subcommand, remove the '--' before it.",
                            invalid_arg
                        ));
                        }
                    }
                    true
                } else {
                    false
                }
            }
            ErrorKind::ArgumentNotFound => {
                let invalid_arg = self.get_context(ContextKind::InvalidArg);
                if let Some(ContextValue::String(invalid_arg)) = invalid_arg {
                    c.none("The argument '");
                    c.warning(invalid_arg.to_string());
                    c.none("' wasn't found");
                    true
                } else {
                    false
                }
            }
            ErrorKind::DisplayHelp
            | ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
            | ErrorKind::DisplayVersion
            | ErrorKind::Io
            | ErrorKind::Format => false,
        }
    }

    /// Returns the singular or plural form on the verb to be based on the argument's value.
    fn singular_or_plural(n: usize) -> &'static str {
        if n > 1 {
            " were provided"
        } else {
            " was provided"
        }
    }
}

impl From<io::Error> for Error {
    fn from(e: io::Error) -> Self {
        Error::raw(ErrorKind::Io, e)
    }
}

impl From<fmt::Error> for Error {
    fn from(e: fmt::Error) -> Self {
        Error::raw(ErrorKind::Format, e)
    }
}

impl error::Error for Error {
    #[allow(trivial_casts)]
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        self.inner.source.as_ref().map(|e| e.as_ref() as _)
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        // Assuming `self.message` already has a trailing newline, from `try_help` or similar
        write!(f, "{}", self.formatted())?;
        if let Some(backtrace) = self.inner.backtrace.as_ref() {
            writeln!(f)?;
            writeln!(f, "Backtrace:")?;
            writeln!(f, "{}", backtrace)?;
        }
        Ok(())
    }
}

fn start_error(c: &mut Colorizer) {
    c.error("error:");
    c.none(" ");
}

fn put_usage(c: &mut Colorizer, usage: impl Into<String>) {
    c.none("\n\n");
    c.none(usage);
}

fn get_help_flag(cmd: &Command) -> Option<&'static str> {
    if !cmd.is_disable_help_flag_set() {
        Some("--help")
    } else if cmd.has_subcommands() && !cmd.is_disable_help_subcommand_set() {
        Some("help")
    } else {
        None
    }
}

fn try_help(c: &mut Colorizer, help: Option<&str>) {
    if let Some(help) = help {
        c.none("\n\nFor more information try ");
        c.good(help);
        c.none("\n");
    } else {
        c.none("\n");
    }
}

fn quote(s: impl AsRef<str>) -> String {
    let s = s.as_ref();
    format!("{:?}", s)
}

fn escape(s: impl AsRef<str>) -> String {
    let s = s.as_ref();
    if s.contains(char::is_whitespace) {
        quote(s)
    } else {
        s.to_owned()
    }
}

#[derive(Clone, Debug)]
pub(crate) enum Message {
    Raw(String),
    Formatted(Colorizer),
}

impl Message {
    fn format(&mut self, cmd: &Command, usage: String) {
        match self {
            Message::Raw(s) => {
                let mut c = Colorizer::new(Stream::Stderr, cmd.get_color());

                let mut message = String::new();
                std::mem::swap(s, &mut message);
                start_error(&mut c);
                c.none(message);
                put_usage(&mut c, usage);
                try_help(&mut c, get_help_flag(cmd));
                *self = Self::Formatted(c);
            }
            Message::Formatted(_) => {}
        }
    }

    fn formatted(&self) -> Cow<Colorizer> {
        match self {
            Message::Raw(s) => {
                let mut c = Colorizer::new(Stream::Stderr, ColorChoice::Never);
                start_error(&mut c);
                c.none(s);
                Cow::Owned(c)
            }
            Message::Formatted(c) => Cow::Borrowed(c),
        }
    }
}

impl From<String> for Message {
    fn from(inner: String) -> Self {
        Self::Raw(inner)
    }
}

impl From<Colorizer> for Message {
    fn from(inner: Colorizer) -> Self {
        Self::Formatted(inner)
    }
}

#[cfg(feature = "debug")]
#[derive(Debug)]
struct Backtrace(backtrace::Backtrace);

#[cfg(feature = "debug")]
impl Backtrace {
    fn new() -> Option<Self> {
        Some(Self(backtrace::Backtrace::new()))
    }
}

#[cfg(feature = "debug")]
impl Display for Backtrace {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        // `backtrace::Backtrace` uses `Debug` instead of `Display`
        write!(f, "{:?}", self.0)
    }
}

#[cfg(not(feature = "debug"))]
#[derive(Debug)]
struct Backtrace;

#[cfg(not(feature = "debug"))]
impl Backtrace {
    fn new() -> Option<Self> {
        None
    }
}

#[cfg(not(feature = "debug"))]
impl Display for Backtrace {
    fn fmt(&self, _: &mut Formatter) -> fmt::Result {
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    /// Check `clap::Error` impls Send and Sync.
    mod clap_error_impl_send_sync {
        use crate::Error;
        trait Foo: std::error::Error + Send + Sync + 'static {}
        impl Foo for Error {}
    }
}
