use std::ops::Range;

use proc_macro2::Span;
use quote::quote;
use syn::spanned::Spanned;

trait SpanHelper {
    fn matching_columns(&self, line: usize) -> Range<usize>;
}

impl SpanHelper for Span {
    fn matching_columns(&self, line: usize) -> Range<usize> {
        let start = self.start();
        let end = self.end();

        if line == start.line && line == end.line {
            start.column..end.column
        } else {
            todo!()
        }
    }
}

pub enum SyntaxError {
    UnhandledType(syn::Type),
    UndefinedIdentifier(syn::Ident),
    UnexpectedAttributes(syn::Ident),
    PrimitiveReprRequired(syn::Ident),
}

pub enum ParseError {
    HardError(syn::parse::Error),
    IoError(std::io::Error),
    SyntaxError(Vec<SyntaxError>),
}

pub trait UnwrapFancy<T> {
    fn unwrap_fancy<S: AsRef<str>>(self, source_code: S) -> T;
}

impl From<std::io::Error> for ParseError {
    fn from(value: std::io::Error) -> Self {
        Self::IoError(value)
    }
}

impl From<syn::parse::Error> for ParseError {
    fn from(value: syn::parse::Error) -> Self {
        Self::HardError(value)
    }
}

impl From<SyntaxError> for ParseError {
    fn from(value: SyntaxError) -> Self {
        Self::SyntaxError(vec![value])
    }
}

impl SyntaxError {
    pub fn span(&self) -> Span {
        match self {
            Self::UnhandledType(ty) => ty.span(),
            Self::PrimitiveReprRequired(id)
            | Self::UnexpectedAttributes(id)
            | Self::UndefinedIdentifier(id) => id.span(),
        }
    }

    pub fn describe(&self) -> String {
        match self {
            Self::UnhandledType(ty) => format!("Unhandled type '{}'", quote!(#ty)),
            Self::UndefinedIdentifier(id) => format!("Undefined identifier '{}'", id),
            Self::PrimitiveReprRequired(id) => {
                format!("Primitive repr required in type definition, got '{}'", id)
            }
            Self::UnexpectedAttributes(id) => {
                format!("Unexpected attributes for type '{}'", id)
            }
        }
    }

    pub fn print(&self, source_code: &str) {
        let span = self.span();
        let start = span.start();
        let end = span.end();
        let line_range = start.line..=end.line;

        let lines_context = source_code
            .split('\n')
            .skip(start.line.saturating_sub(2))
            .take(3 + end.line - start.line);

        if start.line == end.line {
            eprintln!("[ERROR] {}:", self.describe());
        }

        for (i, line) in lines_context.enumerate() {
            let line_idx = i + start.line - 1;
            eprintln!("{:4} | {}", line_idx, line);
            if line_range.contains(&line_idx) {
                let columns = span.matching_columns(line_idx);
                eprint!("{0:4} | {0:>width$}", "", width = columns.start);
                for _ in columns {
                    eprint!("~");
                }
                eprintln!();
            }
        }
        eprintln!();
    }
}

impl<T> UnwrapFancy<T> for Result<T, ParseError> {
    fn unwrap_fancy<S: AsRef<str>>(self, source_code: S) -> T {
        match self {
            Self::Ok(value) => value,
            Self::Err(err) => {
                let n_errors = match err {
                    ParseError::IoError(err) => {
                        eprintln!("ABI read error: {}", err);

                        1
                    }
                    ParseError::HardError(err) => {
                        eprintln!("{}", err);

                        1
                    }
                    ParseError::SyntaxError(errs) => {
                        for err in errs.iter() {
                            err.print(source_code.as_ref());
                        }

                        errs.len()
                    }
                };
                panic!("Compilation aborted, {} error(s)", n_errors);
            }
        }
    }
}

impl ParseError {
    pub fn syntax1(e: SyntaxError) -> Self {
        Self::SyntaxError(vec![e])
    }

    pub fn fold<T, I: IntoIterator<Item = Result<T, Self>>, Q: Extend<T>>(
        mut acc: Q,
        it: I,
    ) -> Result<Q, Self> {
        let mut errors = vec![];
        for item in it {
            match item {
                Ok(val) => acc.extend_one(val),
                Err(ParseError::SyntaxError(err)) => errors.extend(err),
                Err(err) => return Err(err),
            }
        }
        if errors.is_empty() {
            Ok(acc)
        } else {
            Err(ParseError::SyntaxError(errors))
        }
    }
}
