use crate::internal::as2::error::ParsingError;
use annotate_snippets::renderer::DecorStyle;
use annotate_snippets::{Group, Renderer};
use indexmap::IndexMap;

#[derive(Debug)]
pub(crate) struct ErrorSet {
    files: IndexMap<String, (String, Vec<ParsingError>)>,
    io_errors: Vec<(String, std::io::Error)>,
    misc_errors: Vec<String>,
}

impl ErrorSet {
    pub fn new() -> Self {
        Self {
            files: IndexMap::new(),
            io_errors: vec![],
            misc_errors: vec![],
        }
    }

    pub fn add_parsing_error(&mut self, filename: &str, source: &str, error: ParsingError) {
        self.files
            .entry(filename.to_owned())
            .or_insert_with(|| (source.to_owned(), vec![]))
            .1
            .push(error);
    }

    pub fn add_io_error(&mut self, filename: &str, error: std::io::Error) {
        self.io_errors.push((filename.to_owned(), error));
    }

    pub fn add_misc_error(&mut self, error: String) {
        self.misc_errors.push(error);
    }

    pub fn report<'a>(&'a self) -> Vec<Group<'a>> {
        let mut report = vec![];
        for (filename, (source, errors)) in &self.files {
            for error in errors {
                let mut source = annotate_snippets::Snippet::source(source).path(filename);
                source = source.annotation(error.annotation());
                report.push(
                    annotate_snippets::Level::ERROR
                        .primary_title(error.error.to_string())
                        .element(source),
                )
            }
        }
        for (filename, error) in &self.io_errors {
            report.push(
                annotate_snippets::Level::ERROR
                    .primary_title(error.to_string())
                    .element(annotate_snippets::Origin::path(filename)),
            )
        }
        for error in &self.misc_errors {
            report.push(
                annotate_snippets::Level::ERROR
                    .primary_title(error.clone())
                    .element(annotate_snippets::Level::ERROR.message(error.clone())),
            );
        }
        report
    }

    pub fn error_unless_empty(self) -> Result<(), Error> {
        if self.io_errors.is_empty() && self.files.is_empty() && self.misc_errors.is_empty() {
            return Ok(());
        }
        Err(Error(self))
    }
}

#[derive(Debug)]
pub struct Error(pub(crate) ErrorSet);

impl Error {
    pub fn to_string_plain(&self) -> String {
        let report = self.0.report();
        let renderer = Renderer::plain().decor_style(DecorStyle::Unicode);
        renderer.render(&report)
    }

    pub fn to_string_styled(&self) -> String {
        let report = self.0.report();
        let renderer = Renderer::styled().decor_style(DecorStyle::Unicode);
        renderer.render(&report)
    }
}

#[cfg(test)]
impl serde::Serialize for Error {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if cfg!(test) {
            self.to_string_plain().fmt(f)
        } else {
            self.to_string_styled().fmt(f)
        }
    }
}

impl std::error::Error for Error {}
