use std::{fmt::Display, iter::Peekable};

use crate::frontend::{
    ast::*,
    lexer::Lexer,
    token::{Span, Token, TokenKind},
};

/// A structured error produced during parsing, carrying a human-readable
/// message and the [Span] of the offending token for precise diagnostics.
#[derive(Debug, PartialEq, Eq)]
pub struct ParseError {
    /// Human-readable description of what went wrong.
    pub message: String,
    /// Source location of the offending token.
    pub span: Span,
}

impl ParseError {
    /// Creates a new [ParserError] with the given message and source span.
    pub fn new(message: impl Display, span: Span) -> Self {
        Self {
            message: message.to_string(),
            span,
        }
    }
}

/// Convenience alias for parser operations that may fail with a [ParserError].
type ParseResult<T> = Result<T, ParseError>;

/// Consumes a [`Lexer`] token stream and builds an AST.
///
/// The parser operates as a recursive-descent parser, peeking one token
/// ahead to make branching decisions without backtracking.
pub struct Parser<'src> {
    lexer: Peekable<Lexer<'src>>,
    errors: Vec<ParseError>,
}

impl<'src> Parser<'src> {
    /// Creates a new [Parser] with the given source text.
    pub fn new(source: &'src str) -> Self {
        Self {
            lexer: Lexer::new(source).peekable(),
            errors: Vec::new(),
        }
    }

    /// Finish the parsing process returning the errors if any have occured.
    pub fn errors(self) -> Option<Vec<ParseError>> {
        (!self.errors.is_empty()).then_some(self.errors)
    }

    /// Advances the lexer and returns the next [`Token`], or `None` at end of file.
    fn advance(&mut self) -> Option<Token<'src>> {
        self.lexer.next()
    }

    /// Returns a copy of the next [Token] without consuming it, or `None` at end of file.
    fn peek(&mut self) -> Option<Token<'src>> {
        self.lexer.peek().copied()
    }

    /// Returns a copy of the next [Token], or `Err` at end of file.
    fn peek_no_eof(&mut self) -> ParseResult<Token<'src>> {
        self.peek()
            .ok_or_else(|| ParseError::new("unexpected end of file", Span::EOF))
    }

    /// Returns `true` if we have reached the end of file.
    fn is_at_eof(&mut self) -> bool {
        self.lexer.peek().is_none()
    }

    /// Returns `true` if the next [Token] matches the given [TokenKind].
    fn is_peek(&mut self, kind: TokenKind) -> bool {
        self.peek().is_some_and(|tok| tok.kind == kind)
    }

    /// Peeks at the next [Token] and returns it if it matches `expected`,
    /// otherwise returns a [ParserError] describing the mismatch or unexpected EOF.
    fn expect(&mut self, expected: TokenKind) -> ParseResult<Token<'src>> {
        let token = self.peek_no_eof()?;

        if token.kind != expected {
            return Err(ParseError::new(
                format!("expected {} but found {} instead", expected, token.kind),
                token.span,
            ));
        }

        self.advance();

        Ok(token)
    }

    /// Consumes tokens until we reach a synchronization token or we reach end of file.
    fn synchronize(&mut self, kinds: &[TokenKind]) {
        while self.peek().is_some_and(|tok| !kinds.contains(&tok.kind)) {
            self.advance();
        }
    }

    // ====== Recursive Descent Parser Implementation ======

    /// Parses a module.
    ///
    /// ```ebnf
    /// module = { decl } ;
    /// ```
    pub fn parse_module(&mut self) -> Module {
        let mut decls = Vec::new();

        while !self.is_at_eof() {
            match self.parse_decl() {
                Ok(decl) => decls.push(decl),
                Err(err) => {
                    self.errors.push(err);
                    self.synchronize(&[TokenKind::Fn, TokenKind::Foreign, TokenKind::Struct]);
                }
            }
        }

        Module { decls }
    }

    /// Parses a declaration.
    ///
    /// ```ebnf
    /// decl = function_decl ;
    /// ```
    pub fn parse_decl(&mut self) -> ParseResult<Decl> {
        let peek_token = self.peek_no_eof()?;

        match peek_token.kind {
            TokenKind::Fn => self.parse_function_decl(),
            TokenKind::Foreign => self.parse_foreign_function_decl(),
            TokenKind::Struct => self.parse_struct_decl(),

            _ => Err(ParseError::new(
                format!(
                    "expected a declaration but found {} instead",
                    peek_token.kind
                ),
                peek_token.span,
            )),
        }
    }

    /// Parses a function declaration.
    ///
    /// ```ebnf
    /// function_decl = "fn" IDENTIFIER "(" function_params ")" [ "->" type ] stmt ;
    /// ```
    fn parse_function_decl(&mut self) -> ParseResult<Decl> {
        let fn_token = self.expect(TokenKind::Fn)?;

        let (name, name_span) = {
            let ident_token = self.expect(TokenKind::Identifier)?;
            (ident_token.text.to_string(), ident_token.span)
        };

        self.expect(TokenKind::LParen)?;
        let params = self.parse_function_params()?;
        self.expect(TokenKind::RParen)?;

        let return_type = if self.is_peek(TokenKind::Arrow) {
            self.advance();
            Some(self.parse_type()?)
        } else {
            None
        };

        let body = self.parse_compound_stmt()?;
        let span = fn_token.span.join(body.span);

        Ok(Decl {
            kind: DeclKind::Function {
                name,
                name_span,
                params,
                return_type,
                body,
            },
            span,
        })
    }

    /// Parses a foreign function declaration.
    ///
    /// ```ebnf
    /// foreign_function_decl = "foreign" "fn" IDENTIFIER "(" function_params ")" [ "->" type ] ";" ;
    /// ```
    fn parse_foreign_function_decl(&mut self) -> ParseResult<Decl> {
        let foreign_token = self.expect(TokenKind::Foreign)?;
        self.expect(TokenKind::Fn)?;

        let (name, name_span) = {
            let ident_token = self.expect(TokenKind::Identifier)?;
            (ident_token.text.to_string(), ident_token.span)
        };

        self.expect(TokenKind::LParen)?;
        let params = self.parse_function_params()?;
        self.expect(TokenKind::RParen)?;

        let return_type = if self.is_peek(TokenKind::Arrow) {
            self.advance();
            Some(self.parse_type()?)
        } else {
            None
        };

        let semi_token = self.expect(TokenKind::Semicolon)?;
        let span = foreign_token.span.join(semi_token.span);

        Ok(Decl {
            kind: DeclKind::ForeignFunction {
                name,
                name_span,
                params,
                return_type,
            },
            span,
        })
    }

    /// Parses a struct declaration.
    ///
    /// ```ebnf
    /// struct_decl = "struct" IDENTIFIER "{" { struct_field } "}" ;
    /// struct_field = IDENTIFIER ":" type [ "," ] ;
    /// ```
    fn parse_struct_decl(&mut self) -> ParseResult<Decl> {
        let struct_token = self.expect(TokenKind::Struct)?;

        let (name, name_span) = {
            let ident_token = self.expect(TokenKind::Identifier)?;
            (ident_token.text.to_string(), ident_token.span)
        };

        self.expect(TokenKind::LBrace)?;

        let mut fields = Vec::new();
        while !self.is_at_eof() && !self.is_peek(TokenKind::RBrace) {
            let field_ident = self.expect(TokenKind::Identifier)?;
            self.expect(TokenKind::Colon)?;
            let ty = self.parse_type()?;

            fields.push(StructField {
                name: field_ident.text.to_string(),
                name_span: field_ident.span,
                ty,
            });

            if self.is_peek(TokenKind::Comma) {
                self.advance();
            } else {
                break;
            }
        }

        let rbrace_token = self.expect(TokenKind::RBrace)?;
        let span = struct_token.span.join(rbrace_token.span);

        Ok(Decl {
            kind: DeclKind::Struct {
                name,
                name_span,
                fields,
            },
            span,
        })
    }

    /// Parses the function parameter list.
    ///
    /// ```ebnf
    /// function_params = [ function_param { "," function_param } ] ;
    /// function_param  = IDENTIFIER ":" type ;
    /// ```
    fn parse_function_params(&mut self) -> ParseResult<Vec<FunctionParam>> {
        let mut params = Vec::new();

        while !self.is_at_eof() && !self.is_peek(TokenKind::RParen) {
            if !params.is_empty() {
                self.expect(TokenKind::Comma)?;
            }

            let (name, name_span) = {
                let ident_token = self.expect(TokenKind::Identifier)?;
                (ident_token.text.to_string(), ident_token.span)
            };

            self.expect(TokenKind::Colon)?;

            let ty = self.parse_type()?;

            params.push(FunctionParam {
                name,
                name_span,
                ty,
            });
        }

        Ok(params)
    }

    /// Parses a type.
    ///
    /// ```ebnf
    /// type = "i8" | "i16" | "i32" | "i64"
    ///      | "u8" | "u16" | "u32" | "u64"
    ///      | "f32" | "f64"
    ///      | "bool" ;
    ///      | IDENTIFIER
    /// ```
    pub fn parse_type(&mut self) -> ParseResult<Type> {
        let peek_token = self.peek_no_eof()?;

        let kind = match peek_token.kind {
            TokenKind::I8 => {
                self.advance();
                TypeKind::I8
            }
            TokenKind::I16 => {
                self.advance();
                TypeKind::I16
            }
            TokenKind::I32 => {
                self.advance();
                TypeKind::I32
            }
            TokenKind::I64 => {
                self.advance();
                TypeKind::I64
            }
            TokenKind::U8 => {
                self.advance();
                TypeKind::U8
            }
            TokenKind::U16 => {
                self.advance();
                TypeKind::U16
            }
            TokenKind::U32 => {
                self.advance();
                TypeKind::U32
            }
            TokenKind::U64 => {
                self.advance();
                TypeKind::U64
            }
            TokenKind::F32 => {
                self.advance();
                TypeKind::F32
            }
            TokenKind::F64 => {
                self.advance();
                TypeKind::F64
            }
            TokenKind::Bool => {
                self.advance();
                TypeKind::Bool
            }
            TokenKind::Identifier => {
                let token = self.advance().unwrap();
                TypeKind::Struct(token.text.to_string())
            }
            TokenKind::Star => {
                let star_token = self.advance().unwrap();
                let inner = self.parse_type()?;
                let span = star_token.span.join(inner.span);
                return Ok(Type {
                    kind: TypeKind::Pointer(Box::new(inner)),
                    span,
                });
            }
            TokenKind::LBracket => {
                let lbracket = self.advance().unwrap();
                let inner = self.parse_type()?;
                self.expect(TokenKind::Semicolon)?;
                let len_token = self.expect(TokenKind::IntegerLit)?;
                let len = len_token.text.parse().unwrap();
                let rbracket = self.expect(TokenKind::RBracket)?;
                let span = lbracket.span.join(rbracket.span);
                return Ok(Type {
                    kind: TypeKind::Array(Box::new(inner), len),
                    span,
                });
            }

            _ => {
                return Err(ParseError::new(
                    format!("expected a type but found {} instead", peek_token.kind),
                    peek_token.span,
                ));
            }
        };

        Ok(Type {
            kind,
            span: peek_token.span,
        })
    }

    /// Parses a statement.
    ///
    /// ```ebnf
    /// stmt = compound_stmt
    ///      | if_stmt
    ///      | return_stmt
    ///      | let_stmt
    ///      | while_stmt
    ///      | break_stmt
    ///      | continue_stmt
    ///      | expr_stmt
    ///      ;
    /// ```
    pub fn parse_stmt(&mut self) -> ParseResult<Stmt> {
        let peek_token = self.peek_no_eof()?;

        match peek_token.kind {
            TokenKind::LBrace => self.parse_compound_stmt(),
            TokenKind::If => self.parse_if_stmt(),
            TokenKind::While => self.parse_while_stmt(),
            TokenKind::Return => self.parse_return_stmt(),
            TokenKind::Let => self.parse_let_stmt(),
            TokenKind::Break => self.parse_break_stmt(),
            TokenKind::Continue => self.parse_continue_stmt(),
            _ => self.parse_expr_stmt(),
        }
    }

    /// Parses a compound statement.
    ///
    /// ```ebnf
    /// compound_stmt = "{" { stmt } "}";
    /// ```
    fn parse_compound_stmt(&mut self) -> ParseResult<Stmt> {
        let lbrace_token = self.expect(TokenKind::LBrace)?;
        let mut inner = Vec::new();

        while !self.is_at_eof() && !self.is_peek(TokenKind::RBrace) {
            match self.parse_stmt() {
                Ok(stmt) => inner.push(stmt),
                Err(error) => {
                    self.errors.push(error);

                    // skip ahead until we've reached a statement border
                    self.synchronize(&[TokenKind::Semicolon, TokenKind::Return, TokenKind::RBrace]);
                    if self.is_peek(TokenKind::Semicolon) {
                        self.advance();
                    }
                }
            }
        }

        let rbrace_token = self.expect(TokenKind::RBrace)?;
        let span = lbrace_token.span.join(rbrace_token.span);

        Ok(Stmt {
            kind: StmtKind::Compound { inner },
            span,
        })
    }

    /// Parses an if statement.
    ///
    /// ```ebnf
    /// if_stmt = "if" expr compound_stmt [ "else" ( if_stmt | compound_stmt ) ] ;
    /// ```
    fn parse_if_stmt(&mut self) -> ParseResult<Stmt> {
        let if_token = self.expect(TokenKind::If)?;

        let condition = self.parse_expr_no_struct()?;
        let consequence = self.parse_compound_stmt()?;

        let alternative = if self.is_peek(TokenKind::Else) {
            self.advance();

            Some(if self.is_peek(TokenKind::If) {
                self.parse_if_stmt()
            } else {
                self.parse_compound_stmt()
            }?)
        } else {
            None
        };

        let span = if_token.span.join(
            alternative
                .as_ref()
                .map(|stmt| stmt.span)
                .unwrap_or(consequence.span),
        );

        Ok(Stmt {
            kind: StmtKind::If {
                condition,
                then: Box::new(consequence),
                elze: alternative.map(Box::new),
            },
            span,
        })
    }

    /// Parses a while statement.
    ///
    /// ```ebnf
    /// while_stmt = "while" expr compound_stmt ;
    /// ```
    fn parse_while_stmt(&mut self) -> ParseResult<Stmt> {
        let while_token = self.expect(TokenKind::While)?;
        let condition = self.parse_expr_no_struct()?;
        let body = self.parse_compound_stmt()?;
        let span = while_token.span.join(body.span);

        Ok(Stmt {
            kind: StmtKind::While {
                condition,
                body: Box::new(body),
            },
            span,
        })
    }

    /// Parses an expression statement.
    ///
    /// ```ebnf
    /// expr_stmt = expr ";" ;
    /// ```
    fn parse_expr_stmt(&mut self) -> ParseResult<Stmt> {
        let expr = self.parse_expr()?;
        let semi_token = self.expect(TokenKind::Semicolon)?;
        let span = expr.span.join(semi_token.span);

        Ok(Stmt {
            kind: StmtKind::Expression { expr },
            span,
        })
    }

    /// Parses a return statement.
    ///
    /// ```ebnf
    /// return_stmt = "return" [ expr ] ";" ;
    /// ```
    fn parse_return_stmt(&mut self) -> ParseResult<Stmt> {
        let return_token = self.expect(TokenKind::Return)?;

        let value = if !self.is_peek(TokenKind::Semicolon) {
            Some(self.parse_expr()?)
        } else {
            None
        };

        let semi_token = self.expect(TokenKind::Semicolon)?;
        let span = return_token.span.join(semi_token.span);

        Ok(Stmt {
            kind: StmtKind::Return { value },
            span,
        })
    }

    /// Parses a let statement.
    ///
    /// ```ebnf
    /// let_stmt = "let" IDENTIFIER [ ":" type ] [ "=" expr ] ";" ;
    /// ```
    fn parse_let_stmt(&mut self) -> ParseResult<Stmt> {
        let let_token = self.expect(TokenKind::Let)?;

        let (name, name_span) = {
            let ident_token = self.expect(TokenKind::Identifier)?;
            (ident_token.text.to_string(), ident_token.span)
        };

        let ty = if self.is_peek(TokenKind::Colon) {
            self.advance();
            Some(self.parse_type()?)
        } else {
            None
        };

        let initializer = if self.is_peek(TokenKind::Assign) {
            self.advance();
            Some(self.parse_expr()?)
        } else {
            None
        };

        let semi_token = self.expect(TokenKind::Semicolon)?;
        let span = let_token.span.join(semi_token.span);

        Ok(Stmt {
            kind: StmtKind::Let {
                name,
                name_span,
                ty,
                initializer,
            },
            span,
        })
    }

    /// Parses a break statement.
    ///
    /// ```ebnf
    /// break_stmt = "break" ";" ;
    /// ```
    fn parse_break_stmt(&mut self) -> ParseResult<Stmt> {
        let break_token = self.expect(TokenKind::Break)?;
        let semi_token = self.expect(TokenKind::Semicolon)?;
        let span = break_token.span.join(semi_token.span);

        Ok(Stmt {
            kind: StmtKind::Break,
            span,
        })
    }

    /// Parses a continue statement.
    ///
    /// ```ebnf
    /// continue_stmt = "continue" ";" ;
    /// ```
    fn parse_continue_stmt(&mut self) -> ParseResult<Stmt> {
        let continue_token = self.expect(TokenKind::Continue)?;
        let semi_token = self.expect(TokenKind::Semicolon)?;
        let span = continue_token.span.join(semi_token.span);

        Ok(Stmt {
            kind: StmtKind::Continue,
            span,
        })
    }

    // ====== Pratt Parsing Implementation ======

    /// Parses an expression.
    pub fn parse_expr(&mut self) -> ParseResult<Expr> {
        self.parse_expr_bp(0, true)
    }

    /// Parses an expression, disallowing struct literals.
    pub fn parse_expr_no_struct(&mut self) -> ParseResult<Expr> {
        self.parse_expr_bp(0, false)
    }

    /// Pratt parsing implementation for expressions.
    fn parse_expr_bp(&mut self, min_bp: u8, allow_struct: bool) -> ParseResult<Expr> {
        let mut lhs = self.parse_leading_expr(allow_struct)?;

        loop {
            let Some(peek_token) = self.peek() else {
                break;
            };

            if peek_token.kind == TokenKind::Assign {
                let left_bp = 2;
                let right_bp = 1; // Right-associative mapping

                if left_bp < min_bp {
                    break;
                }

                self.advance(); // consume '='
                let rhs = self.parse_expr_bp(right_bp, allow_struct)?;
                let span = lhs.span.join(rhs.span);

                lhs = Expr {
                    kind: ExprKind::Assign {
                        lval: Box::new(lhs),
                        rval: Box::new(rhs),
                    },
                    ty: (),
                    span,
                };
                continue;
            }

            if peek_token.kind == TokenKind::As {
                let left_bp = 25; // high precedence, tighter than multiply
                if left_bp < min_bp {
                    break;
                }
                self.advance(); // consume `as`

                let ty = self.parse_type()?;
                let span = lhs.span.join(ty.span);

                lhs = Expr {
                    kind: ExprKind::Cast {
                        expr: Box::new(lhs),
                        ty,
                    },
                    ty: (),
                    span,
                };

                continue;
            }

            if peek_token.kind == TokenKind::LParen {
                let left_bp = 30; // Function calls have very high precedence
                if left_bp < min_bp {
                    break;
                }
                self.advance(); // consume `(`

                let mut args = Vec::new();
                if !self.is_peek(TokenKind::RParen) {
                    loop {
                        args.push(self.parse_expr()?);
                        if !self.is_peek(TokenKind::Comma) {
                            break;
                        }
                        self.advance(); // consume `,`
                    }
                }

                let rparen_token = self.expect(TokenKind::RParen)?;
                let span = lhs.span.join(rparen_token.span);

                lhs = Expr {
                    kind: ExprKind::Call {
                        callee: Box::new(lhs),
                        args,
                    },
                    ty: (),
                    span,
                };
                continue;
            }

            if peek_token.kind == TokenKind::Dot {
                let left_bp = 30; // Field access has very high precedence
                if left_bp < min_bp {
                    break;
                }
                self.advance(); // consume `.`

                let field_token = self.expect(TokenKind::Identifier)?;
                let span = lhs.span.join(field_token.span);

                lhs = Expr {
                    kind: ExprKind::FieldAccess {
                        expr: Box::new(lhs),
                        field: field_token.text.to_string(),
                        field_span: field_token.span,
                    },
                    ty: (),
                    span,
                };
                continue;
            }

            if peek_token.kind == TokenKind::LBracket {
                let left_bp = 30; // Array indexing has high precedence
                if left_bp < min_bp {
                    break;
                }
                self.advance(); // consume `[`

                let index = self.parse_expr()?;
                let rbracket_token = self.expect(TokenKind::RBracket)?;
                let span = lhs.span.join(rbracket_token.span);

                lhs = Expr {
                    kind: ExprKind::Index {
                        array: Box::new(lhs),
                        index: Box::new(index),
                    },
                    ty: (),
                    span,
                };
                continue;
            }

            let Some((op, left_bp, right_bp)) = self.infix_operator(peek_token.kind) else {
                break; // Not an infix operator
            };

            if left_bp < min_bp {
                break; // The operator binds less tightly than the current context
            }

            self.advance(); // consume the operator

            let rhs = self.parse_expr_bp(right_bp, allow_struct)?;
            let span = lhs.span.join(rhs.span);

            lhs = Expr {
                kind: ExprKind::Binary {
                    op,
                    lhs: Box::new(lhs),
                    rhs: Box::new(rhs),
                },
                ty: (),
                span,
            };
        }

        Ok(lhs)
    }

    /// Parses a leading expression such as identifiers, integer and boolean literals
    /// or prefix expressions.
    fn parse_leading_expr(&mut self, allow_struct: bool) -> ParseResult<Expr> {
        let peek_token = self.peek_no_eof()?;

        match peek_token.kind {
            TokenKind::Identifier => {
                let token = self.advance().unwrap();

                if allow_struct && self.is_peek(TokenKind::LBrace) {
                    self.advance(); // consume `{`

                    let mut fields = Vec::new();
                    while !self.is_at_eof() && !self.is_peek(TokenKind::RBrace) {
                        let field_token = self.expect(TokenKind::Identifier)?;
                        self.expect(TokenKind::Colon)?;
                        let value = self.parse_expr()?;

                        fields.push(FieldValue {
                            name: field_token.text.to_string(),
                            name_span: field_token.span,
                            value,
                        });

                        if self.is_peek(TokenKind::Comma) {
                            self.advance();
                        } else {
                            break;
                        }
                    }

                    let rbrace_token = self.expect(TokenKind::RBrace)?;
                    let span = token.span.join(rbrace_token.span);

                    Ok(Expr {
                        kind: ExprKind::Struct {
                            name: token.text.to_string(),
                            name_span: token.span,
                            fields,
                        },
                        ty: (),
                        span,
                    })
                } else {
                    Ok(Expr {
                        kind: ExprKind::Identifier {
                            name: token.text.to_string(),
                        },
                        ty: (),
                        span: token.span,
                    })
                }
            }

            TokenKind::IntegerLit => {
                let token = self.advance().unwrap();
                let text = token.text;

                let value = if text.starts_with("0x") || text.starts_with("0X") {
                    u64::from_str_radix(&text[2..], 16)
                } else if text.starts_with("0o") || text.starts_with("0O") {
                    u64::from_str_radix(&text[2..], 8)
                } else if text.starts_with("0b") || text.starts_with("0B") {
                    u64::from_str_radix(&text[2..], 2)
                } else {
                    text.parse()
                }
                .unwrap();

                Ok(Expr {
                    kind: ExprKind::Integer { value },
                    ty: (),
                    span: token.span,
                })
            }

            TokenKind::FloatLit => {
                let token = self.advance().unwrap();
                let value = token.text.parse().unwrap();

                Ok(Expr {
                    kind: ExprKind::Float { value },
                    ty: (),
                    span: token.span,
                })
            }

            TokenKind::BooleanLit => {
                let token = self.advance().unwrap();

                Ok(Expr {
                    kind: ExprKind::Boolean {
                        value: token.text == "true",
                    },
                    ty: (),
                    span: token.span,
                })
            }

            TokenKind::LParen => {
                let lparen = self.advance().unwrap();
                let expr = self.parse_expr()?; // Inner expressions allow struct literals
                let rparen = self.expect(TokenKind::RParen)?;

                Ok(Expr {
                    kind: expr.kind,
                    ty: (),
                    span: lparen.span.join(rparen.span),
                })
            }

            TokenKind::LBracket => {
                let lbracket = self.advance().unwrap();
                let mut elements = Vec::new();

                if !self.is_peek(TokenKind::RBracket) {
                    loop {
                        elements.push(self.parse_expr()?);
                        if !self.is_peek(TokenKind::Comma) {
                            break;
                        }
                        self.advance(); // consume `,`
                    }
                }

                let rbracket = self.expect(TokenKind::RBracket)?;
                let span = lbracket.span.join(rbracket.span);

                Ok(Expr {
                    kind: ExprKind::Array { elements },
                    ty: (),
                    span,
                })
            }

            kind if let Some((op, r_bp)) = self.prefix_operator(kind) => {
                let op_token = self.advance().unwrap();
                let rhs = self.parse_expr_bp(r_bp, allow_struct)?;

                Ok(Expr {
                    ty: (),
                    span: op_token.span.join(rhs.span),
                    kind: ExprKind::Unary {
                        op,
                        expr: Box::new(rhs),
                    },
                })
            }

            _ => Err(ParseError::new(
                format!("expected an expression but found {}", peek_token.kind),
                peek_token.span,
            )),
        }
    }

    /// Returns the [UnaryOp] and right binding power of a prefix operator,
    /// or `None` if the [TokenKind] is not a valid prefix operator.
    fn prefix_operator(&self, op: TokenKind) -> Option<(UnaryOp, u8)> {
        match op {
            TokenKind::Minus => Some((UnaryOp::Neg, 30)),
            TokenKind::Bang => Some((UnaryOp::Not, 30)),
            TokenKind::Star => Some((UnaryOp::Deref, 30)),
            TokenKind::Ampersand => Some((UnaryOp::AddressOf, 30)),

            _ => None,
        }
    }

    /// Returns the [BinaryOp], left and right binding powers of an infix operator,
    /// or `None` if the [TokenKind] is not a valid infix operator.
    fn infix_operator(&self, op: TokenKind) -> Option<(BinaryOp, u8, u8)> {
        match op {
            TokenKind::Equal => Some((BinaryOp::Eq, 5, 6)),
            TokenKind::Unequal => Some((BinaryOp::Neq, 5, 6)),
            TokenKind::LessThan => Some((BinaryOp::Lt, 5, 6)),
            TokenKind::LessEqual => Some((BinaryOp::Le, 5, 6)),
            TokenKind::GreaterThan => Some((BinaryOp::Gt, 5, 6)),
            TokenKind::GreaterEqual => Some((BinaryOp::Ge, 5, 6)),

            TokenKind::Plus => Some((BinaryOp::Add, 10, 11)),
            TokenKind::Minus => Some((BinaryOp::Sub, 10, 11)),

            TokenKind::Star => Some((BinaryOp::Mul, 20, 21)),
            TokenKind::Slash => Some((BinaryOp::Div, 20, 21)),
            TokenKind::Percent => Some((BinaryOp::Rem, 20, 21)),

            _ => None,
        }
    }
}

#[cfg(test)]
mod test {
    use std::fmt::Debug;

    use crate::frontend::{
        ast::*,
        parser::{ParseError, ParseResult, Parser},
        token::Span,
    };

    #[derive(Debug, PartialEq)]
    enum TestResult<T: Debug + PartialEq> {
        Success(T),
        Recovered(T, Vec<ParseError>),
        Error(Vec<ParseError>),
    }

    use TestResult::*;

    fn parse<'src, T: Debug + PartialEq>(
        source: &'src str,
        method: impl Fn(&mut Parser<'src>) -> ParseResult<T>,
    ) -> TestResult<T> {
        let mut parser = Parser::new(source);

        match method(&mut parser) {
            Ok(result) => {
                if parser.errors.is_empty() {
                    TestResult::Success(result)
                } else {
                    TestResult::Recovered(result, parser.errors)
                }
            }
            Err(error) => {
                parser.errors.push(error);
                TestResult::Error(parser.errors)
            }
        }
    }

    #[test]
    fn integer_literals() {
        assert_eq!(
            parse("0xBEEF;", Parser::parse_expr),
            Success(Expr {
                kind: ExprKind::Integer { value: 0xBEEF },
                ty: (),
                span: Span::new(0, 6)
            })
        );

        assert_eq!(
            parse("0o777;", Parser::parse_expr),
            Success(Expr {
                kind: ExprKind::Integer { value: 0o777 },
                ty: (),
                span: Span::new(0, 5)
            })
        );

        assert_eq!(
            parse("0b1001;", Parser::parse_expr),
            Success(Expr {
                kind: ExprKind::Integer { value: 0b1001 },
                ty: (),
                span: Span::new(0, 6)
            })
        );

        assert_eq!(
            parse("1337;", Parser::parse_expr),
            Success(Expr {
                kind: ExprKind::Integer { value: 1337 },
                ty: (),
                span: Span::new(0, 4)
            })
        );
    }

    #[test]
    fn float_literals() {
        assert_eq!(
            parse("3.14;", Parser::parse_expr),
            Success(Expr {
                kind: ExprKind::Float { value: 3.14 },
                ty: (),
                span: Span::new(0, 4)
            })
        );
    }

    #[test]
    fn boolean_literals() {
        assert_eq!(
            parse("true;", Parser::parse_expr),
            Success(Expr {
                kind: ExprKind::Boolean { value: true },
                ty: (),
                span: Span::new(0, 4)
            })
        );

        assert_eq!(
            parse("false;", Parser::parse_expr),
            Success(Expr {
                kind: ExprKind::Boolean { value: false },
                ty: (),
                span: Span::new(0, 5)
            })
        );
    }

    #[test]
    fn unary_expr() {
        assert_eq!(
            parse("-5;", Parser::parse_expr),
            Success(Expr {
                kind: ExprKind::Unary {
                    op: UnaryOp::Neg,
                    expr: Box::new(Expr {
                        kind: ExprKind::Integer { value: 5 },
                        ty: (),
                        span: Span::new(1, 2)
                    })
                },
                ty: (),
                span: Span::new(0, 2)
            })
        );
    }

    #[test]
    fn binary_expr() {
        assert_eq!(
            parse("12 + 3 * 6;", Parser::parse_expr),
            Success(Expr {
                kind: ExprKind::Binary {
                    op: BinaryOp::Add,
                    lhs: Box::new(Expr {
                        kind: ExprKind::Integer { value: 12 },
                        ty: (),
                        span: Span::new(0, 2)
                    }),
                    rhs: Box::new(Expr {
                        kind: ExprKind::Binary {
                            op: BinaryOp::Mul,
                            lhs: Box::new(Expr {
                                kind: ExprKind::Integer { value: 3 },
                                ty: (),
                                span: Span::new(5, 6)
                            }),
                            rhs: Box::new(Expr {
                                kind: ExprKind::Integer { value: 6 },
                                ty: (),
                                span: Span::new(9, 10)
                            })
                        },
                        ty: (),
                        span: Span::new(5, 10)
                    })
                },
                ty: (),
                span: Span::new(0, 10)
            })
        );
    }

    #[test]
    fn return_stmt() {
        assert_eq!(
            parse("return;", Parser::parse_stmt),
            Success(Stmt {
                kind: StmtKind::Return { value: None },
                span: Span::new(0, 7)
            })
        );

        assert_eq!(
            parse("return 0;", Parser::parse_stmt),
            Success(Stmt {
                kind: StmtKind::Return {
                    value: Some(Expr {
                        kind: ExprKind::Integer { value: 0 },
                        ty: (),
                        span: Span::new(7, 8)
                    })
                },
                span: Span::new(0, 9)
            })
        );
    }

    #[test]
    fn if_stmt() {
        assert_eq!(
            parse("if true { return; }", Parser::parse_stmt),
            Success(Stmt {
                kind: StmtKind::If {
                    condition: Expr {
                        kind: ExprKind::Boolean { value: true },
                        ty: (),
                        span: Span::new(3, 7)
                    },
                    then: Box::new(Stmt {
                        kind: StmtKind::Compound {
                            inner: vec![Stmt {
                                kind: StmtKind::Return { value: None },
                                span: Span::new(10, 17)
                            }]
                        },
                        span: Span::new(8, 19)
                    }),
                    elze: None
                },
                span: Span::new(0, 19)
            })
        )
    }

    #[test]
    fn compound_stmt() {
        assert_eq!(
            parse("{ return; }", Parser::parse_stmt),
            Success(Stmt {
                kind: StmtKind::Compound {
                    inner: vec![Stmt {
                        kind: StmtKind::Return { value: None },
                        span: Span::new(2, 9)
                    }]
                },
                span: Span::new(0, 11)
            })
        );

        assert_eq!(
            parse("{ return 0 }", Parser::parse_stmt),
            Recovered(
                Stmt {
                    kind: StmtKind::Compound { inner: vec![] },
                    span: Span::new(0, 12)
                },
                vec![ParseError::new(
                    "expected `;` but found `}` instead",
                    Span::new(11, 12)
                )]
            )
        );
    }

    #[test]
    fn let_stmt() {
        assert_eq!(
            parse("let a: i32 = 5;", Parser::parse_stmt),
            Success(Stmt {
                kind: StmtKind::Let {
                    name: "a".to_string(),
                    name_span: Span::new(4, 5),
                    ty: Some(Type {
                        kind: TypeKind::I32,
                        span: Span::new(7, 10),
                    }),
                    initializer: Some(Expr {
                        kind: ExprKind::Integer { value: 5 },
                        ty: (),
                        span: Span::new(13, 14),
                    }),
                },
                span: Span::new(0, 15)
            })
        );

        assert_eq!(
            parse("let a;", Parser::parse_stmt),
            Success(Stmt {
                kind: StmtKind::Let {
                    name: "a".to_string(),
                    name_span: Span::new(4, 5),
                    ty: None,
                    initializer: None,
                },
                span: Span::new(0, 6)
            })
        );
    }

    #[test]
    fn let_stmt_float() {
        assert_eq!(
            parse("let a: f32 = 3.14;", Parser::parse_stmt),
            Success(Stmt {
                kind: StmtKind::Let {
                    name: "a".to_string(),
                    name_span: Span::new(4, 5),
                    ty: Some(Type {
                        kind: TypeKind::F32,
                        span: Span::new(7, 10),
                    }),
                    initializer: Some(Expr {
                        kind: ExprKind::Float { value: 3.14 },
                        ty: (),
                        span: Span::new(13, 17),
                    }),
                },
                span: Span::new(0, 18)
            })
        );
    }

    #[test]
    fn while_stmt() {
        assert_eq!(
            parse("while true { break; }", Parser::parse_stmt),
            Success(Stmt {
                kind: StmtKind::While {
                    condition: Expr {
                        kind: ExprKind::Boolean { value: true },
                        ty: (),
                        span: Span::new(6, 10)
                    },
                    body: Box::new(Stmt {
                        kind: StmtKind::Compound {
                            inner: vec![Stmt {
                                kind: StmtKind::Break,
                                span: Span::new(13, 19)
                            }]
                        },
                        span: Span::new(11, 21)
                    }),
                },
                span: Span::new(0, 21)
            })
        )
    }

    #[test]
    fn break_stmt() {
        assert_eq!(
            parse("break;", Parser::parse_stmt),
            Success(Stmt {
                kind: StmtKind::Break,
                span: Span::new(0, 6)
            })
        );
    }

    #[test]
    fn continue_stmt() {
        assert_eq!(
            parse("continue;", Parser::parse_stmt),
            Success(Stmt {
                kind: StmtKind::Continue,
                span: Span::new(0, 9)
            })
        );
    }

    #[test]
    fn assign_expr_stmt() {
        assert_eq!(
            parse("a = b = 5;", Parser::parse_stmt),
            Success(Stmt {
                kind: StmtKind::Expression {
                    expr: Expr {
                        kind: ExprKind::Assign {
                            lval: Box::new(Expr {
                                kind: ExprKind::Identifier {
                                    name: "a".to_string()
                                },
                                ty: (),
                                span: Span::new(0, 1)
                            }),
                            rval: Box::new(Expr {
                                kind: ExprKind::Assign {
                                    lval: Box::new(Expr {
                                        kind: ExprKind::Identifier {
                                            name: "b".to_string()
                                        },
                                        ty: (),
                                        span: Span::new(4, 5)
                                    }),
                                    rval: Box::new(Expr {
                                        kind: ExprKind::Integer { value: 5 },
                                        ty: (),
                                        span: Span::new(8, 9)
                                    })
                                },
                                ty: (),
                                span: Span::new(4, 9)
                            })
                        },
                        ty: (),
                        span: Span::new(0, 9)
                    }
                },
                span: Span::new(0, 10)
            })
        );
    }

    #[test]
    fn function_decl() {
        assert_eq!(
            parse(
                "fn add(a: i32, b: i32) -> i32 { return a + b; }",
                Parser::parse_decl
            ),
            Success(Decl {
                kind: DeclKind::Function {
                    name: "add".to_string(),
                    name_span: Span::new(3, 6),
                    params: vec![
                        FunctionParam {
                            name: "a".to_string(),
                            name_span: Span::new(7, 8),
                            ty: Type {
                                kind: TypeKind::I32,
                                span: Span::new(10, 13)
                            }
                        },
                        FunctionParam {
                            name: "b".to_string(),
                            name_span: Span::new(15, 16),
                            ty: Type {
                                kind: TypeKind::I32,
                                span: Span::new(18, 21)
                            }
                        }
                    ],
                    return_type: Some(Type {
                        kind: TypeKind::I32,
                        span: Span::new(26, 29)
                    }),
                    body: Stmt {
                        kind: StmtKind::Compound {
                            inner: vec![Stmt {
                                kind: StmtKind::Return {
                                    value: Some(Expr {
                                        kind: ExprKind::Binary {
                                            op: BinaryOp::Add,
                                            lhs: Box::new(Expr {
                                                kind: ExprKind::Identifier {
                                                    name: "a".to_string()
                                                },
                                                ty: (),
                                                span: Span::new(39, 40)
                                            }),
                                            rhs: Box::new(Expr {
                                                kind: ExprKind::Identifier {
                                                    name: "b".to_string()
                                                },
                                                ty: (),
                                                span: Span::new(43, 44)
                                            })
                                        },
                                        ty: (),
                                        span: Span::new(39, 44)
                                    })
                                },
                                span: Span::new(32, 45)
                            }]
                        },
                        span: Span::new(30, 47)
                    }
                },
                span: Span::new(0, 47)
            })
        )
    }

    #[test]
    fn comparison_expr() {
        assert_eq!(
            parse("a >= 5;", Parser::parse_expr),
            Success(Expr {
                kind: ExprKind::Binary {
                    op: BinaryOp::Ge,
                    lhs: Box::new(Expr {
                        kind: ExprKind::Identifier {
                            name: "a".to_string()
                        },
                        ty: (),
                        span: Span::new(0, 1)
                    }),
                    rhs: Box::new(Expr {
                        kind: ExprKind::Integer { value: 5 },
                        ty: (),
                        span: Span::new(5, 6)
                    })
                },
                ty: (),
                span: Span::new(0, 6)
            })
        );
    }

    #[test]
    fn logical_not_expr() {
        assert_eq!(
            parse("!true;", Parser::parse_expr),
            Success(Expr {
                kind: ExprKind::Unary {
                    op: UnaryOp::Not,
                    expr: Box::new(Expr {
                        kind: ExprKind::Boolean { value: true },
                        ty: (),
                        span: Span::new(1, 5)
                    })
                },
                ty: (),
                span: Span::new(0, 5)
            })
        );
    }

    #[test]
    fn cast_expr() {
        assert_eq!(
            parse("5 as f32;", Parser::parse_expr),
            Success(Expr {
                kind: ExprKind::Cast {
                    expr: Box::new(Expr {
                        kind: ExprKind::Integer { value: 5 },
                        ty: (),
                        span: Span::new(0, 1)
                    }),
                    ty: Type {
                        kind: TypeKind::F32,
                        span: Span::new(5, 8)
                    }
                },
                ty: (),
                span: Span::new(0, 8)
            })
        );
    }

    #[test]
    fn struct_decl() {
        assert_eq!(
            parse("struct Vec3 { x: f32, y: f32 }", Parser::parse_decl),
            Success(Decl {
                kind: DeclKind::Struct {
                    name: "Vec3".to_string(),
                    name_span: Span::new(7, 11),
                    fields: vec![
                        StructField {
                            name: "x".to_string(),
                            name_span: Span::new(14, 15),
                            ty: Type {
                                kind: TypeKind::F32,
                                span: Span::new(17, 20)
                            }
                        },
                        StructField {
                            name: "y".to_string(),
                            name_span: Span::new(22, 23),
                            ty: Type {
                                kind: TypeKind::F32,
                                span: Span::new(25, 28)
                            }
                        }
                    ],
                },
                span: Span::new(0, 30)
            })
        );
    }

    #[test]
    fn struct_literal() {
        assert_eq!(
            parse("Vec3 { x: 1.0, y: -3.5 }", Parser::parse_expr),
            Success(Expr {
                kind: ExprKind::Struct {
                    name: "Vec3".to_string(),
                    name_span: Span::new(0, 4),
                    fields: vec![
                        FieldValue {
                            name: "x".to_string(),
                            name_span: Span::new(7, 8),
                            value: Expr {
                                kind: ExprKind::Float { value: 1.0 },
                                ty: (),
                                span: Span::new(10, 13)
                            }
                        },
                        FieldValue {
                            name: "y".to_string(),
                            name_span: Span::new(15, 16),
                            value: Expr {
                                kind: ExprKind::Unary {
                                    op: UnaryOp::Neg,
                                    expr: Box::new(Expr {
                                        kind: ExprKind::Float { value: 3.5 },
                                        ty: (),
                                        span: Span::new(19, 22)
                                    })
                                },
                                ty: (),
                                span: Span::new(18, 22)
                            }
                        }
                    ]
                },
                ty: (),
                span: Span::new(0, 24)
            })
        );
    }

    #[test]
    fn field_access() {
        assert_eq!(
            parse("a.b.c", Parser::parse_expr),
            Success(Expr {
                kind: ExprKind::FieldAccess {
                    expr: Box::new(Expr {
                        kind: ExprKind::FieldAccess {
                            expr: Box::new(Expr {
                                kind: ExprKind::Identifier {
                                    name: "a".to_string()
                                },
                                ty: (),
                                span: Span::new(0, 1)
                            }),
                            field: "b".to_string(),
                            field_span: Span::new(2, 3),
                        },
                        ty: (),
                        span: Span::new(0, 3)
                    }),
                    field: "c".to_string(),
                    field_span: Span::new(4, 5),
                },
                ty: (),
                span: Span::new(0, 5)
            })
        );
    }
}
