#[cfg(feature = "report")]
mod report;

use std::fmt;

#[cfg(feature = "report")]
pub use report::Report;

use crate::{
    lexer::{self},
    parser::AdditionalErrors,
    Span,
};

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Error {
    /// Generated by the parser when it encounters a token (or EOF) it did not
    /// expect.
    InvalidToken { location: usize },

    /// Generated by the parser when it encounters an EOF it did not expect.
    UnrecognizedEof {
        /// The end of the final token
        location: usize,

        /// The set of expected tokens: these names are taken from the
        /// grammar and hence may not necessarily be suitable for
        /// presenting to the user.
        expected: Vec<String>,
    },

    /// Generated by the parser when it encounters a token it did not expect.
    UnrecognizedToken {
        /// The unexpected token of type `T` with a span given by the two `L` values.
        token: (usize, String, usize),

        /// The set of expected tokens: these names are taken from the
        /// grammar and hence may not necessarily be suitable for
        /// presenting to the user.
        expected: Vec<String>,
    },

    /// Generated by the parser when it encounters additional, unexpected tokens.
    ExtraToken { token: (usize, String, usize) },

    /// Lexing errors
    Lexical(lexer::LexicalError),

    /// Malformed string literal
    MalformedStringLiteral(crate::common::MalformedStringError),
}

impl Error {
    pub fn span(&self) -> Span {
        match self {
            Error::InvalidToken { location } => Span::new(*location, *location),
            Error::UnrecognizedEof { location, .. } => Span::new(*location, *location),
            Error::UnrecognizedToken {
                token: (start, _, end),
                ..
            } => Span::new(*start, *end),
            Error::ExtraToken {
                token: (start, _, end),
                ..
            } => Span::new(*start, *end),
            Error::Lexical(error) => error.span(),
            Error::MalformedStringLiteral(error) => error.span(),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Error::InvalidToken { .. }
            | Error::UnrecognizedEof { .. }
            | Error::UnrecognizedToken { .. }
            | Error::ExtraToken { .. } => None,
            Error::Lexical(error) => Some(error),
            Error::MalformedStringLiteral(error) => Some(error),
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Error::InvalidToken { location: _ } => {
                write!(f, "invalid token")
            }
            Error::UnrecognizedEof {
                location: _,
                expected,
            } => {
                write!(f, "unexpected end of file (expected one of ")?;
                for (i, item) in expected.iter().enumerate() {
                    if i != 1 {
                        write!(f, ", ")?;
                    }
                    write!(f, "{item}")?;
                }
                write!(f, ")")
            }
            Error::UnrecognizedToken {
                token: (_, token, _),
                expected,
            } => {
                write!(f, "unexpected {token} token (expected one of ")?;
                for (i, item) in expected.iter().enumerate() {
                    if i != 1 {
                        write!(f, ", ")?;
                    }
                    write!(f, "{item}")?;
                }
                write!(f, ")")
            }
            Error::ExtraToken {
                token: (_, token, _),
            } => {
                write!(f, "found a {token} after the expected end of the document")
            }
            Error::Lexical(error) => {
                write!(f, "lexing error: {error}")
            }
            Error::MalformedStringLiteral(error) => {
                write!(f, "malformed string literal: {error}")
            }
        }
    }
}

impl From<lalrpop_util::ParseError<usize, lexer::Token<'_>, AdditionalErrors>> for Error {
    fn from(value: lalrpop_util::ParseError<usize, lexer::Token<'_>, AdditionalErrors>) -> Self {
        use lalrpop_util::ParseError;
        match value {
            ParseError::InvalidToken { location } => Error::InvalidToken { location },
            ParseError::UnrecognizedEof { location, expected } => {
                Error::UnrecognizedEof { location, expected }
            }
            ParseError::UnrecognizedToken {
                token: (lspan, token, rspan),
                expected,
            } => Error::UnrecognizedToken {
                token: (lspan, token.to_string(), rspan),
                expected,
            },
            ParseError::ExtraToken {
                token: (lspan, token, rspan),
            } => Error::ExtraToken {
                token: (lspan, token.to_string(), rspan),
            },
            ParseError::User {
                error: AdditionalErrors::Lexical(error),
            } => Error::Lexical(error),
            ParseError::User {
                error: AdditionalErrors::MalformedString(error),
            } => Error::MalformedStringLiteral(error),
        }
    }
}
