//! Function-declaration, block, statement, parameter, and lambda parsing.
//! Split out of `parser.rs` (ADR 0060) as a further `impl Parser` block; the
//! scanning core and the other concerns stay in the parent module, reached
//! as ancestor privates via `self`.

use super::*;

impl<'a> Parser<'a> {
    // -- function declarations --

    pub(crate) fn parse_fn_decl(&mut self) -> Result<FnDecl, CompileError> {
        let kw = self.expect(TokenKind::Fn, "to start a function declaration")?;
        let first = self.expect_ident("after `fn`")?;
        // A method declaration uses `TypeName.methodName`; a free function
        // is just an identifier. Disambiguate on the next token.
        let name = if self.eat(TokenKind::Dot).is_some() {
            let method = self.expect_ident("after `.` in a method declaration")?;
            FnName::Method {
                type_name: first,
                method_name: method,
            }
        } else {
            FnName::Free(first)
        };
        // v0.20a: optional `[A, B]` type parameters (free functions only —
        // generic methods are checked semantically; bounds are rejected here
        // with `bynk.generics.no_bounds`).
        let mut type_params = Vec::new();
        if self.peek_kind() == Some(TokenKind::LBracket) {
            self.bump();
            loop {
                let p = self.expect_ident("as a type parameter name")?;
                if self.peek_kind() == Some(TokenKind::Colon) {
                    let colon = self.bump().unwrap();
                    return Err(CompileError::new(
                        "bynk.generics.no_bounds",
                        colon.span,
                        format!(
                            "type parameter `{}` carries a bound — bounded generics are not in v0.20a",
                            p.name
                        ),
                    )
                    .with_note("type parameters are unconstrained; remove the `: …` bound"));
                }
                type_params.push(TypeParam {
                    span: p.span,
                    name: p,
                });
                if self.eat(TokenKind::Comma).is_none() {
                    break;
                }
            }
            self.expect(TokenKind::RBracket, "to close the type-parameter list")?;
        }
        self.expect(TokenKind::LParen, "after the function name")?;
        // For methods, the first parameter may be the special `self` keyword.
        let mut params = Vec::new();
        let mut has_self = false;
        if self.peek_kind() == Some(TokenKind::Self_) {
            let self_tok = self.bump().unwrap();
            if !matches!(name, FnName::Method { .. }) {
                return Err(CompileError::new(
                    "bynk.parse.self_outside_method",
                    self_tok.span,
                    "`self` can only appear as the first parameter of a method declaration",
                )
                .with_note(
                    "use `fn TypeName.method(self, ...)` to declare a method, \
                     or remove `self` for a free function",
                ));
            }
            has_self = true;
            // Allow a trailing comma after `self` for further params.
            if self.peek_kind() == Some(TokenKind::Comma) {
                self.bump();
                if self.peek_kind() != Some(TokenKind::RParen) {
                    params.push(self.parse_param()?);
                    while self.eat(TokenKind::Comma).is_some() {
                        params.push(self.parse_param()?);
                    }
                }
            }
        } else if self.peek_kind() != Some(TokenKind::RParen) {
            params.push(self.parse_param()?);
            while self.eat(TokenKind::Comma).is_some() {
                params.push(self.parse_param()?);
            }
        }
        self.expect(TokenKind::RParen, "to close the parameter list")?;
        self.expect(TokenKind::Arrow, "before the return type")?;
        let return_type = self.parse_type_ref("as the return type")?;
        let body = self.parse_block("to open the function body")?;
        let span = kw.span.merge(body.span);
        Ok(FnDecl {
            type_params,
            name,
            params,
            return_type,
            body,
            has_self,
            documentation: None,
            span,
            trivia: Trivia::default(),
        })
    }

    /// Parse a brace-delimited block: `{ statement* expr }` (v0.1 §3.1, v0.5).
    pub(crate) fn parse_block(&mut self, ctx: &str) -> Result<Block, CompileError> {
        let open = self.expect(TokenKind::LBrace, ctx)?;
        let mut statements = Vec::new();
        // Loop: parse statements until we hit something that's not a statement.
        // v0.1: `let`. v0.5: `commit` and `let ... <-` are also statements.
        // v0.7: `assert` is a statement form inside test bodies.
        let tail_leading: Vec<String>;
        loop {
            let leading = self.take_leading_trivia();
            match self.peek_kind() {
                Some(TokenKind::Let) | Some(TokenKind::Commit) | Some(TokenKind::Assert) => {
                    let mut stmt = self.parse_statement()?;
                    let trailing = self.take_trailing_trivia();
                    match &mut stmt {
                        Statement::Let(l) | Statement::EffectLet(l) => {
                            l.trivia.leading = leading;
                            l.trivia.trailing = trailing;
                        }
                        Statement::Commit(c) => {
                            c.trivia.leading = leading;
                            c.trivia.trailing = trailing;
                        }
                        Statement::Assert(a) => {
                            a.trivia.leading = leading;
                            a.trivia.trailing = trailing;
                        }
                    }
                    statements.push(stmt);
                }
                _ => {
                    tail_leading = leading;
                    break;
                }
            }
        }
        // v0.7: a block whose last statement is an `assert` may close without
        // an explicit tail expression. The implicit tail is `()` (unit).
        if self.peek_kind() == Some(TokenKind::RBrace)
            && matches!(statements.last(), Some(Statement::Assert(_)))
        {
            let close = self.expect(TokenKind::RBrace, "to close the block")?;
            let tail = Expr {
                kind: ExprKind::UnitLit,
                span: close.span,
            };
            return Ok(Block {
                statements,
                tail: Box::new(tail),
                span: open.span.merge(close.span),
                tail_leading_comments: tail_leading,
            });
        }
        let tail = self.parse_expr()?;
        let close = self.expect(TokenKind::RBrace, "to close the block")?;
        Ok(Block {
            statements,
            tail: Box::new(tail),
            span: open.span.merge(close.span),
            tail_leading_comments: tail_leading,
        })
    }

    fn parse_statement(&mut self) -> Result<Statement, CompileError> {
        if self.peek_kind() == Some(TokenKind::Commit) {
            let kw = self.expect(TokenKind::Commit, "to start a commit statement")?;
            let value = self.parse_expr()?;
            let span = kw.span.merge(value.span);
            return Ok(Statement::Commit(CommitStmt {
                value,
                span,
                trivia: Trivia::default(),
            }));
        }
        if self.peek_kind() == Some(TokenKind::Assert) {
            let kw = self.expect(TokenKind::Assert, "to start an assert statement")?;
            let value = self.parse_expr()?;
            let span = kw.span.merge(value.span);
            return Ok(Statement::Assert(AssertStmt {
                value,
                span,
                trivia: Trivia::default(),
            }));
        }
        let kw = self.expect(TokenKind::Let, "to start a let statement")?;
        // Allow `_` as a discard name in `let _ = ...` and `let _ <- ...`.
        let name = if self.peek_kind() == Some(TokenKind::Underscore) {
            let t = self.bump().unwrap();
            Ident {
                name: "_".to_string(),
                span: t.span,
            }
        } else {
            self.expect_ident("after `let`")?
        };
        let type_annot = if self.eat(TokenKind::Colon).is_some() {
            Some(self.parse_type_ref("as the let-binding's type annotation")?)
        } else {
            None
        };
        match self.peek_kind() {
            Some(TokenKind::Eq) => {
                self.bump();
                let value = self.parse_expr()?;
                let span = kw.span.merge(value.span);
                Ok(Statement::Let(LetStmt {
                    name,
                    type_annot,
                    value,
                    span,
                    trivia: Trivia::default(),
                }))
            }
            Some(TokenKind::LArrow) => {
                self.bump();
                let value = self.parse_expr()?;
                let span = kw.span.merge(value.span);
                Ok(Statement::EffectLet(LetStmt {
                    name,
                    type_annot,
                    value,
                    span,
                    trivia: Trivia::default(),
                }))
            }
            Some(_) => {
                let t = self.peek().unwrap();
                Err(CompileError::new(
                    "bynk.parse.expected_token",
                    t.span,
                    format!(
                        "expected `=` or `<-` after the let-binding's name, found {}",
                        t.kind.describe()
                    ),
                ))
            }
            None => Err(CompileError::new(
                "bynk.parse.unexpected_eof",
                self.eof_span(),
                "expected `=` or `<-` after the let-binding's name, found end of file",
            )),
        }
    }

    pub(crate) fn parse_param(&mut self) -> Result<Param, CompileError> {
        let name = self.expect_ident("as a parameter name")?;
        self.expect(TokenKind::Colon, "after the parameter name")?;
        let type_ref = self.parse_type_ref("as the parameter type")?;
        let span = name.span.merge(type_ref.span());
        Ok(Param {
            name,
            type_ref,
            span,
        })
    }

    /// v0.20a: at an `LParen` in primary-expression position, decide whether
    /// a lambda follows: scan to the matching `)` counting paren depth, then
    /// peek one token for `=>`. Terminates at EOF; cost is the distance to
    /// the matching paren (the same class as the record-construction
    /// lookahead).
    pub(crate) fn lambda_ahead(&self) -> bool {
        let mut n = 1;
        let mut depth = 1u32;
        loop {
            match self.tokens.get(self.pos + n).map(|t| t.kind) {
                Some(TokenKind::LParen) => depth += 1,
                Some(TokenKind::RParen) => {
                    depth -= 1;
                    if depth == 0 {
                        return self.tokens.get(self.pos + n + 1).map(|t| t.kind)
                            == Some(TokenKind::FatArrow);
                    }
                }
                None => return false,
                _ => {}
            }
            n += 1;
        }
    }

    /// v0.20a: parse `(params) => expr | { block }`. Param annotations are
    /// optional (`(o: Order) => …` / `(o) => …`); the unannotated form relies
    /// on an expected function type at the use site (checked semantically).
    pub(crate) fn parse_lambda(&mut self) -> Result<Expr, CompileError> {
        let open = self.bump().unwrap(); // `(`
        let mut params = Vec::new();
        if self.peek_kind() != Some(TokenKind::RParen) {
            loop {
                let name = self.expect_ident("as a lambda parameter name")?;
                let mut p_span = name.span;
                let type_ref = if self.eat(TokenKind::Colon).is_some() {
                    let t = self.parse_type_ref("as the lambda parameter type")?;
                    p_span = p_span.merge(t.span());
                    Some(t)
                } else {
                    None
                };
                params.push(LambdaParam {
                    name,
                    type_ref,
                    span: p_span,
                });
                if self.eat(TokenKind::Comma).is_none() {
                    break;
                }
            }
        }
        self.expect(TokenKind::RParen, "to close the lambda parameter list")?;
        self.expect(TokenKind::FatArrow, "after the lambda parameter list")?;
        let body = if self.peek_kind() == Some(TokenKind::LBrace) {
            let block = self.parse_block("as the lambda body")?;
            let span = block.span;
            Expr {
                kind: ExprKind::Block(block),
                span,
            }
        } else {
            self.parse_expr()?
        };
        let span = open.span.merge(body.span);
        Ok(Expr {
            kind: ExprKind::Lambda(LambdaExpr {
                params,
                body: Box::new(body),
                span,
            }),
            span,
        })
    }
}
