use crate::abort_now;
use proc_macro2::Span;
use proc_macro2::TokenStream;
use quote::{quote_spanned, ToTokens};
#[derive(Debug, PartialEq)]
pub enum Level {
    Error,
    Warning,
    #[doc(hidden)]
    NonExhaustive,
}
#[derive(Debug)]
pub struct Diagnostic {
    pub(crate) level: Level,
    pub(crate) start: Span,
    pub(crate) end: Span,
    pub(crate) msg: String,
    pub(crate) suggestions: Vec<(SuggestionKind, String, Option<Span>)>,
    pub(crate) children: Vec<(Span, Span, String)>,
}
impl Diagnostic {
    
    pub fn new(level: Level, message: String) -> Self {
        Diagnostic::spanned(Span::call_site(), level, message)
    }
    
    pub fn spanned(span: Span, level: Level, message: String) -> Self {
        Diagnostic::double_spanned(span, span, level, message)
    }
    
    
    pub fn span_error(self, span: Span, msg: String) -> Self {
        self.double_span_error(span, span, msg)
    }
    
    
    
    
    
    pub fn span_help(mut self, span: Span, msg: String) -> Self {
        self.suggestions
            .push((SuggestionKind::Help, msg, Some(span)));
        self
    }
    
    pub fn help(mut self, msg: String) -> Self {
        self.suggestions.push((SuggestionKind::Help, msg, None));
        self
    }
    
    
    
    
    
    pub fn span_note(mut self, span: Span, msg: String) -> Self {
        self.suggestions
            .push((SuggestionKind::Note, msg, Some(span)));
        self
    }
    
    pub fn note(mut self, msg: String) -> Self {
        self.suggestions.push((SuggestionKind::Note, msg, None));
        self
    }
    
    pub fn message(&self) -> &str {
        &self.msg
    }
    
    
    
    
    
    pub fn abort(self) -> ! {
        self.emit();
        abort_now()
    }
    
    
    
    
    
    pub fn emit(self) {
        crate::imp::emit_diagnostic(self);
    }
}
#[doc(hidden)]
impl Diagnostic {
    pub fn double_spanned(start: Span, end: Span, level: Level, message: String) -> Self {
        Diagnostic {
            level,
            start,
            end,
            msg: message,
            suggestions: vec![],
            children: vec![],
        }
    }
    pub fn double_span_error(mut self, start: Span, end: Span, msg: String) -> Self {
        self.children.push((start, end, msg));
        self
    }
    pub fn span_suggestion(self, span: Span, suggestion: &str, msg: String) -> Self {
        match suggestion {
            "help" | "hint" => self.span_help(span, msg),
            _ => self.span_note(span, msg),
        }
    }
    pub fn suggestion(self, suggestion: &str, msg: String) -> Self {
        match suggestion {
            "help" | "hint" => self.help(msg),
            _ => self.note(msg),
        }
    }
}
impl ToTokens for Diagnostic {
    fn to_tokens(&self, ts: &mut TokenStream) {
        use std::borrow::Cow;
        fn ensure_lf(buf: &mut String, s: &str) {
            if s.ends_with('\n') {
                buf.push_str(s);
            } else {
                buf.push_str(s);
                buf.push('\n');
            }
        }
        fn diag_to_tokens(
            start: Span,
            end: Span,
            level: &Level,
            msg: &str,
            suggestions: &[(SuggestionKind, String, Option<Span>)],
        ) -> TokenStream {
            if *level == Level::Warning {
                return TokenStream::new();
            }
            let message = if suggestions.is_empty() {
                Cow::Borrowed(msg)
            } else {
                let mut message = String::new();
                ensure_lf(&mut message, msg);
                message.push('\n');
                for (kind, note, _span) in suggestions {
                    message.push_str("  = ");
                    message.push_str(kind.name());
                    message.push_str(": ");
                    ensure_lf(&mut message, note);
                }
                message.push('\n');
                Cow::Owned(message)
            };
            let msg = syn::LitStr::new(&*message, end);
            let group = quote_spanned!(end=> { #msg } );
            quote_spanned!(start=> compile_error!#group)
        }
        ts.extend(diag_to_tokens(
            self.start,
            self.end,
            &self.level,
            &self.msg,
            &self.suggestions,
        ));
        ts.extend(
            self.children
                .iter()
                .map(|(start, end, msg)| diag_to_tokens(*start, *end, &Level::Error, &msg, &[])),
        );
    }
}
#[derive(Debug)]
pub(crate) enum SuggestionKind {
    Help,
    Note,
}
impl SuggestionKind {
    fn name(&self) -> &'static str {
        match self {
            SuggestionKind::Note => "note",
            SuggestionKind::Help => "help",
        }
    }
}
impl From<syn::Error> for Diagnostic {
    fn from(err: syn::Error) -> Self {
        use proc_macro2::TokenTree;
        fn gut_error(ts: &mut impl Iterator<Item = TokenTree>) -> Option<(Span, Span, String)> {
            let start = match ts.next() {
                
                None => return None,
                Some(tt) => tt.span(),
            };
            ts.next().unwrap(); 
            let lit = match ts.next().unwrap() {
                TokenTree::Group(group) => match group.stream().into_iter().next().unwrap() {
                    TokenTree::Literal(lit) => lit,
                    _ => unreachable!(),
                },
                _ => unreachable!(),
            };
            let end = lit.span();
            let mut msg = lit.to_string();
            msg.pop();
            msg.remove(0);
            Some((start, end, msg))
        }
        let mut ts = err.to_compile_error().into_iter();
        let (start, end, msg) = gut_error(&mut ts).unwrap();
        let mut res = Diagnostic::double_spanned(start, end, Level::Error, msg);
        while let Some((start, end, msg)) = gut_error(&mut ts) {
            res = res.double_span_error(start, end, msg);
        }
        res
    }
}