//! Expression parsing.

use bhc_ast::{Alt, ArithSeq, Expr, FieldBind, Guard, GuardedRhs, Lit, ModuleName, Pat, Rhs, Stmt};
use bhc_intern::Ident;
use bhc_lexer::TokenKind;
use bhc_span::Span;

use crate::{ParseError, ParseResult, Parser};

impl<'src> Parser<'src> {
    /// Parse an expression, including optional type annotation.
    pub fn parse_expr(&mut self) -> ParseResult<Expr> {
        self.enter_recursion()?;
        let result = self.parse_expr_guarded();
        self.exit_recursion();
        result
    }

    fn parse_expr_guarded(&mut self) -> ParseResult<Expr> {
        let expr = self.parse_infix_expr(0)?;

        // Check for type annotation: expr :: Type
        if self.check(&TokenKind::DoubleColon) {
            self.parse_type_annotation(expr)
        } else {
            Ok(expr)
        }
    }

    /// Parse an infix expression with precedence climbing.
    fn parse_infix_expr(&mut self, min_prec: u8) -> ParseResult<Expr> {
        let lhs = self.parse_prefix_expr()?;
        self.continue_infix_expr(lhs, min_prec)
    }

    /// Continue parsing an infix expression from a pre-parsed LHS.
    fn continue_infix_expr(&mut self, mut lhs: Expr, min_prec: u8) -> ParseResult<Expr> {
        while let Some(tok) = self.current() {
            // Track whether the operator was already consumed (for backtick case)
            let mut op_consumed = false;

            let (op, prec, assoc) = match &tok.node.kind {
                TokenKind::Operator(sym) => {
                    let (prec, assoc) = self.get_operator_info(sym.as_str());
                    if prec < min_prec {
                        break;
                    }
                    (Ident::new(*sym), prec, assoc)
                }
                // Handle * (Star token is lexed separately for kind syntax)
                TokenKind::Star => {
                    let (prec, assoc) = self.get_operator_info("*");
                    if prec < min_prec {
                        break;
                    }
                    (Ident::from_str("*"), prec, assoc)
                }
                // Handle - (Minus token is lexed separately for prefix negation)
                TokenKind::Minus => {
                    let (prec, assoc) = self.get_operator_info("-");
                    if prec < min_prec {
                        break;
                    }
                    (Ident::from_str("-"), prec, assoc)
                }
                // Handle % (Percent token is lexed separately)
                TokenKind::Percent => {
                    let (prec, assoc) = self.get_operator_info("%");
                    if prec < min_prec {
                        break;
                    }
                    (Ident::from_str("%"), prec, assoc)
                }
                TokenKind::Backtick => {
                    // Infix function application: `x `mod` y` or `x `E.catch` y`.
                    // Backtick-quoted identifiers have default precedence 9, left-assoc.
                    // Bail BEFORE consuming any tokens if the precedence is too low —
                    // otherwise we would strand the operator and its trailing operand.
                    if 9 < min_prec {
                        break;
                    }
                    self.advance(); // `
                    let Some(func_tok) = self.current() else {
                        return Err(ParseError::UnexpectedEof {
                            expected: "identifier".to_string(),
                        });
                    };
                    let func = match &func_tok.node.kind {
                        TokenKind::Ident(sym) => Ident::new(*sym),
                        TokenKind::ConId(sym) => Ident::new(*sym),
                        TokenKind::QualIdent(qual, name) => {
                            // Create qualified name like "E.catch"
                            let full_name = format!("{}.{}", qual.as_str(), name.as_str());
                            Ident::from_str(&full_name)
                        }
                        _ => {
                            return Err(ParseError::Unexpected {
                                found: func_tok.node.kind.description().to_string(),
                                expected: "identifier".to_string(),
                                span: func_tok.span,
                            });
                        }
                    };
                    self.advance();
                    self.expect(&TokenKind::Backtick)?;
                    op_consumed = true; // Already consumed the entire `func` operator
                    (func, 9, Assoc::Left) // Default infix precedence
                }
                // Handle . (Dot token for function composition)
                TokenKind::Dot => {
                    let (prec, assoc) = self.get_operator_info(".");
                    if prec < min_prec {
                        break;
                    }
                    (Ident::from_str("."), prec, assoc)
                }
                // Handle constructor operators like : and :|
                TokenKind::ConOperator(sym) => {
                    let (prec, assoc) = self.get_operator_info(sym.as_str());
                    if prec < min_prec {
                        break;
                    }
                    (Ident::new(*sym), prec, assoc)
                }
                // Handle qualified operators like Seq.|> or M.\\
                TokenKind::QualOperator(qual, sym) => {
                    let (prec, assoc) = self.get_operator_info(sym.as_str());
                    if prec < min_prec {
                        break;
                    }
                    let full_name = format!("{}.{}", qual.as_str(), sym.as_str());
                    (Ident::from_str(&full_name), prec, assoc)
                }
                // Handle qualified constructor operators like Seq.:> in expressions
                TokenKind::QualConOperator(qual, sym) => {
                    let (prec, assoc) = self.get_operator_info(sym.as_str());
                    if prec < min_prec {
                        break;
                    }
                    let full_name = format!("{}.{}", qual.as_str(), sym.as_str());
                    (Ident::from_str(&full_name), prec, assoc)
                }
                // Single-char tokens that can also appear as binary operators.
                // The lexer emits them as standalone tokens because they have
                // other meanings (bang patterns, implicit params, magic hash).
                // In infix position they are operators.
                TokenKind::Bang => {
                    let (prec, assoc) = self.get_operator_info("!");
                    if prec < min_prec {
                        break;
                    }
                    (Ident::from_str("!"), prec, assoc)
                }
                TokenKind::Question => {
                    let (prec, assoc) = self.get_operator_info("?");
                    if prec < min_prec {
                        break;
                    }
                    (Ident::from_str("?"), prec, assoc)
                }
                TokenKind::Hash => {
                    let (prec, assoc) = self.get_operator_info("#");
                    if prec < min_prec {
                        break;
                    }
                    (Ident::from_str("#"), prec, assoc)
                }
                _ => break,
            };

            if prec < min_prec {
                break;
            }

            let _op_span = self.current_span();
            if !op_consumed {
                self.advance();
            }

            let next_min_prec = match assoc {
                Assoc::Left => prec + 1,
                Assoc::Right => prec,
                Assoc::None => prec + 1,
            };

            let rhs = self.parse_infix_expr(next_min_prec)?;
            let span = lhs.span().merge(rhs.span());
            lhs = Expr::Infix(Box::new(lhs), op, Box::new(rhs), span);
        }

        Ok(lhs)
    }

    /// Parse a prefix expression (negation, etc.).
    fn parse_prefix_expr(&mut self) -> ParseResult<Expr> {
        // This is the knot every nested expression form recurses through
        // (paren exprs re-enter here without passing parse_expr), so the
        // depth guard lives here as well
        self.enter_recursion()?;
        let result = self.parse_prefix_expr_guarded();
        self.exit_recursion();
        result
    }

    fn parse_prefix_expr_guarded(&mut self) -> ParseResult<Expr> {
        if let Some(tok) = self.current() {
            // Handle prefix negation - lexer produces TokenKind::Minus for standalone `-`
            let is_minus = matches!(&tok.node.kind, TokenKind::Minus)
                || matches!(&tok.node.kind, TokenKind::Operator(s) if s.as_str() == "-");
            if is_minus {
                let start = tok.span;
                self.advance();
                let expr = self.parse_prefix_expr()?;
                let span = start.to(expr.span());
                return Ok(Expr::Neg(Box::new(expr), span));
            }
        }

        self.parse_app_expr()
    }

    /// Parse an application expression.
    fn parse_app_expr(&mut self) -> ParseResult<Expr> {
        let mut expr = self.parse_atom_expr()?;

        loop {
            // Check for record update: `expr { field = value }`
            if self.check(&TokenKind::LBrace) && !matches!(expr, Expr::Con(_, _)) {
                let start = expr.span();
                expr = self.parse_record_update(expr, start)?;
                continue;
            }

            // Check for type application: `expr @Type`
            if self.check(&TokenKind::At) {
                self.advance(); // consume @
                let ty = self.parse_atype()?;
                let span = expr.span().to(ty.span());
                expr = Expr::TypeApp(Box::new(expr), ty, span);
                continue;
            }

            // Check if this looks like an argument
            if let Some(tok) = self.current() {
                if self.is_atom_start(&tok.node.kind) {
                    let arg = self.parse_atom_expr()?;
                    let span = expr.span().to(arg.span());
                    expr = Expr::App(Box::new(expr), Box::new(arg), span);
                    continue;
                }
            }

            break;
        }

        Ok(expr)
    }

    /// Check if a token can start an atom expression.
    fn is_atom_start(&self, kind: &TokenKind) -> bool {
        matches!(
            kind,
            TokenKind::Ident(_)
                | TokenKind::QualIdent(_, _)
                | TokenKind::ConId(_)
                | TokenKind::QualConId(_, _)
                | TokenKind::IntLit(_)
                | TokenKind::FloatLit(_)
                | TokenKind::CharLit(_)
                | TokenKind::StringLit(_)
                | TokenKind::LParen
                | TokenKind::LBracket
                | TokenKind::Backslash
                | TokenKind::Let
                | TokenKind::If
                | TokenKind::Case
                | TokenKind::Do
                | TokenKind::Underscore // For holes/wildcards in patterns that are parsed as expressions first
        )
    }

    /// Parse an atomic expression.
    fn parse_atom_expr(&mut self) -> ParseResult<Expr> {
        let tok = self.current().ok_or(ParseError::UnexpectedEof {
            expected: "expression".to_string(),
        })?;

        match &tok.node.kind.clone() {
            TokenKind::Ident(sym)
                if sym.as_str() == "lazy"
                    && self.pos + 1 < self.tokens.len()
                    && self.tokens[self.pos + 1].node.kind == TokenKind::LBrace =>
            {
                self.parse_lazy_expr()
            }
            TokenKind::Ident(sym) => {
                let ident = Ident::new(*sym);
                let span = tok.span;
                self.advance();
                Ok(Expr::Var(ident, span))
            }

            TokenKind::QualIdent(qualifier, name) => {
                let module_name = ModuleName {
                    parts: vec![*qualifier],
                    span: tok.span,
                };
                let ident = Ident::new(*name);
                let span = tok.span;
                self.advance();
                Ok(Expr::QualVar(module_name, ident, span))
            }

            TokenKind::ConId(sym) => {
                let ident = Ident::new(*sym);
                let span = tok.span;
                self.advance();

                // Check for record construction: Con { field = value }
                if self.check(&TokenKind::LBrace) {
                    return self.parse_record_con(ident, span);
                }

                Ok(Expr::Con(ident, span))
            }

            TokenKind::QualConId(qualifier, name) => {
                let module_name = ModuleName {
                    parts: vec![*qualifier],
                    span: tok.span,
                };
                let ident = Ident::new(*name);
                let span = tok.span;
                self.advance();

                // Check for record construction: M.Con { field = value }
                if self.check(&TokenKind::LBrace) {
                    return self.parse_qual_record_con(module_name, ident, span);
                }

                Ok(Expr::QualCon(module_name, ident, span))
            }

            TokenKind::IntLit(ref lit) => {
                let span = tok.span;
                let value = self.parse_int_literal(&lit.text, span)?;
                self.advance();
                Ok(Expr::Lit(Lit::Int(value), span))
            }

            TokenKind::FloatLit(ref lit) => {
                let span = tok.span;
                let value = self.parse_float_literal(&lit.text, span)?;
                self.advance();
                Ok(Expr::Lit(Lit::Float(value), span))
            }

            TokenKind::CharLit(c) => {
                let span = tok.span;
                let c = *c;
                self.advance();
                Ok(Expr::Lit(Lit::Char(c), span))
            }

            TokenKind::StringLit(s) => {
                let span = tok.span;
                let s = s.clone();
                self.advance();
                Ok(Expr::Lit(Lit::String(s), span))
            }

            TokenKind::LParen => self.parse_paren_expr(),

            TokenKind::LBracket => self.parse_list_expr(),

            TokenKind::Backslash => self.parse_lambda(),

            TokenKind::Let => self.parse_let_expr(),

            TokenKind::If => self.parse_if_expr(),

            TokenKind::Case => self.parse_case_expr(),

            TokenKind::Do => self.parse_do_expr(),

            TokenKind::Underscore => {
                // Wildcard/hole - used in patterns that are parsed as expressions first
                let span = tok.span;
                self.advance();
                Ok(Expr::Wildcard(span))
            }

            _ => Err(ParseError::Unexpected {
                found: tok.node.kind.description().to_string(),
                expected: "expression".to_string(),
                span: tok.span,
            }),
        }
    }

    /// Parse an integer literal.
    pub(crate) fn parse_int_literal(&self, s: &str, span: Span) -> ParseResult<i64> {
        let s = s.replace('_', "");
        let value = if s.starts_with("0x") || s.starts_with("0X") {
            i64::from_str_radix(&s[2..], 16)
        } else if s.starts_with("0o") || s.starts_with("0O") {
            i64::from_str_radix(&s[2..], 8)
        } else if s.starts_with("0b") || s.starts_with("0B") {
            i64::from_str_radix(&s[2..], 2)
        } else {
            s.parse()
        };

        value.map_err(|e| ParseError::InvalidLiteral {
            message: e.to_string(),
            span,
        })
    }

    /// Parse a float literal.
    pub(crate) fn parse_float_literal(&self, s: &str, span: Span) -> ParseResult<f64> {
        let s = s.replace('_', "");
        s.parse()
            .map_err(|e: std::num::ParseFloatError| ParseError::InvalidLiteral {
                message: e.to_string(),
                span,
            })
    }

    /// Get operator precedence and associativity.
    fn get_operator_info(&self, op: &str) -> (u8, Assoc) {
        // Default precedences (can be overridden by fixity declarations)
        match op {
            "." => (9, Assoc::Right),
            "^" | "^^" | "**" => (8, Assoc::Right),
            "*" | "/" | "`div`" | "`mod`" => (7, Assoc::Left),
            "+" | "-" => (6, Assoc::Left),
            "<>" => (6, Assoc::Right),
            ":" | "++" => (5, Assoc::Right),
            "==" | "/=" | "<" | "<=" | ">" | ">=" | "`elem`" | "`notElem`" => (4, Assoc::None),
            "<$>" | "<*>" | "<*" | "*>" => (4, Assoc::Left),
            "<$" => (4, Assoc::Left),
            "<|>" => (3, Assoc::Left),
            "&&" => (3, Assoc::Right),
            "||" => (2, Assoc::Right),
            ">>=" | ">>" => (1, Assoc::Left),
            "=<<" => (1, Assoc::Right),
            "$" | "$!" => (0, Assoc::Right),
            _ => (9, Assoc::Left), // Default
        }
    }

    /// Parse a parenthesized expression, tuple, or operator section.
    fn parse_paren_expr(&mut self) -> ParseResult<Expr> {
        let start = self.current_span();
        self.expect(&TokenKind::LParen)?;

        if self.eat(&TokenKind::RParen) {
            // Unit: ()
            let span = start.to(self.tokens[self.pos - 1].span);
            return Ok(Expr::Con(Ident::from_str("()"), span));
        }

        // TupleSections: (,x) or (,x,y) or (,,) etc.
        // Detect leading hole: `(` followed by `,`
        if self.check(&TokenKind::Comma) {
            return self.parse_tuple_section(start, vec![None]);
        }

        // Check for operator section starting with operator: `(+)` or `(+ x)` or `(.)` or `(:)`
        // Note: `-` is NOT included here because `(-5)` should be negative 5, not a section.
        // Only `(-)` (minus followed immediately by rparen) should be a section.
        // We handle that case below.
        if matches!(
            self.current_kind(),
            Some(TokenKind::Operator(_))
                | Some(TokenKind::Star)
                | Some(TokenKind::Percent)
                | Some(TokenKind::Dot)
                | Some(TokenKind::ConOperator(_))
                | Some(TokenKind::QualOperator(_, _))
                | Some(TokenKind::QualConOperator(_, _))
        ) {
            return self.parse_operator_section(start);
        }

        // Special case for `-`: only `(-)` is a section, otherwise it's negation
        if matches!(self.current_kind(), Some(TokenKind::Minus)) {
            // Peek at the next token to see if it's immediately followed by `)`
            if self.pos + 1 < self.tokens.len()
                && matches!(self.tokens[self.pos + 1].node.kind, TokenKind::RParen)
            {
                // `(-)` is the subtraction operator as a function
                return self.parse_operator_section(start);
            }
            // Otherwise, fall through to parse as prefix negation
        }

        // Check for backtick right section: (`op` x) means \y -> y `op` x
        if self.check(&TokenKind::Backtick) {
            self.advance(); // consume opening backtick
            let Some(func_tok) = self.current() else {
                return Err(ParseError::UnexpectedEof {
                    expected: "identifier".to_string(),
                });
            };
            let func = match &func_tok.node.kind {
                TokenKind::Ident(sym) => Ident::new(*sym),
                TokenKind::ConId(sym) => Ident::new(*sym),
                TokenKind::QualIdent(qual, name) => {
                    let full_name = format!("{}.{}", qual.as_str(), name.as_str());
                    Ident::from_str(&full_name)
                }
                _ => {
                    return Err(ParseError::Unexpected {
                        found: func_tok.node.kind.description().to_string(),
                        expected: "identifier".to_string(),
                        span: func_tok.span,
                    });
                }
            };
            self.advance();
            self.expect(&TokenKind::Backtick)?; // closing backtick

            // Parse the RHS of the section
            let rhs = self.parse_app_expr()?;

            let end = self.expect(&TokenKind::RParen)?;
            let span = start.to(end.span);

            // (`op` x) desugars to (\y -> y `op` x)
            let y = Ident::from_str("$section_arg");
            let y_pat = Pat::Var(y, Span::DUMMY);
            let y_expr = Expr::Var(y, Span::DUMMY);
            let body = Expr::Infix(Box::new(y_expr), func, Box::new(rhs), span);
            return Ok(Expr::Lam(vec![y_pat], Box::new(body), span));
        }

        // For left sections like `(1 +)`, we need to parse without consuming operators
        // Parse prefix expression first (handles negation like `(-5)`)
        let first = self.parse_prefix_expr()?;

        // Check for left operator section: `(x +)` or infix expression in parens
        let op_ident = match self.current_kind().cloned() {
            Some(TokenKind::Operator(sym)) => Some(Ident::new(sym)),
            Some(TokenKind::Star) => Some(Ident::from_str("*")),
            Some(TokenKind::Minus) => Some(Ident::from_str("-")),
            Some(TokenKind::Percent) => Some(Ident::from_str("%")),
            Some(TokenKind::Dot) => Some(Ident::from_str(".")),
            Some(TokenKind::ConOperator(sym)) => Some(Ident::new(sym)),
            Some(TokenKind::QualOperator(qual, sym)) => {
                let full_name = format!("{}.{}", qual.as_str(), sym.as_str());
                Some(Ident::from_str(&full_name))
            }
            Some(TokenKind::QualConOperator(qual, sym)) => {
                let full_name = format!("{}.{}", qual.as_str(), sym.as_str());
                Some(Ident::from_str(&full_name))
            }
            _ => None,
        };

        if let Some(op) = op_ident {
            self.advance();

            if self.eat(&TokenKind::RParen) {
                // Left section: `(x +)` desugars to `(\y -> x + y)`
                let span = start.to(self.tokens[self.pos - 1].span);
                let y = Ident::from_str("$section_arg");
                let y_pat = Pat::Var(y, Span::DUMMY);
                let y_expr = Expr::Var(y, Span::DUMMY);
                let body = Expr::Infix(Box::new(first), op, Box::new(y_expr), span);
                return Ok(Expr::Lam(vec![y_pat], Box::new(body), span));
            }

            // Otherwise, this is an infix expression - could be in parens or part of a tuple
            // Use proper precedence for the consumed operator so that lower-precedence
            // operators don't get absorbed into the RHS.
            let (op_prec, op_assoc) = self.get_operator_info(op.name.as_str());
            let next_min_prec = match op_assoc {
                Assoc::Left => op_prec + 1,
                Assoc::Right => op_prec,
                Assoc::None => op_prec + 1,
            };
            let rhs = self.parse_infix_expr(next_min_prec)?;
            let infix_span = first.span().merge(rhs.span());
            let first_infix = Expr::Infix(Box::new(first), op, Box::new(rhs), infix_span);
            // Continue parsing any remaining infix operators (e.g., `1 % 3 + 1 % 6`)
            let infix_expr = self.continue_infix_expr(first_infix, 0)?;

            // Check for type annotation: (expr $ expr :: Type)
            let infix_expr = if self.check(&TokenKind::DoubleColon) {
                self.parse_type_annotation(infix_expr)?
            } else {
                infix_expr
            };

            // Check if this is a tuple: (a .|. b, c, ...)
            if self.eat(&TokenKind::Comma) {
                let mut exprs = vec![infix_expr];
                loop {
                    exprs.push(self.parse_expr()?);
                    if !self.eat(&TokenKind::Comma) {
                        break;
                    }
                }
                let end = self.expect(&TokenKind::RParen)?;
                let span = start.to(end.span);
                return Ok(Expr::Tuple(exprs, span));
            }

            let end = self.expect(&TokenKind::RParen)?;
            let span = start.to(end.span);
            return Ok(Expr::Paren(Box::new(infix_expr), span));
        }

        // Check for backtick infix: (x `elem` xs) or left section: (x `op`)
        if self.check(&TokenKind::Backtick) {
            // Parse the backtick infix expression
            self.advance(); // consume first backtick
            let Some(func_tok) = self.current() else {
                return Err(ParseError::UnexpectedEof {
                    expected: "identifier".to_string(),
                });
            };
            let func = match &func_tok.node.kind {
                TokenKind::Ident(sym) => Ident::new(*sym),
                TokenKind::ConId(sym) => Ident::new(*sym),
                TokenKind::QualIdent(qual, name) => {
                    let full_name = format!("{}.{}", qual.as_str(), name.as_str());
                    Ident::from_str(&full_name)
                }
                _ => {
                    return Err(ParseError::Unexpected {
                        found: func_tok.node.kind.description().to_string(),
                        expected: "identifier".to_string(),
                        span: func_tok.span,
                    });
                }
            };
            self.advance();
            self.expect(&TokenKind::Backtick)?; // closing backtick

            // Check for left section: (x `op`) means \y -> x `op` y
            if self.eat(&TokenKind::RParen) {
                let span = start.to(self.tokens[self.pos - 1].span);
                let y = Ident::from_str("$section_arg");
                let y_pat = Pat::Var(y, Span::DUMMY);
                let y_expr = Expr::Var(y, Span::DUMMY);
                let body = Expr::Infix(Box::new(first), func, Box::new(y_expr), span);
                return Ok(Expr::Lam(vec![y_pat], Box::new(body), span));
            }

            // Parse RHS for full infix expression with proper precedence
            let (bt_prec, bt_assoc) = self.get_operator_info(func.name.as_str());
            let bt_next_min = match bt_assoc {
                Assoc::Left => bt_prec + 1,
                Assoc::Right => bt_prec,
                Assoc::None => bt_prec + 1,
            };
            let rhs = self.parse_infix_expr(bt_next_min)?;
            let infix_span = first.span().merge(rhs.span());
            let first_infix = Expr::Infix(Box::new(first), func, Box::new(rhs), infix_span);
            let infix_expr = self.continue_infix_expr(first_infix, 0)?;

            // Check for type annotation: (expr `op` expr :: Type)
            let infix_expr = if self.check(&TokenKind::DoubleColon) {
                self.parse_type_annotation(infix_expr)?
            } else {
                infix_expr
            };

            // Check for tuple
            if self.eat(&TokenKind::Comma) {
                let mut exprs = vec![infix_expr];
                loop {
                    exprs.push(self.parse_expr()?);
                    if !self.eat(&TokenKind::Comma) {
                        break;
                    }
                }
                let end = self.expect(&TokenKind::RParen)?;
                let span = start.to(end.span);
                return Ok(Expr::Tuple(exprs, span));
            }

            let end = self.expect(&TokenKind::RParen)?;
            let span = start.to(end.span);
            return Ok(Expr::Paren(Box::new(infix_expr), span));
        }

        // Check for type annotation: (expr :: Type)
        if self.check(&TokenKind::DoubleColon) {
            let annotated = self.parse_type_annotation(first)?;
            // Check for tuple with type annotation: (expr :: Type, ...)
            if self.eat(&TokenKind::Comma) {
                let mut exprs = vec![annotated];
                loop {
                    exprs.push(self.parse_expr()?);
                    if !self.eat(&TokenKind::Comma) {
                        break;
                    }
                }
                let end = self.expect(&TokenKind::RParen)?;
                let span = start.to(end.span);
                return Ok(Expr::Tuple(exprs, span));
            }
            let end = self.expect(&TokenKind::RParen)?;
            let span = start.to(end.span);
            return Ok(Expr::Paren(Box::new(annotated), span));
        }

        if self.eat(&TokenKind::Comma) {
            // Check for TupleSections: (x,,z) or (x,) etc.
            // If the next token is `,` or `)`, there's a hole after the comma.
            if self.check(&TokenKind::Comma) || self.check(&TokenKind::RParen) {
                // The comma we just ate separates first from a hole
                return self.parse_tuple_section(start, vec![Some(first), None]);
            }

            // Regular tuple
            let mut exprs = vec![first];
            loop {
                exprs.push(self.parse_expr()?);
                if !self.eat(&TokenKind::Comma) {
                    break;
                }
                // Check for hole after comma in middle of tuple
                if self.check(&TokenKind::Comma) || self.check(&TokenKind::RParen) {
                    // Switch to tuple section parser, adding None for the hole
                    let mut elements: Vec<Option<Expr>> = exprs.into_iter().map(Some).collect();
                    elements.push(None);
                    return self.parse_tuple_section(start, elements);
                }
            }
            let end = self.expect(&TokenKind::RParen)?;
            let span = start.to(end.span);
            Ok(Expr::Tuple(exprs, span))
        } else {
            // Parenthesized expression
            let end = self.expect(&TokenKind::RParen)?;
            let span = start.to(end.span);
            Ok(Expr::Paren(Box::new(first), span))
        }
    }

    /// Parse a tuple section (TupleSections extension).
    ///
    /// Called when a hole (missing expression) has been detected in a tuple.
    /// `elements` contains the already-parsed elements (Some = present, None = hole).
    /// Continues parsing remaining elements from the current position.
    ///
    /// Desugars `(,x)` to `\a -> (a, x)`, `(x,,z)` to `\b -> (x, b, z)`, etc.
    fn parse_tuple_section(
        &mut self,
        start: Span,
        mut elements: Vec<Option<Expr>>,
    ) -> ParseResult<Expr> {
        // Continue parsing remaining elements
        loop {
            if self.eat(&TokenKind::RParen) {
                break;
            }
            if self.eat(&TokenKind::Comma) {
                // Check if next is comma or rparen → hole
                if self.check(&TokenKind::Comma) || self.check(&TokenKind::RParen) {
                    elements.push(None);
                } else {
                    elements.push(Some(self.parse_expr()?));
                }
            } else {
                // Should not happen — we expect commas between elements
                elements.push(Some(self.parse_expr()?));
                if !self.eat(&TokenKind::Comma) {
                    self.expect(&TokenKind::RParen)?;
                    break;
                }
                // After comma, check for hole
                if self.check(&TokenKind::Comma) || self.check(&TokenKind::RParen) {
                    elements.push(None);
                }
            }
        }

        let span = start.to(self.tokens[self.pos.saturating_sub(1)].span);

        // Generate fresh parameter names for holes
        let mut params = Vec::new();
        let mut tuple_exprs = Vec::new();
        let mut hole_idx = 0;

        for elem in elements {
            match elem {
                Some(expr) => tuple_exprs.push(expr),
                None => {
                    let name = Ident::from_str(&format!("$tsec_{}", hole_idx));
                    hole_idx += 1;
                    params.push(Pat::Var(name, Span::DUMMY));
                    tuple_exprs.push(Expr::Var(name, Span::DUMMY));
                }
            }
        }

        let tuple = Expr::Tuple(tuple_exprs, span);

        if params.is_empty() {
            // No holes — shouldn't happen but return the tuple
            Ok(tuple)
        } else {
            Ok(Expr::Lam(params, Box::new(tuple), span))
        }
    }

    /// Parse a list expression.
    fn parse_list_expr(&mut self) -> ParseResult<Expr> {
        let start = self.current_span();
        self.expect(&TokenKind::LBracket)?;

        if self.eat(&TokenKind::RBracket) {
            // Empty list
            let span = start.to(self.tokens[self.pos - 1].span);
            return Ok(Expr::List(vec![], span));
        }

        let first = self.parse_expr()?;

        // Check for list comprehension: [x | ...]
        if self.eat(&TokenKind::Pipe) {
            let stmts = self.parse_list_comp_stmts()?;
            let end = self.expect(&TokenKind::RBracket)?;
            let span = start.to(end.span);
            return Ok(Expr::ListComp(Box::new(first), stmts, span));
        }

        // Check for arithmetic sequence: [1..] or [1..10] or [1,2..]
        if self.eat(&TokenKind::DotDot) {
            if self.eat(&TokenKind::RBracket) {
                // [from..]
                let span = start.to(self.tokens[self.pos - 1].span);
                return Ok(Expr::ArithSeq(ArithSeq::From(Box::new(first)), span));
            }
            let to = self.parse_expr()?;
            let end = self.expect(&TokenKind::RBracket)?;
            let span = start.to(end.span);
            return Ok(Expr::ArithSeq(
                ArithSeq::FromTo(Box::new(first), Box::new(to)),
                span,
            ));
        }

        if self.eat(&TokenKind::Comma) {
            // Could be tuple-like list or [from, then..]
            let second = self.parse_expr()?;

            if self.eat(&TokenKind::DotDot) {
                if self.eat(&TokenKind::RBracket) {
                    // [from, then..]
                    let span = start.to(self.tokens[self.pos - 1].span);
                    return Ok(Expr::ArithSeq(
                        ArithSeq::FromThen(Box::new(first), Box::new(second)),
                        span,
                    ));
                }
                let to = self.parse_expr()?;
                let end = self.expect(&TokenKind::RBracket)?;
                let span = start.to(end.span);
                return Ok(Expr::ArithSeq(
                    ArithSeq::FromThenTo(Box::new(first), Box::new(second), Box::new(to)),
                    span,
                ));
            }

            // Regular list
            let mut exprs = vec![first, second];
            while self.eat(&TokenKind::Comma) {
                exprs.push(self.parse_expr()?);
            }
            let end = self.expect(&TokenKind::RBracket)?;
            let span = start.to(end.span);
            return Ok(Expr::List(exprs, span));
        }

        // Single-element list
        let end = self.expect(&TokenKind::RBracket)?;
        let span = start.to(end.span);
        Ok(Expr::List(vec![first], span))
    }

    /// Parse list comprehension statements.
    fn parse_list_comp_stmts(&mut self) -> ParseResult<Vec<Stmt>> {
        let mut stmts = vec![self.parse_stmt()?];
        while self.eat(&TokenKind::Comma) {
            stmts.push(self.parse_stmt()?);
        }
        Ok(stmts)
    }

    /// Parse a statement (for do blocks and list comprehensions).
    fn parse_stmt(&mut self) -> ParseResult<Stmt> {
        let start = self.current_span();

        // Try to parse as generator: pat <- expr
        // For simplicity, we first try parsing an expression
        // and check if it's followed by <-
        if self.check(&TokenKind::Let) {
            self.advance();
            let decls = self.parse_local_decls()?;
            let span = start.to(self.tokens[self.pos - 1].span);
            return Ok(Stmt::LetStmt(decls, span));
        }

        // First try to parse as a pattern directly if we might have one
        // This is needed because patterns can contain `@` which isn't valid in expressions
        // We try parsing as a pattern and check for `<-`
        let saved_pos = self.pos;

        // Try pattern first - if it fails or isn't followed by `<-`, backtrack
        if self.is_pattern_start() {
            if let Ok(pat) = self.parse_pattern() {
                if self.eat(&TokenKind::LeftArrow) {
                    let expr = self.parse_expr()?;
                    let span = start.to(expr.span());
                    return Ok(Stmt::Generator(pat, expr, span));
                }
            }
            // Backtrack - not a generator
            self.pos = saved_pos;
        }

        // Parse as expression
        let expr = self.parse_expr()?;
        let span = expr.span();
        Ok(Stmt::Qualifier(expr, span))
    }

    /// Convert an expression to a pattern (for generators).
    #[allow(dead_code)]
    fn expr_to_pat(&self, expr: Expr) -> ParseResult<Pat> {
        match expr {
            Expr::Var(id, span) => Ok(Pat::Var(id, span)),
            Expr::Con(id, span) => Ok(Pat::Con(id, vec![], span)),
            Expr::Lit(lit, span) => Ok(Pat::Lit(lit, span)),
            Expr::Tuple(exprs, span) => {
                let pats: ParseResult<Vec<_>> =
                    exprs.into_iter().map(|e| self.expr_to_pat(e)).collect();
                Ok(Pat::Tuple(pats?, span))
            }
            Expr::List(exprs, span) => {
                let pats: ParseResult<Vec<_>> =
                    exprs.into_iter().map(|e| self.expr_to_pat(e)).collect();
                Ok(Pat::List(pats?, span))
            }
            Expr::Paren(e, span) => {
                let p = self.expr_to_pat(*e)?;
                Ok(Pat::Paren(Box::new(p), span))
            }
            Expr::QualCon(module, id, span) => {
                // Qualified constructor - create qualified name for pattern
                let qual_name = format!(
                    "{}.{}",
                    module
                        .parts
                        .iter()
                        .map(|s| s.as_str())
                        .collect::<Vec<_>>()
                        .join("."),
                    id.as_str()
                );
                Ok(Pat::Con(Ident::from_str(&qual_name), vec![], span))
            }
            Expr::App(f, x, span) => {
                // Could be constructor application (Con arg1 arg2 ...)
                // Flatten nested applications
                let mut args = vec![*x];
                let mut cur = *f;
                while let Expr::App(inner_f, inner_x, _) = cur {
                    args.push(*inner_x);
                    cur = *inner_f;
                }
                args.reverse();

                let con_id = match cur {
                    Expr::Con(id, _) => id,
                    Expr::QualCon(module, id, _) => {
                        let qual_name = format!(
                            "{}.{}",
                            module
                                .parts
                                .iter()
                                .map(|s| s.as_str())
                                .collect::<Vec<_>>()
                                .join("."),
                            id.as_str()
                        );
                        Ident::from_str(&qual_name)
                    }
                    _ => {
                        return Err(ParseError::Unexpected {
                            found: "expression".to_string(),
                            expected: "pattern".to_string(),
                            span,
                        });
                    }
                };

                let pats: ParseResult<Vec<_>> =
                    args.into_iter().map(|e| self.expr_to_pat(e)).collect();
                Ok(Pat::Con(con_id, pats?, span))
            }
            Expr::Wildcard(span) => Ok(Pat::Wildcard(span)),
            Expr::RecordCon(con, field_binds, has_wildcard, span) => {
                // Record pattern: Con { field = pat, ... }
                use bhc_ast::FieldPat;
                let mut field_pats = Vec::new();
                for fb in field_binds {
                    let pat = match fb.value {
                        Some(expr) => Some(self.expr_to_pat(expr)?),
                        None => None, // Punning: { x } means { x = x }
                    };
                    field_pats.push(FieldPat {
                        qualifier: fb.qualifier,
                        name: fb.name,
                        pat,
                        span: fb.span,
                    });
                }
                Ok(Pat::Record(con, field_pats, has_wildcard, span))
            }
            Expr::QualRecordCon(module_name, con, field_binds, has_wildcard, span) => {
                // Qualified record pattern: M.Con { field = pat, ... }
                use bhc_ast::FieldPat;
                let mut field_pats = Vec::new();
                for fb in field_binds {
                    let pat = match fb.value {
                        Some(expr) => Some(self.expr_to_pat(expr)?),
                        None => None,
                    };
                    field_pats.push(FieldPat {
                        qualifier: fb.qualifier,
                        name: fb.name,
                        pat,
                        span: fb.span,
                    });
                }
                Ok(Pat::QualRecord(
                    module_name,
                    con,
                    field_pats,
                    has_wildcard,
                    span,
                ))
            }
            _ => Err(ParseError::Unexpected {
                found: "expression".to_string(),
                expected: "pattern".to_string(),
                span: expr.span(),
            }),
        }
    }

    /// Parse a lambda expression or lambda-case.
    fn parse_lambda(&mut self) -> ParseResult<Expr> {
        let start = self.current_span();
        self.expect(&TokenKind::Backslash)?;

        // Check for lambda-case: \case { ... }
        if self.eat(&TokenKind::Case) {
            let alts = self.parse_case_alts()?;
            let span = start.to(alts.last().map(|a| a.span).unwrap_or(start));

            // Lambda-case desugars to \x -> case x of { ... }
            let x = Ident::from_str("$lambdacase_arg");
            let x_pat = Pat::Var(x, Span::DUMMY);
            let x_expr = Expr::Var(x, Span::DUMMY);
            let case_expr = Expr::Case(Box::new(x_expr), alts, span);
            return Ok(Expr::Lam(vec![x_pat], Box::new(case_expr), span));
        }

        let mut pats = vec![self.parse_pattern()?];
        while !self.check(&TokenKind::Arrow) && !self.at_eof() {
            if self.is_pattern_start() {
                pats.push(self.parse_pattern()?);
            } else {
                break;
            }
        }

        self.expect(&TokenKind::Arrow)?;
        let body = self.parse_expr()?;
        let span = start.to(body.span());

        Ok(Expr::Lam(pats, Box::new(body), span))
    }

    /// Parse a let expression.
    fn parse_let_expr(&mut self) -> ParseResult<Expr> {
        let start = self.current_span();
        self.expect(&TokenKind::Let)?;

        let decls = self.parse_local_decls()?;

        self.expect(&TokenKind::In)?;

        // When `in` appears on the same line as a case alternative (e.g.,
        //   let x = case n of _ -> 0 in body
        // ), the lexer still emits VirtualRBrace tokens for the case-of and
        // let layout contexts when the next line is at a lower column. These
        // are "phantom" closers — the blocks were already semantically
        // terminated by `in`. Skip them so `parse_expr` sees the body.
        while self.check(&TokenKind::VirtualRBrace) {
            self.advance();
        }

        let body = self.parse_expr()?;
        let span = start.to(body.span());

        Ok(Expr::Let(decls, Box::new(body), span))
    }

    /// Parse an if expression.
    fn parse_if_expr(&mut self) -> ParseResult<Expr> {
        let start = self.current_span();
        self.expect(&TokenKind::If)?;

        // MultiWayIf: if | cond1 -> e1 | cond2 -> e2 | otherwise -> e3
        if self.check(&TokenKind::Pipe) {
            return self.parse_multi_way_if(start);
        }

        let cond = self.parse_expr()?;
        self.expect(&TokenKind::Then)?;
        let then_branch = self.parse_expr()?;
        self.expect(&TokenKind::Else)?;
        let else_branch = self.parse_expr()?;

        let span = start.to(else_branch.span());
        Ok(Expr::If(
            Box::new(cond),
            Box::new(then_branch),
            Box::new(else_branch),
            span,
        ))
    }

    /// Parse a multi-way if expression (MultiWayIf extension).
    ///
    /// Desugars `if | c1 -> e1 | c2 -> e2 | otherwise -> e3` into
    /// nested `if c1 then e1 else if c2 then e2 else e3`.
    fn parse_multi_way_if(&mut self, start: Span) -> ParseResult<Expr> {
        let guarded_rhss = self.parse_guarded_rhss()?;

        if guarded_rhss.is_empty() {
            return Err(ParseError::Unexpected {
                found: "end of multi-way if".to_string(),
                expected: "guarded alternative".to_string(),
                span: start,
            });
        }

        // Desugar to nested if-then-else.
        // Build from the last guard backwards.
        let mut result = Expr::App(
            Box::new(Expr::Var(Ident::from_str("error"), start)),
            Box::new(Expr::Lit(
                Lit::String("Non-exhaustive guards in multi-way if".to_string()),
                start,
            )),
            start,
        );

        for grhs in guarded_rhss.into_iter().rev() {
            // For now, handle only single boolean guards (the common case).
            // Multi-guard and pattern guards fall through to the first guard expression.
            let cond = match grhs.guards.into_iter().next() {
                Some(Guard::Expr(cond_expr, _)) => cond_expr,
                Some(Guard::Pattern(_, expr, _)) => expr,
                None => continue,
            };
            let span = start.to(grhs.body.span());
            result = Expr::If(Box::new(cond), Box::new(grhs.body), Box::new(result), span);
        }

        Ok(result)
    }

    /// Parse a case expression.
    fn parse_case_expr(&mut self) -> ParseResult<Expr> {
        let start = self.current_span();
        self.expect(&TokenKind::Case)?;

        let scrutinee = self.parse_expr()?;
        self.expect(&TokenKind::Of)?;

        let alts = self.parse_case_alts()?;
        let span = start.to(self.tokens[self.pos.saturating_sub(1)].span);

        Ok(Expr::Case(Box::new(scrutinee), alts, span))
    }

    /// Parse case alternatives.
    fn parse_case_alts(&mut self) -> ParseResult<Vec<Alt>> {
        let mut alts = Vec::new();

        // Check for explicit or virtual opening brace
        let explicit_braces = self.eat(&TokenKind::LBrace);
        let virtual_braces = !explicit_braces && self.eat(&TokenKind::VirtualLBrace);
        let has_braces = explicit_braces || virtual_braces;

        if has_braces {
            // Parse alternatives separated by semicolons (real or virtual)
            let rbrace = if explicit_braces {
                TokenKind::RBrace
            } else {
                TokenKind::VirtualRBrace
            };

            // `in` terminates case blocks inside `let` (when on the same line
            // as the last alternative, the lexer doesn't insert VirtualRBrace)
            let is_terminated = |s: &Self| {
                s.check(&rbrace)
                    || s.check(&TokenKind::VirtualRBrace)
                    || (!explicit_braces && s.check(&TokenKind::In))
            };

            if !is_terminated(self) {
                alts.push(self.parse_alt()?);
                // Accept either real or virtual semicolons
                while self.eat(&TokenKind::Semi) || self.eat(&TokenKind::VirtualSemi) {
                    if is_terminated(self) {
                        break;
                    }
                    // Skip any extra semicolons
                    while self.eat(&TokenKind::Semi) || self.eat(&TokenKind::VirtualSemi) {}
                    if is_terminated(self) {
                        break;
                    }
                    alts.push(self.parse_alt()?);
                }
            }

            if explicit_braces {
                self.expect(&TokenKind::RBrace)?;
            } else {
                // Virtual braces: consume if present, but don't require.
                // Don't consume `in` — it's consumed by parse_let_expr.
                self.eat(&TokenKind::VirtualRBrace);
            }
        } else {
            // No braces at all - single alternative
            alts.push(self.parse_alt()?);
        }

        Ok(alts)
    }

    /// Parse a case alternative.
    fn parse_alt(&mut self) -> ParseResult<Alt> {
        let start = self.current_span();
        let pat = self.parse_pattern()?;

        // Check for guards: `| guard -> expr`
        let rhs = if self.check(&TokenKind::Pipe) {
            let guards = self.parse_guarded_rhss()?;
            let end_span = guards.last().map(|g| g.span).unwrap_or(start);
            Rhs::Guarded(guards, start.to(end_span))
        } else {
            self.expect(&TokenKind::Arrow)?;
            let expr = self.parse_expr()?;
            let span = start.to(expr.span());
            Rhs::Simple(expr, span)
        };

        // Parse optional where clause
        let wheres = if self.eat(&TokenKind::Where) {
            self.parse_local_decls()?
        } else {
            vec![]
        };

        let span = start.to(self.tokens[self.pos.saturating_sub(1)].span);
        Ok(Alt {
            pat,
            rhs,
            wheres,
            span,
        })
    }

    /// Parse guarded right-hand sides: `| guard -> expr | guard -> expr ...`
    fn parse_guarded_rhss(&mut self) -> ParseResult<Vec<GuardedRhs>> {
        let mut guarded_rhss = Vec::new();
        while self.eat(&TokenKind::Pipe) {
            let start = self.current_span();

            // Parse guards (can be multiple, separated by commas)
            // Each guard is either:
            //   - A pattern guard: `pat <- expr`
            //   - A boolean guard: `expr`
            let mut guards = Vec::new();
            loop {
                let guard = self.parse_guard_item()?;
                guards.push(guard);

                // Multiple guards can be separated by commas
                if !self.eat(&TokenKind::Comma) {
                    break;
                }
            }

            self.expect(&TokenKind::Arrow)?;
            let body = self.parse_expr()?;
            let span = start.to(body.span());
            guarded_rhss.push(GuardedRhs { guards, body, span });
        }
        Ok(guarded_rhss)
    }

    /// Parse a single guard item (pattern guard `pat <- expr` or boolean guard `expr`).
    fn parse_guard_item(&mut self) -> ParseResult<Guard> {
        let start = self.current_span();

        // Save position for backtracking
        let saved_pos = self.pos;

        // Try parsing as pattern guard: pat <- expr
        if let Ok(pat) = self.parse_pattern() {
            if self.eat(&TokenKind::LeftArrow) {
                // This is a pattern guard
                let expr = self.parse_expr()?;
                let span = start.to(expr.span());
                return Ok(Guard::Pattern(pat, expr, span));
            }
            // Not a pattern guard, backtrack
            self.pos = saved_pos;
        } else {
            // Couldn't parse as pattern, reset position
            self.pos = saved_pos;
        }

        // Parse as boolean guard
        let expr = self.parse_expr()?;
        let span = start.to(expr.span());
        Ok(Guard::Expr(expr, span))
    }

    /// Parse a do expression.
    fn parse_do_expr(&mut self) -> ParseResult<Expr> {
        let start = self.current_span();
        self.expect(&TokenKind::Do)?;

        let stmts = self.parse_do_stmts()?;
        let span = start.to(self.tokens[self.pos.saturating_sub(1)].span);

        Ok(Expr::Do(stmts, span))
    }

    /// Parse do statements.
    fn parse_do_stmts(&mut self) -> ParseResult<Vec<Stmt>> {
        let mut stmts = Vec::new();

        // Check for explicit or virtual opening brace
        let explicit_braces = self.eat(&TokenKind::LBrace);
        let virtual_braces = !explicit_braces && self.eat(&TokenKind::VirtualLBrace);
        let has_braces = explicit_braces || virtual_braces;

        if has_braces {
            // Parse statements separated by semicolons (real or virtual)
            let rbrace = if explicit_braces {
                TokenKind::RBrace
            } else {
                TokenKind::VirtualRBrace
            };

            if !self.check(&rbrace) {
                stmts.push(self.parse_stmt()?);
                // Accept either real or virtual semicolons
                while self.eat(&TokenKind::Semi) || self.eat(&TokenKind::VirtualSemi) {
                    if self.check(&rbrace) || self.check(&TokenKind::VirtualRBrace) {
                        break;
                    }
                    // Stop at 'where' - it belongs to the enclosing function binding
                    if self.check(&TokenKind::Where) {
                        break;
                    }
                    // Skip any extra semicolons
                    while self.eat(&TokenKind::Semi) || self.eat(&TokenKind::VirtualSemi) {}
                    if self.check(&rbrace) || self.check(&TokenKind::VirtualRBrace) {
                        break;
                    }
                    // Stop at 'where' - it belongs to the enclosing function binding
                    if self.check(&TokenKind::Where) {
                        break;
                    }
                    stmts.push(self.parse_stmt()?);
                }
            }

            if explicit_braces {
                self.expect(&TokenKind::RBrace)?;
            } else {
                // Virtual braces: consume if present, but don't require
                self.eat(&TokenKind::VirtualRBrace);
            }
        } else {
            // No braces at all - single statement
            stmts.push(self.parse_stmt()?);
        }

        Ok(stmts)
    }

    /// Parse a lazy expression (H26 extension).
    fn parse_lazy_expr(&mut self) -> ParseResult<Expr> {
        let start = self.current_span();
        self.expect_ident_str("lazy")?;
        self.expect(&TokenKind::LBrace)?;
        let expr = self.parse_expr()?;
        let end = self.expect(&TokenKind::RBrace)?;
        let span = start.to(end.span);

        Ok(Expr::Lazy(Box::new(expr), span))
    }

    /// Parse record construction: `Con { field = value, ... }` or `Con { field = value, .. }`
    fn parse_record_con(&mut self, con: Ident, start: Span) -> ParseResult<Expr> {
        self.expect(&TokenKind::LBrace)?;

        let mut fields = Vec::new();
        let mut has_wildcard = false;
        if !self.check(&TokenKind::RBrace) {
            // Check for leading `..`
            if self.eat(&TokenKind::DotDot) {
                has_wildcard = true;
            } else {
                fields.push(self.parse_field_bind()?);
                while self.eat(&TokenKind::Comma) {
                    if self.check(&TokenKind::RBrace) {
                        break;
                    }
                    // Check for trailing `..`
                    if self.eat(&TokenKind::DotDot) {
                        has_wildcard = true;
                        break;
                    }
                    fields.push(self.parse_field_bind()?);
                }
            }
        }

        let end = self.expect(&TokenKind::RBrace)?;
        let span = start.to(end.span);
        Ok(Expr::RecordCon(con, fields, has_wildcard, span))
    }

    /// Parse qualified record construction: `M.Con { field = value, ... }`
    fn parse_qual_record_con(
        &mut self,
        module_name: ModuleName,
        con: Ident,
        start: Span,
    ) -> ParseResult<Expr> {
        self.expect(&TokenKind::LBrace)?;

        let mut fields = Vec::new();
        let mut has_wildcard = false;
        if !self.check(&TokenKind::RBrace) {
            if self.eat(&TokenKind::DotDot) {
                has_wildcard = true;
            } else {
                fields.push(self.parse_field_bind()?);
                while self.eat(&TokenKind::Comma) {
                    if self.check(&TokenKind::RBrace) {
                        break;
                    }
                    if self.eat(&TokenKind::DotDot) {
                        has_wildcard = true;
                        break;
                    }
                    fields.push(self.parse_field_bind()?);
                }
            }
        }

        let end = self.expect(&TokenKind::RBrace)?;
        let span = start.to(end.span);
        Ok(Expr::QualRecordCon(
            module_name,
            con,
            fields,
            has_wildcard,
            span,
        ))
    }

    /// Parse record update: `expr { field = value, ... }`
    fn parse_record_update(&mut self, expr: Expr, start: Span) -> ParseResult<Expr> {
        self.expect(&TokenKind::LBrace)?;

        let mut fields = Vec::new();
        if !self.check(&TokenKind::RBrace) {
            fields.push(self.parse_field_bind()?);
            while self.eat(&TokenKind::Comma) {
                if self.check(&TokenKind::RBrace) {
                    break;
                }
                fields.push(self.parse_field_bind()?);
            }
        }

        let end = self.expect(&TokenKind::RBrace)?;
        let span = start.to(end.span);
        Ok(Expr::RecordUpd(Box::new(expr), fields, span))
    }

    /// Parse a field binding: `field = expr`, `Mod.field = expr`, or `field` (punning)
    fn parse_field_bind(&mut self) -> ParseResult<FieldBind> {
        let tok = self.current().ok_or(ParseError::UnexpectedEof {
            expected: "field name".to_string(),
        })?;

        let (qualifier, name, span) = match &tok.node.kind {
            TokenKind::Ident(sym) => (None, Ident::new(*sym), tok.span),
            TokenKind::QualIdent(qual, sym) => {
                let module_name = ModuleName {
                    parts: vec![*qual],
                    span: tok.span,
                };
                (Some(module_name), Ident::new(*sym), tok.span)
            }
            _ => {
                return Err(ParseError::Unexpected {
                    found: tok.node.kind.description().to_string(),
                    expected: "field name".to_string(),
                    span: tok.span,
                });
            }
        };
        self.advance();

        let value = if self.eat(&TokenKind::Eq) {
            Some(self.parse_expr()?)
        } else {
            None // Punning: `Foo { bar }` means `Foo { bar = bar }`
        };

        let end_span = value.as_ref().map(|e| e.span()).unwrap_or(span);
        let full_span = span.to(end_span);
        Ok(FieldBind {
            qualifier,
            name,
            value,
            span: full_span,
        })
    }

    /// Parse an operator section: `(+ 1)` or `(1 +)`
    fn parse_operator_section(&mut self, start: Span) -> ParseResult<Expr> {
        // Already consumed LParen, now at operator
        let tok = self.current().ok_or(ParseError::UnexpectedEof {
            expected: "operator".to_string(),
        })?;

        let op = match &tok.node.kind {
            TokenKind::Operator(sym) => Ident::new(*sym),
            TokenKind::Star => Ident::from_str("*"),
            TokenKind::Minus => Ident::from_str("-"),
            TokenKind::Percent => Ident::from_str("%"),
            TokenKind::Dot => Ident::from_str("."),
            TokenKind::ConOperator(sym) => Ident::new(*sym),
            TokenKind::QualOperator(qual, sym) => {
                let full_name = format!("{}.{}", qual.as_str(), sym.as_str());
                Ident::from_str(&full_name)
            }
            TokenKind::QualConOperator(qual, sym) => {
                let full_name = format!("{}.{}", qual.as_str(), sym.as_str());
                Ident::from_str(&full_name)
            }
            _ => {
                return Err(ParseError::Unexpected {
                    found: tok.node.kind.description().to_string(),
                    expected: "operator".to_string(),
                    span: tok.span,
                });
            }
        };
        self.advance();

        if self.eat(&TokenKind::RParen) {
            // Just `(+)` - operator as a function
            let span = start.to(self.tokens[self.pos - 1].span);
            return Ok(Expr::Var(op, span));
        }

        // Right section: `(+ 1)`
        let arg = self.parse_expr()?;
        let end = self.expect(&TokenKind::RParen)?;
        let span = start.to(end.span);

        // Desugar (+ x) to (\y -> y + x)
        let y = Ident::from_str("$section_arg");
        let y_pat = Pat::Var(y, Span::DUMMY);
        let y_expr = Expr::Var(y, Span::DUMMY);
        let body = Expr::Infix(Box::new(y_expr), op, Box::new(arg), span);
        Ok(Expr::Lam(vec![y_pat], Box::new(body), span))
    }

    /// Parse type annotation: `expr :: Type`
    fn parse_type_annotation(&mut self, expr: Expr) -> ParseResult<Expr> {
        let start = expr.span();
        self.expect(&TokenKind::DoubleColon)?;
        let ty = self.parse_type()?;
        let span = start.to(ty.span());
        Ok(Expr::Ann(Box::new(expr), ty, span))
    }
}

/// Operator associativity.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Assoc {
    Left,
    Right,
    None,
}
