#![allow(clippy::result_large_err)] // We accept large errors on the stack for now. Boxing all error types is a pain.

pub mod ast;
pub mod cursor;
pub mod parse_error;
mod parse_error_ext;

pub use ast::*;
pub use parse_error::{ParseError, ParseErrorKind};

use crate::{Location, Source, Token, TokenKind, Tokens};
use cursor::Cursor;
use miette::Result;
use parse_error_ext::ParseErrorExt;

/// Maximum nesting depth for block expressions.
const MAX_EXPR_DEPTH: u64 = 50;

/// Nexus parser.
pub struct Parser<'a> {
    source: Source<'a>,
    cursor: Cursor,
    depth: u64,
    expr_depth: u64,
}

impl<'a> Parser<'a> {
    /// Create a new Parser.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::{Parser, Scanner, Source};
    ///
    /// let source = Source::new("let x = 42;");
    /// let tokens = Scanner::new(source).scan().expect("Scanning failed");
    /// let parser = Parser::new(source, tokens);
    /// ```
    #[must_use]
    pub fn new(source: Source<'a>, tokens: Tokens) -> Self {
        Parser {
            source,
            cursor: Cursor::new(tokens),
            depth: 0,
            expr_depth: 0,
        }
    }

    /// Parse the sources/token sequence and generate an AST.
    ///
    /// # Errors
    ///
    /// This function will return a `ParseError` if the token sequence can not be parsed properly.
    ///
    /// Note: The current implementation will bail out at the first error it encounters.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::{Parser, Scanner, Source};
    ///
    /// let source = Source::new("print 42;");
    /// let tokens = Scanner::new(source).scan().expect("Scanning failed");
    /// let mut parser = Parser::new(source, tokens);
    /// let ast = parser.parse().expect("Parsing failed");
    /// ```
    pub fn parse(&mut self) -> Result<Ast, ParseError> {
        if self.cursor.is_eos() {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedEndOfStream,
                Some("Expected at least a single statement/expression."),
            ));
        }

        let result = self.parse_program()?;

        if !self.cursor.is_eos() {
            let trailing_token = self.cursor.value();

            return Err(self.error(
                trailing_token,
                ParseErrorKind::UnexpectedTrailingToken,
                Some("...Uhhh, well, this is awkward; trailing unparsed tokens :-/"),
            ));
        }

        Ok(result)
    }

    /// Parse a program (i.e. one or more statements).
    fn parse_program(&mut self) -> Result<Ast, ParseError> {
        let mut result = Vec::new();

        loop {
            match self.parse_decl() {
                Ok(stmt) => {
                    result.push(stmt);
                }
                Err(e) => match e.kind() {
                    ParseErrorKind::EndOfStream => break,
                    _ => {
                        return Err(self.error_with_cause(
                            self.cursor.value(),
                            ParseErrorKind::InvalidStatement,
                            Some("Failed to parse statement."),
                            e,
                            Some("Consult the Nexus documentation to learn how to write correct programs."),
                        ));
                    }
                },
            }
        }

        Ok(result)
    }

    /// Parse a declaration statement.
    fn parse_decl(&mut self) -> Result<Stmt, ParseError> {
        let result = if self.cursor.matches(&[TokenKind::Const]) {
            self.parse_const_decl()?
        } else if self.cursor.matches(&[TokenKind::Function]) {
            self.parse_fn_decl()?
        } else if self.cursor.matches(&[TokenKind::Let]) {
            self.parse_var_decl()?
        } else if self.cursor.matches(&[TokenKind::Node])
            && self
                .cursor
                .peek()
                .is_some_and(|t| t.kind == TokenKind::Type)
        {
            self.parse_node_type_decl()?
        } else if self.cursor.matches(&[TokenKind::Use]) {
            self.parse_use_decl()?
        } else {
            self.parse_stmt()?
        };

        Ok(result)
    }

    /// Parse a constant declaration statement.
    fn parse_const_decl(&mut self) -> Result<Stmt, ParseError> {
        let token = self.cursor.value().cloned();

        if !self.cursor.consume(TokenKind::Const) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected the 'const' keyword."),
            ));
        }

        let name_token = self.cursor.value().cloned();
        let name = self.parse_ident()?;

        if !&name
            .chars()
            .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_')
        {
            return Err(self.error(
                name_token.as_ref(),
                ParseErrorKind::InvalidConstantName(name),
                Some("Constant names must be in SCREAMING_SNAKE_CASE."),
            ));
        }

        if !self.cursor.consume(TokenKind::Colon) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected a colon ':' after the constant identifier."),
            ));
        }

        let type_id = self.parse_type_id()?;

        if !self.cursor.consume(TokenKind::Is) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected an assignment after constant type identifier."),
            ));
        }

        let expr = self.parse_expr()?;

        if !self.cursor.consume(TokenKind::Semicolon) {
            let token = token.as_ref();
            return Err(self.error(
                token,
                ParseErrorKind::MissingSemicolonAfterStatement(token.unwrap().lexeme.clone()),
                None,
            ));
        }

        let loc = token
            .as_ref()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        Ok(Stmt::ConstDecl {
            name,
            type_id,
            expr: Box::new(expr),
            loc,
        })
    }

    /// Parse a variable declaration statement.
    fn parse_var_decl(&mut self) -> Result<Stmt, ParseError> {
        let token = self.cursor.value().cloned();

        if !self.cursor.consume(TokenKind::Let) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected the 'let' keyword."),
            ));
        }

        let mutable = self.cursor.consume(TokenKind::Mut);

        let name = if self.cursor.consume(TokenKind::Underscore) {
            "_".to_owned()
        } else {
            self.parse_ident()?
        };

        let type_id = if self.cursor.consume(TokenKind::Colon) {
            Some(self.parse_type_id()?)
        } else {
            None
        };

        let expr = if self.cursor.consume(TokenKind::Is) {
            Some(Box::new(self.parse_expr()?))
        } else {
            None
        };

        if !self.cursor.consume(TokenKind::Semicolon) {
            let token = token.as_ref();
            return Err(self.error(
                token,
                ParseErrorKind::MissingSemicolonAfterStatement(token.unwrap().lexeme.clone()),
                None,
            ));
        }

        let loc = token
            .as_ref()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        Ok(Stmt::VarDecl {
            name,
            mutable,
            type_id,
            expr,
            loc,
        })
    }

    /// Parse a function declaration statement.
    fn parse_fn_decl(&mut self) -> Result<Stmt, ParseError> {
        let token = self.cursor.value().cloned();

        if !self.cursor.consume(TokenKind::Function) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected the 'fn' keyword."),
            ));
        }

        let name = self.parse_ident()?;

        if !self.cursor.consume(TokenKind::LParen) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected '(' after function name."),
            ));
        }

        let mut params = Vec::new();

        while !self.cursor.matches(&[TokenKind::RParen]) && !self.cursor.is_eos() {
            let param_name = self.parse_ident()?;

            if !self.cursor.consume(TokenKind::Colon) {
                return Err(self.error(
                    self.cursor.value(),
                    ParseErrorKind::UnexpectedToken,
                    Some("Expected ':' after parameter name."),
                ));
            }

            let param_type = self.parse_type_id()?;
            params.push(Parameter {
                name: param_name,
                type_id: param_type,
            });

            if !self.cursor.advance_if_matches(&[TokenKind::Comma]) {
                break;
            }
        }

        if !self.cursor.consume(TokenKind::RParen) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected ')' after function parameters."),
            ));
        }

        let return_type = if self.cursor.consume(TokenKind::Arrow) {
            Some(self.parse_type_id()?)
        } else {
            None
        };

        if !self.cursor.matches(&[TokenKind::LBrace]) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected '{' for function body."),
            ));
        }

        let body = Box::new(self.parse_block()?);

        let loc = token
            .as_ref()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        Ok(Stmt::FnDecl {
            name,
            params,
            return_type,
            body,
            loc,
        })
    }

    /// Parse a node type declaration statement.
    fn parse_node_type_decl(&mut self) -> Result<Stmt, ParseError> {
        let token = self.cursor.value().cloned();

        if !self.cursor.consume(TokenKind::Node) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected the 'node' keyword."),
            ));
        }

        if !self.cursor.consume(TokenKind::Type) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected the 'type' keyword after 'node'."),
            ));
        }

        let name = self.parse_ident()?;

        if !self.cursor.consume(TokenKind::LBrace) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected '{' after node type name."),
            ));
        }

        let mut ports: Vec<PortSpec> = Vec::new();

        while !self.cursor.matches(&[TokenKind::RBrace]) && !self.cursor.is_eos() {
            let specs = self.parse_port_spec()?;
            for spec in specs {
                if ports.iter().any(|p| p.name == spec.name) {
                    return Err(self.error(
                        Some(&Token::new(
                            TokenKind::Identifier(spec.name.clone()),
                            &spec.name,
                            spec.loc,
                        )),
                        ParseErrorKind::DuplicatePortName(spec.name.clone()),
                        Some("Port names must be unique within a node type declaration."),
                    ));
                }
                ports.push(spec);
            }
        }

        if !self.cursor.consume(TokenKind::RBrace) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected '}' to close node type declaration."),
            ));
        }

        let loc = token
            .as_ref()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        Ok(Stmt::NodeTypeDecl { name, ports, loc })
    }

    /// Parse a `use libname;` declaration statement.
    fn parse_use_decl(&mut self) -> Result<Stmt, ParseError> {
        let token = self.cursor.value().cloned();

        self.cursor.consume(TokenKind::Use);

        let name = self.parse_ident()?;

        if !self.cursor.consume(TokenKind::Semicolon) {
            return Err(self.error(
                token.as_ref(),
                ParseErrorKind::MissingSemicolonAfterStatement("use".to_owned()),
                None,
            ));
        }

        let loc = token
            .as_ref()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        Ok(Stmt::UseDecl { name, loc })
    }

    /// Parse a node instantiation expression: `node IDENT`.
    fn parse_node_expr(&mut self) -> Result<Expr, ParseError> {
        let token = self.cursor.value().cloned();
        let loc = token
            .as_ref()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        if !self.cursor.consume(TokenKind::Node) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected the 'node' keyword."),
            ));
        }

        // Defense in depth: `node type ...` should never reach here (decl/stmt/block paths
        // all filter it out earlier), but if it does, surface the proper error.
        if self.cursor.matches(&[TokenKind::Type]) {
            return Err(self.error(
                token.as_ref(),
                ParseErrorKind::NodeTypeDeclNotTopLevel,
                Some("'node type' declarations are only valid at file top level."),
            ));
        }

        let type_name = self.parse_ident()?;

        Ok(Expr::Node { type_name, loc })
    }

    /// Parse a `group { ... }` expression.
    fn parse_group_expr(&mut self) -> Result<Expr, ParseError> {
        let loc = self
            .cursor
            .value()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        if !self.cursor.consume(TokenKind::Group) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected the 'group' keyword."),
            ));
        }

        if !self.cursor.consume(TokenKind::LBrace) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected '{' after 'group'."),
            ));
        }

        let stmts = self.parse_group_stmts()?;

        if !self.cursor.consume(TokenKind::RBrace) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected '}' to close group body."),
            ));
        }

        Ok(Expr::Group { stmts, loc })
    }

    /// Parse the statement list inside a `group { ... }` body.
    ///
    /// Handles the same constructs as a regular block, plus `self.NAME = expr;` member
    /// declarations and `expr -> expr;` connect statements.  No result expression;
    /// the group itself is the value produced by `eval_group_expr`.
    ///
    /// Parse a keyword-introduced statement if the current token is a keyword that starts one.
    ///
    /// Returns `Ok(Some(stmt))` on success, `Ok(None)` if the current token does not start a
    /// keyword statement (caller handles the expression case), or `Err` on a parse failure.
    /// Also rejects `node type` declarations inside blocks and group bodies (top-level only).
    ///
    fn try_parse_keyword_stmt(&mut self) -> Result<Option<Stmt>, ParseError> {
        if self.cursor.matches(&[TokenKind::Node])
            && self
                .cursor
                .peek()
                .is_some_and(|t| t.kind == TokenKind::Type)
        {
            let token = self.cursor.value().cloned();
            return Err(self.error(
                token.as_ref(),
                ParseErrorKind::NodeTypeDeclNotTopLevel,
                Some("'node type' declarations are only valid at file top level."),
            ));
        }

        if self.cursor.matches(&[TokenKind::Port]) {
            let token = self.cursor.value().cloned();
            return Err(self.error(
                token.as_ref(),
                ParseErrorKind::PortDeclOutsideGroup,
                Some("'port' declarations are only valid inside a 'group { ... }' body."),
            ));
        }

        if self.cursor.matches(&[TokenKind::Use]) {
            let token = self.cursor.value().cloned();
            return Err(self.error(
                token.as_ref(),
                ParseErrorKind::UseDeclNotTopLevel,
                Some("'use' declarations are only valid at file top level."),
            ));
        }

        if self.cursor.matches(&[TokenKind::Const]) {
            Ok(Some(self.parse_const_decl()?))
        } else if self.cursor.matches(&[TokenKind::Function]) {
            Ok(Some(self.parse_fn_decl()?))
        } else if self.cursor.matches(&[TokenKind::Let]) {
            Ok(Some(self.parse_var_decl()?))
        } else if self.cursor.matches(&[TokenKind::Assert]) {
            Ok(Some(self.parse_assert_stmt()?))
        } else if self.cursor.matches(&[TokenKind::Print]) {
            Ok(Some(self.parse_print_stmt()?))
        } else if self.cursor.matches(&[TokenKind::Return]) {
            Ok(Some(self.parse_return_stmt()?))
        } else {
            Ok(None)
        }
    }

    fn parse_group_stmts(&mut self) -> Result<Vec<Stmt>, ParseError> {
        let mut stmts = Vec::new();

        while !self.cursor.matches(&[TokenKind::RBrace]) && !self.cursor.is_eos() {
            if self.cursor.matches(&[TokenKind::Port]) {
                stmts.push(self.parse_port_decl()?);
            } else if let Some(stmt) = self.try_parse_keyword_stmt()? {
                stmts.push(stmt);
            } else {
                let expr = self.parse_expr()?;

                if self.matches_assignment() {
                    stmts.push(self.parse_assignment_or_member_decl(expr)?);
                } else if self.cursor.matches(&[TokenKind::Arrow]) {
                    stmts.push(self.parse_connect_stmt(expr)?);
                } else {
                    let discarded = self.cursor.consume(TokenKind::Semicolon);
                    stmts.push(Stmt::Expr {
                        expr: Box::new(expr),
                        discarded,
                    });
                }
            }
        }

        Ok(stmts)
    }

    /// Parse `port NAME = <expr> ;`; a group port declaration.
    fn parse_port_decl(&mut self) -> Result<Stmt, ParseError> {
        let loc = self
            .cursor
            .value()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        if !self.cursor.consume(TokenKind::Port) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected the 'port' keyword."),
            ));
        }

        let name = self.parse_ident()?;

        if !self.cursor.consume(TokenKind::Is) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected '=' after port name."),
            ));
        }

        let mut exprs = vec![self.parse_expr()?];
        while self.cursor.matches(&[TokenKind::Comma]) {
            self.cursor.advance();
            exprs.push(self.parse_expr()?);
        }

        if !self.cursor.consume(TokenKind::Semicolon) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::MissingSemicolonAfterStatement("port declaration".to_owned()),
                None,
            ));
        }

        Ok(Stmt::PortDecl { name, exprs, loc })
    }

    /// Parse an assignment or, if the LHS is `self.NAME =`, a member declaration.
    fn parse_assignment_or_member_decl(&mut self, lhs: Expr) -> Result<Stmt, ParseError> {
        if let Expr::MemberAccess {
            ref object,
            ref field,
            loc,
        } = lhs
            && matches!(object.as_ref(), Expr::Self_ { .. })
        {
            let is_plain_assign = self.cursor.value().is_some_and(|t| t.kind == TokenKind::Is);

            if is_plain_assign {
                let name = field.clone();
                self.cursor.advance(); // consume '='
                let rhs = self.parse_expr()?;
                if !self.cursor.consume(TokenKind::Semicolon) {
                    return Err(self.error(
                        self.cursor.value(),
                        ParseErrorKind::MissingSemicolonAfterStatement(
                            "member declaration".to_owned(),
                        ),
                        None,
                    ));
                }
                return Ok(Stmt::MemberDecl {
                    name,
                    expr: Box::new(rhs),
                    loc,
                });
            }

            // Compound assignment to self.NAME (+=, -=, etc.); parse error.
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::InvalidAssignmentTarget,
                Some("Member bindings are immutable; compound assignment to 'self.NAME' is not allowed."),
            ));
        }
        self.parse_assignment(lhs)
    }

    /// Parse a single port specification line: `( 'in' | 'out' | 'inout' ) ID ( ',' ID )* ';'`.
    ///
    /// Returns one `PortSpec` per comma-separated name.
    ///
    fn parse_port_spec(&mut self) -> Result<Vec<PortSpec>, ParseError> {
        let direction_token = self.cursor.value().cloned();

        let direction = match direction_token.as_ref().map(|t| &t.kind) {
            Some(TokenKind::In) => PortDirection::In,
            Some(TokenKind::Out) => PortDirection::Out,
            Some(TokenKind::Inout) => PortDirection::Inout,
            _ => {
                return Err(self.error(
                    self.cursor.value(),
                    ParseErrorKind::UnexpectedToken,
                    Some("Expected a port direction ('in', 'out', or 'inout')."),
                ));
            }
        };
        self.cursor.advance();

        let mut specs = Vec::new();

        loop {
            let name_token = self.cursor.value().cloned();
            let name_loc = name_token
                .as_ref()
                .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);
            let name = self.parse_ident()?;
            specs.push(PortSpec {
                direction,
                name,
                loc: name_loc,
            });

            if !self.cursor.advance_if_matches(&[TokenKind::Comma]) {
                break;
            }
        }

        if !self.cursor.consume(TokenKind::Semicolon) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::MissingSemicolonAfterStatement("port".to_string()),
                Some("Expected ';' after port specification."),
            ));
        }

        Ok(specs)
    }

    /// Parse a return statement.
    fn parse_return_stmt(&mut self) -> Result<Stmt, ParseError> {
        let token = self.cursor.value().cloned();

        if !self.cursor.consume(TokenKind::Return) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected the 'return' keyword."),
            ));
        }

        let expr = if self.cursor.is_eos() || self.cursor.matches(&[TokenKind::Semicolon]) {
            None
        } else {
            Some(Box::new(self.parse_expr()?))
        };

        if !self.cursor.consume(TokenKind::Semicolon) {
            let token = token.as_ref();
            return Err(self.error(
                token,
                ParseErrorKind::MissingSemicolonAfterStatement(token.unwrap().lexeme.clone()),
                None,
            ));
        }

        let loc = token
            .as_ref()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        Ok(Stmt::Return { expr, loc })
    }

    /// Parse a regular statement (that is not a declaration).
    fn parse_stmt(&mut self) -> Result<Stmt, ParseError> {
        if self.cursor.matches(&[TokenKind::Node])
            && self
                .cursor
                .peek()
                .is_some_and(|t| t.kind == TokenKind::Type)
        {
            let token = self.cursor.value().cloned();
            return Err(self.error(
                token.as_ref(),
                ParseErrorKind::NodeTypeDeclNotTopLevel,
                Some("'node type' declarations are only valid at file top level."),
            ));
        }

        let result = if self.cursor.matches(&[TokenKind::Assert]) {
            self.parse_assert_stmt()?
        } else if self.cursor.matches(&[TokenKind::Print]) {
            self.parse_print_stmt()?
        } else if self.cursor.matches(&[TokenKind::Return]) {
            self.parse_return_stmt()?
        } else {
            self.parse_expr_or_assignment_stmt()?
        };

        Ok(result)
    }

    /// Check if the current token is an assignment operator (`=`, `+=`, `-=`, `*=`, `/=`, `%=`).
    fn matches_assignment(&mut self) -> bool {
        self.cursor.matches(&[
            TokenKind::Is,
            TokenKind::PlusIs,
            TokenKind::MinusIs,
            TokenKind::StarIs,
            TokenKind::SlashIs,
            TokenKind::PercentIs,
        ])
    }

    /// Map a compound assignment token to its corresponding binary operator.
    ///
    /// Returns the operation part of the compound assignment, `None` for plain `=`.
    ///
    fn compound_assignment_op(kind: &TokenKind) -> Option<BinaryOp> {
        match kind {
            TokenKind::PlusIs => Some(BinaryOp::Add),
            TokenKind::MinusIs => Some(BinaryOp::Subtract),
            TokenKind::StarIs => Some(BinaryOp::Multiply),
            TokenKind::SlashIs => Some(BinaryOp::Divide),
            TokenKind::PercentIs => Some(BinaryOp::Modulo),
            _ => None,
        }
    }

    /// Parse an expression statement or assignment statement.
    fn parse_expr_or_assignment_stmt(&mut self) -> Result<Stmt, ParseError> {
        let expr = self.parse_expr()?;

        if self.matches_assignment() {
            self.parse_assignment_or_member_decl(expr)
        } else if self.cursor.matches(&[TokenKind::Arrow]) {
            self.parse_connect_stmt(expr)
        } else {
            // Consume optional trailing semicolon for expression statements.
            let discarded = self.cursor.consume(TokenKind::Semicolon);
            Ok(Stmt::Expr {
                expr: Box::new(expr),
                discarded,
            })
        }
    }

    /// Parse a connection statement, given the left-hand side expression.
    fn parse_connect_stmt(&mut self, from: Expr) -> Result<Stmt, ParseError> {
        let loc = *from.loc();

        // Consume the '->' arrow.
        self.cursor.advance();

        let mut targets = vec![self.parse_expr()?];
        while self.cursor.consume(TokenKind::Comma) {
            targets.push(self.parse_expr()?);
        }

        if !self.cursor.consume(TokenKind::Semicolon) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::MissingSemicolonAfterStatement("connect".to_owned()),
                None,
            ));
        }

        Ok(Stmt::Connect {
            from: Box::new(from),
            targets,
            loc,
        })
    }

    /// Parse an assignment statement, given the left-hand side expression.
    fn parse_assignment(&mut self, lhs: Expr) -> Result<Stmt, ParseError> {
        // Determine if this is a compound assignment (+=, -=, etc.) or plain =.
        let compound_op = self
            .cursor
            .value()
            .and_then(|t| Self::compound_assignment_op(&t.kind));

        // Consume the assignment operator.
        self.cursor.advance();

        // Validate assignment target: must be an identifier or array index expression.
        let loc = *lhs.loc();
        let label = match &lhs {
            Expr::Identifier { name, .. } => name.clone(),
            Expr::Index { .. } => "index".to_owned(),
            _ => {
                return Err(self.error(
                    self.cursor.value(),
                    ParseErrorKind::InvalidAssignmentTarget,
                    Some("Only identifiers and array indices can be assigned to."),
                ));
            }
        };

        let rhs = self.parse_expr()?;

        // Desugar compound assignment: `x += expr` becomes `x = x + expr`.
        // For indexed targets: `arr[i] += expr` becomes `arr[i] = arr[i] + expr`.
        let value_expr = if let Some(op) = compound_op {
            Expr::Binary {
                op,
                lhs: Box::new(lhs.clone()),
                rhs: Box::new(rhs),
                loc,
            }
        } else {
            rhs
        };

        if !self.cursor.consume(TokenKind::Semicolon) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::MissingSemicolonAfterStatement(label),
                None,
            ));
        }

        Ok(Stmt::Assignment {
            target: Box::new(lhs),
            expr: Box::new(value_expr),
            loc,
        })
    }

    fn parse_assert_stmt(&mut self) -> Result<Stmt, ParseError> {
        let token = self.cursor.value().cloned();

        if !self.cursor.consume(TokenKind::Assert) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected the 'assert' keyword."),
            ));
        }

        let loc = token
            .as_ref()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        if self.cursor.is_eos() {
            let token_ref = token.as_ref();
            let lexeme = token_ref.map_or_else(|| "assert".to_owned(), |t| t.lexeme.clone());
            return Err(self.error(
                token_ref,
                ParseErrorKind::MissingSemicolonAfterStatement(lexeme),
                Some("Expected an expression after 'assert'."),
            ));
        }

        let condition = self.parse_expr()?;

        let message = if self.cursor.advance_if_matches(&[TokenKind::Comma]) {
            Some(Box::new(self.parse_expr()?))
        } else {
            None
        };

        if !self.cursor.consume(TokenKind::Semicolon) {
            let token = token.as_ref();
            return Err(self.error(
                token,
                ParseErrorKind::MissingSemicolonAfterStatement(token.unwrap().lexeme.clone()),
                None,
            ));
        }

        Ok(Stmt::Assert {
            condition: Box::new(condition),
            message,
            loc,
        })
    }

    fn parse_print_stmt(&mut self) -> Result<Stmt, ParseError> {
        let token = self.cursor.value().cloned();

        if !self.cursor.consume(TokenKind::Print) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected the 'print' keyword."),
            ));
        }

        // Special case: no expression: print a newline.
        if self.cursor.advance_if_matches(&[TokenKind::Semicolon]) {
            let loc = token
                .as_ref()
                .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);
            return Ok(Stmt::Print {
                expr: Box::new(Expr::Literal(Literal::String("\n".to_owned(), loc))),
            });
        }

        if self.cursor.is_eos() {
            let token_ref = token.as_ref();
            let lexeme = token_ref.map_or_else(|| "print".to_owned(), |t| t.lexeme.clone());
            return Err(self.error(
                token_ref,
                ParseErrorKind::MissingSemicolonAfterStatement(lexeme),
                Some("Expected an expression or semicolon after 'print'."),
            ));
        }

        let expr = self.parse_expr()?;

        if !self.cursor.consume(TokenKind::Semicolon) {
            let token = token.as_ref();
            return Err(self.error(
                token,
                ParseErrorKind::MissingSemicolonAfterStatement(token.unwrap().lexeme.clone()),
                None,
            ));
        }

        Ok(Stmt::Print {
            expr: Box::new(expr),
        })
    }

    /// Parse an expression.
    fn parse_expr(&mut self) -> Result<Expr, ParseError> {
        if self.expr_depth >= MAX_EXPR_DEPTH {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::ExpressionDepthLimitExceeded(MAX_EXPR_DEPTH),
                Some(&format!(
                    "Expression nesting depth exceeds limit of {MAX_EXPR_DEPTH}"
                )),
            ));
        }
        self.expr_depth += 1;
        let result = self.parse_range_expr();
        self.expr_depth -= 1;
        result
    }

    /// Parse a range expression.
    fn parse_range_expr(&mut self) -> Result<Expr, ParseError> {
        let expr = self.parse_logical_or()?;

        if self.cursor.advance_if_matches(&[TokenKind::Range]) {
            let loc = *expr.loc();
            let end = self.parse_logical_or()?;
            Ok(Expr::Range {
                start: Box::new(expr),
                end: Box::new(end),
                inclusive: false,
                loc,
            })
        } else if self.cursor.advance_if_matches(&[TokenKind::RangeInclusive]) {
            let loc = *expr.loc();
            let end = self.parse_logical_or()?;
            Ok(Expr::Range {
                start: Box::new(expr),
                end: Box::new(end),
                inclusive: true,
                loc,
            })
        } else {
            Ok(expr)
        }
    }

    /// Parse an logical OR expression.
    fn parse_logical_or(&mut self) -> Result<Expr, ParseError> {
        let mut expr = self.parse_logical_and()?;

        while self.cursor.advance_if_matches(&[TokenKind::DoublePipe]) {
            if self.cursor.is_eos() {
                return Err(self.error(
                    None,
                    ParseErrorKind::UnexpectedEndOfStream,
                    Some("Expected an expression."),
                ));
            }

            let loc = *expr.loc();
            expr = Expr::Binary {
                op: BinaryOp::LogicalOr,
                lhs: Box::new(expr),
                rhs: Box::new(self.parse_logical_and()?),
                loc,
            }
        }

        Ok(expr)
    }

    /// Parse an logical AND expression.
    fn parse_logical_and(&mut self) -> Result<Expr, ParseError> {
        let mut expr = self.parse_equality()?;

        while self.cursor.advance_if_matches(&[TokenKind::And]) {
            if self.cursor.is_eos() {
                return Err(self.error(
                    None,
                    ParseErrorKind::UnexpectedEndOfStream,
                    Some("Expected an expression."),
                ));
            }

            let loc = *expr.loc();
            expr = Expr::Binary {
                op: BinaryOp::LogicalAnd,
                lhs: Box::new(expr),
                rhs: Box::new(self.parse_equality()?),
                loc,
            }
        }

        Ok(expr)
    }

    /// Parse an equality comparison expression.
    fn parse_equality(&mut self) -> Result<Expr, ParseError> {
        let mut expr = self.parse_ordering()?;

        while self.cursor.matches(&[TokenKind::Eq, TokenKind::NotEq]) {
            if self.cursor.is_eos() {
                return Err(self.error(
                    None,
                    ParseErrorKind::UnexpectedEndOfStream,
                    Some("Expected an expression."),
                ));
            }

            // NOTE: We just checked if the cursor is at EOS: there is a value here.
            let op = self.cursor.value().unwrap().kind.clone();
            let loc = *expr.loc();

            self.cursor.advance();

            expr = Expr::Binary {
                op: op.into(),
                lhs: Box::new(expr),
                rhs: Box::new(self.parse_ordering()?),
                loc,
            }
        }

        Ok(expr)
    }

    /// Parse a (partial) ordering/comparison expression.
    fn parse_ordering(&mut self) -> Result<Expr, ParseError> {
        let mut expr = self.parse_additive()?;

        while self.cursor.matches(&[
            TokenKind::Gt,
            TokenKind::GtEq,
            TokenKind::Lt,
            TokenKind::LtEq,
        ]) {
            if self.cursor.is_eos() {
                return Err(self.error(
                    None,
                    ParseErrorKind::UnexpectedEndOfStream,
                    Some("Expected an expression."),
                ));
            }

            // NOTE: We just checked if the cursor is at EOS: there is a value here.
            let op = self.cursor.value().unwrap().kind.clone();
            let loc = *expr.loc();

            self.cursor.advance();

            expr = Expr::Binary {
                op: op.into(),
                lhs: Box::new(expr),
                rhs: Box::new(self.parse_additive()?),
                loc,
            }
        }

        Ok(expr)
    }

    /// Parse an additive expression.
    fn parse_additive(&mut self) -> Result<Expr, ParseError> {
        let mut expr = self.parse_factor()?;

        while self.cursor.matches(&[TokenKind::Plus, TokenKind::Minus]) {
            if self.cursor.is_eos() {
                return Err(self.error(
                    None,
                    ParseErrorKind::UnexpectedEndOfStream,
                    Some("Expected an expression."),
                ));
            }

            // NOTE: We just checked if the cursor is at EOS: there is a value here.
            let op = self.cursor.value().unwrap().kind.clone();
            let loc = *expr.loc();

            self.cursor.advance();

            expr = Expr::Binary {
                op: op.into(),
                lhs: Box::new(expr),
                rhs: Box::new(self.parse_factor()?),
                loc,
            }
        }

        Ok(expr)
    }

    /// Parse a factor expression.
    fn parse_factor(&mut self) -> Result<Expr, ParseError> {
        let mut expr = self.parse_unary()?;

        while self
            .cursor
            .matches(&[TokenKind::Star, TokenKind::Slash, TokenKind::Percent])
        {
            if self.cursor.is_eos() {
                return Err(self.error(
                    None,
                    ParseErrorKind::UnexpectedEndOfStream,
                    Some("Expected an expression."),
                ));
            }

            // NOTE: We just checked if the cursor is at EOS: there is a value here.
            let op = self.cursor.value().unwrap().kind.clone();
            let loc = *expr.loc();

            self.cursor.advance();

            expr = Expr::Binary {
                op: op.into(),
                lhs: Box::new(expr),
                rhs: Box::new(self.parse_unary()?),
                loc,
            }
        }

        Ok(expr)
    }

    /// Parse a unary expression.
    fn parse_unary(&mut self) -> Result<Expr, ParseError> {
        if self.expr_depth >= MAX_EXPR_DEPTH {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::ExpressionDepthLimitExceeded(MAX_EXPR_DEPTH),
                Some(&format!(
                    "Expression nesting depth exceeds limit of {MAX_EXPR_DEPTH}"
                )),
            ));
        }
        self.expr_depth += 1;
        let result = self.parse_unary_inner();
        self.expr_depth -= 1;
        result
    }

    fn parse_unary_inner(&mut self) -> Result<Expr, ParseError> {
        if self.cursor.matches(&[TokenKind::Amp]) {
            let token = self.cursor.value().unwrap();
            let loc = token.loc;
            self.cursor.advance();

            let expr = self.parse_unary()?;

            return Ok(Expr::Ref {
                expr: Box::new(expr),
                loc,
            });
        }

        if self
            .cursor
            .matches(&[TokenKind::Bang, TokenKind::Plus, TokenKind::Minus])
        {
            if self.cursor.is_eos() {
                return Err(self.error(
                    None,
                    ParseErrorKind::UnexpectedEndOfStream,
                    Some("Expected an expression."),
                ));
            }

            // NOTE: We just checked if the cursor is at EOS: there is a value here.
            let token = self.cursor.value().unwrap();
            let op = token.kind.clone();
            let loc = token.loc;

            self.cursor.advance();

            Ok(Expr::Unary {
                op: op.into(),
                factor: Box::new(self.parse_unary()?),
                loc,
            })
        } else {
            self.parse_postfix()
        }
    }

    /// Parse a postfix expression (function calls and array indexing).
    fn parse_postfix(&mut self) -> Result<Expr, ParseError> {
        let mut expr = self.parse_primary()?;

        loop {
            if self.cursor.matches(&[TokenKind::LParen]) {
                let loc = *expr.loc();
                self.cursor.advance();

                let mut args = Vec::new();

                if !self.cursor.matches(&[TokenKind::RParen]) {
                    args.push(self.parse_expr()?);

                    while self.cursor.advance_if_matches(&[TokenKind::Comma]) {
                        args.push(self.parse_expr()?);
                    }
                }

                if !self.cursor.consume(TokenKind::RParen) {
                    return Err(self.error(
                        self.cursor.value(),
                        ParseErrorKind::UnexpectedToken,
                        Some("Expected ')' after function arguments."),
                    ));
                }

                expr = Expr::Call {
                    callee: Box::new(expr),
                    args,
                    loc,
                };
            } else if self.cursor.matches(&[TokenKind::Dot]) {
                let loc = *expr.loc();
                self.cursor.advance();

                let name = self.parse_ident()?;

                if !self.cursor.matches(&[TokenKind::LParen]) {
                    expr = Expr::MemberAccess {
                        object: Box::new(expr),
                        field: name,
                        loc,
                    };
                    continue;
                }

                self.cursor.advance();

                let mut args = Vec::new();

                if !self.cursor.matches(&[TokenKind::RParen]) {
                    args.push(self.parse_expr()?);

                    while self.cursor.advance_if_matches(&[TokenKind::Comma]) {
                        args.push(self.parse_expr()?);
                    }
                }

                if !self.cursor.consume(TokenKind::RParen) {
                    return Err(self.error(
                        self.cursor.value(),
                        ParseErrorKind::UnexpectedToken,
                        Some("Expected ')' after method arguments."),
                    ));
                }

                expr = Expr::MethodCall {
                    object: Box::new(expr),
                    method: name,
                    args,
                    loc,
                };
            } else if self.cursor.matches(&[TokenKind::LBracket]) {
                let loc = *expr.loc();
                self.cursor.advance();

                let index = self.parse_expr()?;

                if !self.cursor.consume(TokenKind::RBracket) {
                    return Err(self.error(
                        self.cursor.value(),
                        ParseErrorKind::UnexpectedToken,
                        Some("Expected ']' after array index."),
                    ));
                }

                expr = Expr::Index {
                    array: Box::new(expr),
                    index: Box::new(index),
                    loc,
                };
            } else {
                break;
            }
        }

        Ok(expr)
    }

    /// Parse a primary expression.
    fn parse_primary(&mut self) -> Result<Expr, ParseError> {
        if self
            .cursor
            .matches(&[TokenKind::Pipe, TokenKind::DoublePipe])
        {
            return self.parse_closure();
        }

        if self.cursor.matches(&[TokenKind::LBracket]) {
            self.parse_array_or_fill()
        } else if self.cursor.matches(&[TokenKind::LBrace]) {
            self.parse_block()
        } else if self.cursor.matches(&[TokenKind::Break]) {
            self.parse_break()
        } else if self.cursor.matches(&[TokenKind::Continue]) {
            self.parse_continue()
        } else if self.cursor.matches(&[TokenKind::For]) {
            self.parse_for()
        } else if self.cursor.matches(&[TokenKind::If]) {
            self.parse_if()
        } else if self.cursor.matches(&[TokenKind::Loop]) {
            self.parse_loop()
        } else if self.cursor.matches(&[TokenKind::While]) {
            self.parse_while()
        } else if self.cursor.matches(&[TokenKind::Group]) {
            self.parse_group_expr()
        } else if self.cursor.matches(&[TokenKind::Node]) {
            self.parse_node_expr()
        } else if self.cursor.matches(&[TokenKind::Self_]) {
            let loc = self
                .cursor
                .value()
                .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);
            self.cursor.advance();
            Ok(Expr::Self_ { loc })
        } else if self.cursor.advance_if_matches(&[TokenKind::LParen]) {
            if self.cursor.is_eos() {
                return Err(self.error(
                    None,
                    ParseErrorKind::UnexpectedEndOfStream,
                    Some("Expected an expression."),
                ));
            }

            let expr = self.parse_expr()?;

            if !self.cursor.consume(TokenKind::RParen) {
                return Err(self.error(
                    self.cursor.value(),
                    ParseErrorKind::UnexpectedEndOfStream,
                    Some("Expected a closing parenthesis."),
                ));
            }

            Ok(expr)
        } else {
            self.parse_literal_or_identifier()
        }
    }

    /// Parse a closure expression: `|params| body` or `|| body`.
    fn parse_closure(&mut self) -> Result<Expr, ParseError> {
        let token = self.cursor.value().cloned();
        let loc = token
            .as_ref()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        let mut params = Vec::new();

        if self.cursor.consume(TokenKind::DoublePipe) {
            // Zero-parameter closure: `|| body`
        } else if self.cursor.consume(TokenKind::Pipe) {
            // One or more parameters: `|name: Type, ...| body`
            while !self.cursor.matches(&[TokenKind::Pipe]) && !self.cursor.is_eos() {
                let param_name = self.parse_ident()?;

                if !self.cursor.consume(TokenKind::Colon) {
                    return Err(self.error(
                        self.cursor.value(),
                        ParseErrorKind::UnexpectedToken,
                        Some("Expected ':' after closure parameter name."),
                    ));
                }

                let param_type = self.parse_type_id()?;
                params.push(Parameter {
                    name: param_name,
                    type_id: param_type,
                });

                if !self.cursor.advance_if_matches(&[TokenKind::Comma]) {
                    break;
                }
            }

            if !self.cursor.consume(TokenKind::Pipe) {
                return Err(self.error(
                    self.cursor.value(),
                    ParseErrorKind::UnexpectedToken,
                    Some("Expected '|' after closure parameters."),
                ));
            }
        }

        let return_type = if self.cursor.consume(TokenKind::Arrow) {
            Some(self.parse_type_id()?)
        } else {
            None
        };

        let body = if self.cursor.matches(&[TokenKind::LBrace]) {
            self.parse_block()?
        } else {
            self.parse_expr()?
        };

        Ok(Expr::Closure {
            params,
            return_type,
            body: Box::new(body),
            loc,
        })
    }

    /// Parse a block expression.
    fn parse_block(&mut self) -> Result<Expr, ParseError> {
        if self.depth >= MAX_EXPR_DEPTH {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::ExpressionDepthLimitExceeded(MAX_EXPR_DEPTH),
                Some(&format!(
                    "Block nesting depth exceeds limit of {MAX_EXPR_DEPTH}"
                )),
            ));
        }

        let token = self.cursor.value().cloned();
        let loc = token
            .as_ref()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        self.depth += 1;

        if !self.cursor.consume(TokenKind::LBrace) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected '{'."),
            ));
        }

        let mut stmts = Vec::new();
        let mut result = None;

        while !self.cursor.matches(&[TokenKind::RBrace]) && !self.cursor.is_eos() {
            if let Some(stmt) = self.try_parse_keyword_stmt()? {
                stmts.push(stmt);
            } else {
                // Parse expression - could be a statement or the block result.
                let expr = self.parse_expr()?;

                if self.cursor.consume(TokenKind::Semicolon) {
                    stmts.push(Stmt::Expr {
                        expr: Box::new(expr),
                        discarded: true,
                    });
                } else if self.matches_assignment() {
                    stmts.push(self.parse_assignment_or_member_decl(expr)?);
                } else if self.cursor.matches(&[TokenKind::Arrow]) {
                    stmts.push(self.parse_connect_stmt(expr)?);
                } else if matches!(
                    expr,
                    Expr::For { .. } | Expr::If { .. } | Expr::Loop { .. } | Expr::While { .. }
                ) && !self.cursor.matches(&[TokenKind::RBrace])
                {
                    // Block-bodied control flow used as a statement; no semicolon needed.
                    stmts.push(Stmt::Expr {
                        expr: Box::new(expr),
                        discarded: false,
                    });
                } else {
                    // Semicolon-less trailing expression; this is the block's result value.
                    result = Some(Box::new(expr));
                    break;
                }
            }
        }

        if !self.cursor.consume(TokenKind::RBrace) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected '}'."),
            ));
        }

        self.depth -= 1;

        Ok(Expr::Block { stmts, result, loc })
    }

    /// Parse a conditional expression.
    fn parse_if(&mut self) -> Result<Expr, ParseError> {
        let token = self.cursor.value().cloned();
        let loc = token
            .as_ref()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        if !self.cursor.consume(TokenKind::If) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected the 'if' keyword."),
            ));
        }

        let condition = Box::new(self.parse_expr()?);

        if !self.cursor.matches(&[TokenKind::LBrace]) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected '{' after if condition."),
            ));
        }

        let then_branch = Box::new(self.parse_block()?);

        let else_branch = if self.cursor.consume(TokenKind::Else) {
            if self.cursor.matches(&[TokenKind::If]) {
                Some(Box::new(self.parse_if()?))
            } else if self.cursor.matches(&[TokenKind::LBrace]) {
                Some(Box::new(self.parse_block()?))
            } else {
                return Err(self.error(
                    self.cursor.value(),
                    ParseErrorKind::UnexpectedToken,
                    Some("Expected 'if' or '{' after 'else'."),
                ));
            }
        } else {
            None
        };

        Ok(Expr::If {
            condition,
            then_branch,
            else_branch,
            loc,
        })
    }

    /// Parse a break expression.
    fn parse_break(&mut self) -> Result<Expr, ParseError> {
        let token = self.cursor.value().cloned();
        let loc = token
            .as_ref()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        if !self.cursor.consume(TokenKind::Break) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected the 'break' keyword."),
            ));
        }

        // If the next token is '}' or ';' or EOS, there is no break value.
        let value = if self.cursor.is_eos()
            || self
                .cursor
                .matches(&[TokenKind::RBrace, TokenKind::Semicolon])
        {
            None
        } else {
            Some(Box::new(self.parse_expr()?))
        };

        Ok(Expr::Break { value, loc })
    }

    /// Parse a continue expression.
    fn parse_continue(&mut self) -> Result<Expr, ParseError> {
        let token = self.cursor.value().cloned();
        let loc = token
            .as_ref()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        if !self.cursor.consume(TokenKind::Continue) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected the 'continue' keyword."),
            ));
        }

        Ok(Expr::Continue { loc })
    }

    /// Parse a for loop expression.
    fn parse_for(&mut self) -> Result<Expr, ParseError> {
        let token = self.cursor.value().cloned();
        let loc = token
            .as_ref()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        if !self.cursor.consume(TokenKind::For) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected the 'for' keyword."),
            ));
        }

        let var_loc = self
            .cursor
            .value()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);
        let variable = if self.cursor.consume(TokenKind::Underscore) {
            "_".to_owned()
        } else {
            self.parse_ident()?
        };

        if !self.cursor.consume(TokenKind::In) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected 'in' after for loop variable."),
            ));
        }

        let iterable = Box::new(self.parse_expr()?);

        if !self.cursor.matches(&[TokenKind::LBrace]) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected '{' after for loop iterable."),
            ));
        }

        let body = Box::new(self.parse_block()?);

        Ok(Expr::For {
            variable,
            var_loc,
            iterable,
            body,
            loc,
        })
    }

    /// Parse a loop expression.
    fn parse_loop(&mut self) -> Result<Expr, ParseError> {
        let token = self.cursor.value().cloned();
        let loc = token
            .as_ref()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        if !self.cursor.consume(TokenKind::Loop) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected the 'loop' keyword."),
            ));
        }

        if !self.cursor.matches(&[TokenKind::LBrace]) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected '{' after 'loop'."),
            ));
        }

        let body = Box::new(self.parse_block()?);

        if !body_contains_break(&body) {
            return Err(self.error(
                token.as_ref(),
                ParseErrorKind::LoopWithoutBreak,
                Some("'loop' expression must contain a 'break' to guarantee termination."),
            ));
        }

        Ok(Expr::Loop { body, loc })
    }

    /// Parse a while loop expression.
    fn parse_while(&mut self) -> Result<Expr, ParseError> {
        let token = self.cursor.value().cloned();
        let loc = token
            .as_ref()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        if !self.cursor.consume(TokenKind::While) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected the 'while' keyword."),
            ));
        }

        let condition = Box::new(self.parse_expr()?);

        if !self.cursor.matches(&[TokenKind::LBrace]) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected '{' after while condition."),
            ));
        }

        let body = Box::new(self.parse_block()?);

        Ok(Expr::While {
            condition,
            body,
            loc,
        })
    }

    /// Parse an array literal `[expr, expr, ...]` or a fill expression `[Type; N]`.
    fn parse_array_or_fill(&mut self) -> Result<Expr, ParseError> {
        let token = self.cursor.value().cloned();
        let loc = token
            .as_ref()
            .map_or_else(|| Location::new(0, 1, 1), |t| t.loc);

        if !self.cursor.consume(TokenKind::LBracket) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected '['."),
            ));
        }

        // Disambiguate: if next token is a type keyword followed by `;`, it's a fill expression.
        if self
            .cursor
            .matches(&[TokenKind::BoolId, TokenKind::NumberId, TokenKind::StringId])
        {
            let type_id = self.parse_type_id()?;

            if !self.cursor.consume(TokenKind::Semicolon) {
                return Err(self.error(
                    self.cursor.value(),
                    ParseErrorKind::UnexpectedToken,
                    Some("Expected ';' after type in array fill expression."),
                ));
            }

            let length = self.parse_expr()?;

            if !self.cursor.consume(TokenKind::RBracket) {
                return Err(self.error(
                    self.cursor.value(),
                    ParseErrorKind::UnexpectedToken,
                    Some("Expected ']' after array fill expression."),
                ));
            }

            return Ok(Expr::ArrayFill {
                type_id,
                length: Box::new(length),
                loc,
            });
        }

        // Array literal: [expr, expr, ...] or empty [].
        let mut elements = Vec::new();

        if !self.cursor.matches(&[TokenKind::RBracket]) {
            elements.push(self.parse_expr()?);

            while self.cursor.advance_if_matches(&[TokenKind::Comma]) {
                if self.cursor.matches(&[TokenKind::RBracket]) {
                    break; // Trailing comma is allowed.
                }

                elements.push(self.parse_expr()?);
            }
        }

        if !self.cursor.consume(TokenKind::RBracket) {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::UnexpectedToken,
                Some("Expected ']' after array elements."),
            ));
        }

        Ok(Expr::Array { elements, loc })
    }

    /// Parse a literal or identifier.
    fn parse_literal_or_identifier(&mut self) -> Result<Expr, ParseError> {
        if self.cursor.is_eos() {
            return Err(self.error(None, ParseErrorKind::EndOfStream, None));
        }

        // NOTE: We just checked if the cursor is at EOS: there is a value here.
        let token = self.cursor.value().unwrap();
        let loc = token.loc;

        let expr = match &token.kind {
            TokenKind::True => Expr::Literal(Literal::Boolean(true, loc)),
            TokenKind::False => Expr::Literal(Literal::Boolean(false, loc)),
            TokenKind::Number(n) => {
                let num = *n;
                Expr::Literal(Literal::Number(num, loc))
            }
            TokenKind::String(s) => {
                let string = s.clone();
                Expr::Literal(Literal::String(string, loc))
            }
            TokenKind::Identifier(name) => {
                let name = name.clone();
                Expr::Identifier { name, loc }
            }
            _ => {
                return Err(self.error(
                    Some(token),
                    ParseErrorKind::UnexpectedToken,
                    Some("Expected a literal value or identifier."),
                ));
            }
        };

        self.cursor.advance();

        Ok(expr)
    }

    /// Parse an identifier.
    fn parse_ident(&mut self) -> Result<String, ParseError> {
        if self.cursor.is_eos() {
            return Err(self.error(None, ParseErrorKind::EndOfStream, None));
        }

        // NOTE: We just checked if the cursor is at EOS: there is a value here.
        let token = self.cursor.value().unwrap();

        let name = match &token.kind {
            TokenKind::Identifier(id) => id.clone(),
            _ => {
                return Err(self.error(
                    Some(token),
                    ParseErrorKind::UnexpectedToken,
                    Some("Expected an identifier."),
                ));
            }
        };

        self.cursor.advance();

        Ok(name)
    }

    /// Parse a type ID.
    fn parse_type_id(&mut self) -> Result<TypeId, ParseError> {
        if self.expr_depth >= MAX_EXPR_DEPTH {
            return Err(self.error(
                self.cursor.value(),
                ParseErrorKind::ExpressionDepthLimitExceeded(MAX_EXPR_DEPTH),
                Some(&format!(
                    "Type annotation nesting depth exceeds limit of {MAX_EXPR_DEPTH}"
                )),
            ));
        }
        self.expr_depth += 1;
        let result = self.parse_type_id_inner();
        self.expr_depth -= 1;
        result
    }

    fn parse_type_id_inner(&mut self) -> Result<TypeId, ParseError> {
        if self.cursor.is_eos() {
            return Err(self.error(None, ParseErrorKind::EndOfStream, None));
        }

        // Function type: fn(Type, Type, ...) -> Type
        if self.cursor.matches(&[TokenKind::Function]) {
            self.cursor.advance();

            if !self.cursor.consume(TokenKind::LParen) {
                return Err(self.error(
                    self.cursor.value(),
                    ParseErrorKind::UnexpectedToken,
                    Some("Expected '(' after 'fn' in function type."),
                ));
            }

            let mut params = Vec::new();

            if !self.cursor.matches(&[TokenKind::RParen]) {
                params.push(self.parse_type_id()?);

                while self.cursor.advance_if_matches(&[TokenKind::Comma]) {
                    params.push(self.parse_type_id()?);
                }
            }

            if !self.cursor.consume(TokenKind::RParen) {
                return Err(self.error(
                    self.cursor.value(),
                    ParseErrorKind::UnexpectedToken,
                    Some("Expected ')' after function type parameters."),
                ));
            }

            let return_type = if self.cursor.consume(TokenKind::Arrow) {
                Some(Box::new(self.parse_type_id()?))
            } else {
                None
            };

            return Ok(TypeId::Fn {
                params,
                return_type,
            });
        }

        // Array type: [Type; N] (sized) or [Type] (unsized)
        if self.cursor.matches(&[TokenKind::LBracket]) {
            self.cursor.advance();

            let element_type = self.parse_type_id()?;

            // Unsized: [Type]
            if self.cursor.consume(TokenKind::RBracket) {
                return Ok(TypeId::Array(Box::new(element_type), None));
            }

            // Sized: [Type; N]
            if !self.cursor.consume(TokenKind::Semicolon) {
                return Err(self.error(
                    self.cursor.value(),
                    ParseErrorKind::UnexpectedToken,
                    Some("Expected ']' or ';' in array type annotation."),
                ));
            }

            let length_expr = self.parse_expr()?;

            if !self.cursor.consume(TokenKind::RBracket) {
                return Err(self.error(
                    self.cursor.value(),
                    ParseErrorKind::UnexpectedToken,
                    Some("Expected ']' after array type annotation."),
                ));
            }

            return Ok(TypeId::Array(
                Box::new(element_type),
                Some(Box::new(length_expr)),
            ));
        }

        // NOTE: We just checked if the cursor is at EOS: there is a value here.
        let token = self.cursor.value().unwrap();

        let type_id = match &token.kind {
            TokenKind::BoolId => TypeId::Boolean,
            TokenKind::GroupId => TypeId::Group,
            TokenKind::NodeId => TypeId::Node,
            TokenKind::NumberId => TypeId::Number,
            TokenKind::PortRefId => TypeId::PortRef,
            TokenKind::StringId => TypeId::String,
            _ => {
                return Err(self.error(
                    Some(token),
                    ParseErrorKind::UnexpectedToken,
                    Some("Expected a type ID."),
                ));
            }
        };

        self.cursor.advance();

        Ok(type_id)
    }
}

impl ParseErrorExt for Parser<'_> {
    fn error(
        &self,
        token: Option<&Token>,
        kind: ParseErrorKind,
        message: Option<&str>,
    ) -> ParseError {
        let (loc, length) = if let Some(t) = token {
            (t.loc, Some(t.lexeme.len()))
        } else {
            // Default to the last position of the source code.
            let line = self.source.code().lines().count();
            let col = self.source.code().lines().last().unwrap_or("").len();
            (Location::new(self.source.code().len(), line, col), None)
        };

        ParseError::new(
            self.source.into(),
            &loc,
            kind,
            message.map(std::borrow::ToOwned::to_owned),
            None::<String>,
            length,
        )
    }

    fn error_with_cause(
        &self,
        token: Option<&Token>,
        kind: ParseErrorKind,
        message: Option<&str>,
        source: ParseError,
        help: Option<&str>,
    ) -> ParseError {
        let (loc, length) = if let Some(t) = token {
            (t.loc, Some(t.lexeme.len()))
        } else {
            // Default to the last position of the source code.
            let line = self.source.code().lines().count();
            let col = self.source.code().lines().last().unwrap_or("").len();
            (Location::new(self.source.code().len(), line, col), None)
        };

        ParseError::with_related(
            self.source.into(),
            &loc,
            kind,
            message.map(std::borrow::ToOwned::to_owned),
            source,
            help,
            length,
        )
    }
}

/// Check if an expression tree contains a `break` that targets the current loop.
///
/// This does NOT recurse into nested loops (`for`, `while`, `loop`) since those have
/// their own scope for `break`/`continue`.
///
fn body_contains_break(expr: &Expr) -> bool {
    match expr {
        Expr::Break { .. } => true,
        Expr::Block { stmts, result, .. } => {
            stmts.iter().any(stmt_contains_break)
                || result.as_deref().is_some_and(body_contains_break)
        }
        Expr::If {
            condition,
            then_branch,
            else_branch,
            ..
        } => {
            body_contains_break(condition)
                || body_contains_break(then_branch)
                || else_branch.as_deref().is_some_and(body_contains_break)
        }
        Expr::Call { callee, args, .. } => {
            body_contains_break(callee) || args.iter().any(body_contains_break)
        }
        Expr::MemberAccess { object, .. } => body_contains_break(object),
        Expr::MethodCall { object, args, .. } => {
            body_contains_break(object) || args.iter().any(body_contains_break)
        }

        Expr::Array { elements, .. } => elements.iter().any(body_contains_break),
        Expr::Index { array, index, .. } => {
            body_contains_break(array) || body_contains_break(index)
        }

        // Do not recurse into nested loops, closures, or group bodies; their breaks belong to them.
        Expr::ArrayFill { .. }
        | Expr::Closure { .. }
        | Expr::For { .. }
        | Expr::Group { .. }
        | Expr::While { .. }
        | Expr::Loop { .. }
        | Expr::Continue { .. }
        | Expr::Identifier { .. }
        | Expr::Literal(_)
        | Expr::Node { .. }
        | Expr::Self_ { .. } => false,
        Expr::Binary { lhs, rhs, .. }
        | Expr::Range {
            start: lhs,
            end: rhs,
            ..
        } => body_contains_break(lhs) || body_contains_break(rhs),
        Expr::Ref { expr, .. } | Expr::Unary { factor: expr, .. } => body_contains_break(expr),
    }
}

/// Check if a statement contains a `break` targeting the current loop.
fn stmt_contains_break(stmt: &Stmt) -> bool {
    match stmt {
        Stmt::Expr { expr, .. } | Stmt::Print { expr } | Stmt::ConstDecl { expr, .. } => {
            body_contains_break(expr)
        }
        Stmt::Assert {
            condition, message, ..
        } => body_contains_break(condition) || message.as_deref().is_some_and(body_contains_break),
        Stmt::Assignment { target, expr, .. } => {
            body_contains_break(target) || body_contains_break(expr)
        }
        Stmt::Connect { from, targets, .. } => {
            body_contains_break(from) || targets.iter().any(body_contains_break)
        }
        Stmt::MemberDecl { expr, .. } => body_contains_break(expr),
        Stmt::PortDecl { exprs, .. } => exprs.iter().any(body_contains_break),
        Stmt::FnDecl { .. } | Stmt::NodeTypeDecl { .. } | Stmt::UseDecl { .. } => false,
        Stmt::Return { expr, .. } | Stmt::VarDecl { expr, .. } => {
            expr.as_deref().is_some_and(body_contains_break)
        }
    }
}
