// Allow dead code, until we've fully implemented CLI/error handling
#![allow(dead_code)]

use std::borrow::Borrow;
use std::fmt::{Debug, Display};
use std::io::{self, Write};
pub use std::process::exit;

use anyhow::anyhow;

use crate::util::style::{highlight, highlight_error, highlight_info, highlight_warning};

/// Print the given error in a proper format for the user,
/// with it's causes.
pub fn print_error(err: anyhow::Error) {
    // Report each printable error, count them
    let count = err
        .chain()
        .map(|err| format!("{}", err))
        .filter(|err| !err.is_empty())
        .enumerate()
        .map(|(i, err)| {
            if i == 0 {
                eprintln!("{} {}", highlight_error("error:"), err);
            } else {
                eprintln!("{} {}", highlight_error("caused by:"), err);
            }
        })
        .count();

    // Fall back to a basic message
    if count == 0 {
        eprintln!("{} an undefined error occurred", highlight_error("error:"),);
    }
}

/// Print the given error message in a proper format for the user,
/// with it's causes.
pub fn print_error_msg<S>(err: S)
where
    S: AsRef<str> + Display + Debug + Sync + Send + 'static,
{
    print_error(anyhow!(err));
}

/// Print a warning.
pub fn print_warning<S>(err: S)
where
    S: AsRef<str> + Display + Debug + Sync + Send + 'static,
{
    eprintln!("{} {}", highlight_warning("warning:"), err);
}

/// Quit the application regularly.
pub fn quit() -> ! {
    exit(0);
}

/// Quit the application with an error code,
/// and print the given error.
pub fn quit_error(err: anyhow::Error, hints: impl Borrow<ErrorHints>) -> ! {
    // Print the error
    print_error(err);

    // Print error hints
    hints.borrow().print(false);

    // Quit
    exit(1);
}

/// Quit the application with an error code,
/// and print the given error message.
pub fn quit_error_msg<S>(err: S, hints: impl Borrow<ErrorHints>) -> !
where
    S: AsRef<str> + Display + Debug + Sync + Send + 'static,
{
    quit_error(anyhow!(err), hints);
}

/// The error hint configuration.
#[derive(Clone, Builder)]
#[builder(default)]
pub struct ErrorHints {
    /// A list of info messages to print along with the error.
    info: Vec<String>,

    /// Show about the config flag.
    config: bool,

    /// Show about the config generate command.
    config_generate: bool,

    /// Show about the config test command.
    config_test: bool,

    /// Show about the verbose flag.
    verbose: bool,

    /// Show about the help flag.
    help: bool,
}

impl ErrorHints {
    /// Check whether any hint should be printed.
    pub fn any(&self) -> bool {
        self.config || self.config_generate || self.config_test || self.verbose || self.help
    }

    /// Print the error hints.
    pub fn print(&self, end_newline: bool) {
        // Print info messages
        for msg in &self.info {
            eprintln!("{} {}", highlight_info("info:"), msg);
        }

        // Stop if nothing should be printed
        if !self.any() {
            return;
        }

        eprintln!();

        // Print hints
        let bin = crate::util::bin_name();
        if self.config_generate {
            eprintln!(
                "Use '{}' to generate a new config file",
                highlight(&format!("{} config generate", bin))
            );
        }
        if self.config {
            eprintln!(
                "Use '{}' to select a config file",
                highlight("--config FILE")
            );
        }
        if self.config_test {
            eprintln!(
                "Use '{}' to test a config file",
                highlight(&format!("{} config test -c FILE", bin))
            );
        }
        if self.verbose {
            eprintln!("For a detailed log add '{}'", highlight("--verbose"));
        }
        if self.help {
            eprintln!("For more information add '{}'", highlight("--help"));
        }

        // End with additional newline
        if end_newline {
            eprintln!();
        }

        // Flush
        let _ = io::stderr().flush();
    }
}

impl Default for ErrorHints {
    fn default() -> Self {
        ErrorHints {
            info: Vec::new(),
            config: false,
            config_generate: false,
            config_test: false,
            verbose: true,
            help: true,
        }
    }
}

impl ErrorHintsBuilder {
    /// Add a single info entry.
    pub fn add_info(mut self, info: String) -> Self {
        // Initialize the info list
        if self.info.is_none() {
            self.info = Some(Vec::new());
        }

        // Add the item to the info list
        if let Some(ref mut list) = self.info {
            list.push(info);
        }

        self
    }
}
