use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use serde::{Deserialize, Serialize};
use std::fmt::Display;

#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct LineColumn {
    pub line: usize,
    pub column: usize,
}
impl From<proc_macro2::LineColumn> for LineColumn {
    fn from(lc: proc_macro2::LineColumn) -> Self {
        let line = lc.line.saturating_sub(1);
        let column = lc.column;
        Self { line, column }
    }
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum CodeRegion {
    Unknown,
    Known(LineColumn, LineColumn),
}

#[derive(Debug, Clone, Copy)]
pub struct SpanRange {
    pub start: Span,
    pub end: Span,
}

impl SpanRange {
    pub fn new(start: Span, end: Span) -> Self {
        Self { start, end }
    }
    pub fn is_set(&self) -> bool {
        fn is_span_spanned(span: &Span) -> bool {
            span.source_text() != Span::call_site().source_text()
        }
        is_span_spanned(&self.start) && is_span_spanned(&self.end)
    }
    pub fn call_site() -> Self {
        SpanRange::new(Span::call_site(), Span::call_site())
    }
    pub fn region(&self) -> CodeRegion {
        if self.is_set() {
            CodeRegion::Known(self.start.start().into(), self.end.end().into())
        } else {
            CodeRegion::Unknown
        }
    }
}

impl From<proc_macro2::extra::DelimSpan> for SpanRange {
    fn from(value: proc_macro2::extra::DelimSpan) -> Self {
        Self::new(value.open(), value.close())
    }
}

pub trait HasSpan {
    fn span(&self) -> SpanRange;
}

impl<T: ToTokens + ?Sized> HasSpan for T {
    fn span(&self) -> SpanRange {
        let tokens = self.to_token_stream();
        let mut iter = tokens.into_iter();
        let start = iter.next().map_or_else(Span::call_site, |t| t.span());
        let end = iter.last().map_or(start, |t| t.span());
        SpanRange::new(start, end)
    }
}

#[derive(Debug, Clone)]
pub struct ErrorMessage {
    pub msg: String,
    span: SpanRange,
}

impl ErrorMessage {
    pub fn new<U: Display>(span: SpanRange, msg: U) -> ErrorMessage {
        let msg = msg.to_string();
        ErrorMessage { msg, span }
    }
    pub fn new_spanned<T: ToTokens, U: Display>(tokens: T, message: U) -> Self {
        ErrorMessage::new(tokens.span(), message)
    }
    pub fn is_span_set(&self) -> bool {
        self.span.is_set()
    }
    pub fn set_span(&mut self, span: SpanRange) {
        self.span = span;
    }
}

impl ToTokens for ErrorMessage {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        use proc_macro2::{
            Delimiter,
            Group,
            Ident,
            Literal,
            Punct,
            Spacing,
            TokenStream,
            TokenTree,
        };
        let start = self.span.start;
        let end = self.span.end;
        let joint_punct = TokenTree::Punct({
            let mut punct = Punct::new(':', Spacing::Joint);
            punct.set_span(start);
            punct
        });
        let alone_punct = TokenTree::Punct({
            let mut punct = Punct::new(':', Spacing::Alone);
            punct.set_span(start);
            punct
        });
        let bang = TokenTree::Punct({
            let mut punct = Punct::new('!', Spacing::Alone);
            punct.set_span(start);
            punct
        });
        tokens.extend([
            joint_punct.clone(),
            alone_punct.clone(),
            TokenTree::Ident(Ident::new("core", start)),
            joint_punct,
            alone_punct,
            TokenTree::Ident(Ident::new("compile_error", start)),
            bang,
            TokenTree::Group({
                let mut group = Group::new(Delimiter::Brace, {
                    TokenStream::from_iter([TokenTree::Literal({
                        let mut string = Literal::string(&self.msg);
                        string.set_span(end);
                        string
                    })])
                });
                group.set_span(end);
                group
            }),
        ])
    }
}

impl Display for ErrorMessage {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        f.write_str(&self.msg)
    }
}

impl From<syn::Error> for Error {
    fn from(err: syn::Error) -> Self {
        Self::new_spanned(err.to_compile_error(), err.to_string())
    }
}

#[derive(Debug, Clone)]
pub struct Error {
    messages: Vec<ErrorMessage>,
}

impl Error {
    fn new_inner(messages: Vec<ErrorMessage>) -> Self {
        Self { messages }
    }
    pub fn new<U: Display>(span: SpanRange, msg: U) -> Self {
        ErrorMessage::new(span, msg).into()
    }
    pub fn new_spanned<T: ToTokens, U: Display>(tokens: T, message: U) -> Self {
        ErrorMessage::new_spanned(tokens, message).into()
    }
    pub fn combine(&mut self, other: Error) {
        self.messages.extend(other.messages);
    }
    pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, ErrorMessage> {
        self.messages.iter_mut()
    }
}

impl From<ErrorMessage> for Error {
    fn from(err: ErrorMessage) -> Self {
        Self::new_inner(vec![err])
    }
}

impl ToTokens for Error {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        for msg in &self.messages {
            msg.to_tokens(tokens);
        }
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        for msg in &self.messages {
            msg.fmt(f)?;
        }
        Ok(())
    }
}
