//! Recursive-descent parser for the security-relevant subset of Structured Text.
//!
//! Tolerant by design: it parses the POUs, variable sections, and statement
//! bodies it understands, and skips (with statement/POU-level recovery) anything
//! it does not, so a single odd construct never sinks the whole file.

use super::ast::*;
use super::lexer::{Keyword as K, Tok, Token};

pub struct Parser {
    toks: Vec<Token>,
    pos: usize,
}

impl Parser {
    pub fn new(toks: Vec<Token>) -> Self {
        Self { toks, pos: 0 }
    }

    // ── token helpers ──────────────────────────────────────────────
    fn peek(&self) -> &Tok {
        &self.toks[self.pos.min(self.toks.len() - 1)].kind
    }
    fn line(&self) -> u32 {
        self.toks[self.pos.min(self.toks.len() - 1)].line
    }
    fn at_end(&self) -> bool {
        matches!(self.peek(), Tok::Eof)
    }
    fn advance(&mut self) -> Tok {
        let t = self.toks[self.pos.min(self.toks.len() - 1)].kind.clone();
        if self.pos < self.toks.len() - 1 {
            self.pos += 1;
        }
        t
    }
    fn eat(&mut self, t: &Tok) -> bool {
        if self.peek() == t {
            self.advance();
            true
        } else {
            false
        }
    }
    fn eat_kw(&mut self, k: K) -> bool {
        if matches!(self.peek(), Tok::Kw(x) if *x == k) {
            self.advance();
            true
        } else {
            false
        }
    }
    fn at_kw(&self, k: K) -> bool {
        matches!(self.peek(), Tok::Kw(x) if *x == k)
    }
    fn ident(&mut self) -> Option<String> {
        if let Tok::Ident(s) = self.peek() {
            let s = s.clone();
            self.advance();
            Some(s)
        } else {
            None
        }
    }

    // ── top level ──────────────────────────────────────────────────
    /// Parse every POU in the token stream.
    pub fn parse_units(&mut self) -> Vec<Pou> {
        let mut pous = Vec::new();
        while !self.at_end() {
            match self.peek() {
                Tok::Kw(K::Program) => {
                    self.advance();
                    if let Some(p) = self.parse_pou(PouKind::Program, K::EndProgram) {
                        pous.push(p);
                    }
                }
                Tok::Kw(K::Function) => {
                    self.advance();
                    if let Some(p) = self.parse_pou(PouKind::Function, K::EndFunction) {
                        pous.push(p);
                    }
                }
                Tok::Kw(K::FunctionBlock) => {
                    self.advance();
                    if let Some(p) = self.parse_pou(PouKind::FunctionBlock, K::EndFunctionBlock) {
                        pous.push(p);
                    }
                }
                // Skip TYPE...END_TYPE and anything else at top level.
                _ => {
                    self.advance();
                }
            }
        }
        pous
    }

    fn parse_pou(&mut self, kind: PouKind, end: K) -> Option<Pou> {
        let line = self.line();
        let name = self.ident().unwrap_or_else(|| "<anonymous>".to_string());
        // Optional `: return_type` for functions.
        if self.eat(&Tok::Colon) {
            let _ = self.advance(); // return type token
        }

        let mut vars = Vec::new();
        // Variable sections precede the body.
        while let Some(section) = self.var_section_kw() {
            self.advance();
            let _ = self.eat_kw(K::Constant); // CONSTANT is informational for our rules
            self.parse_var_decls(section, &mut vars);
        }

        // Body statements until END_<kind>.
        let mut body = Vec::new();
        while !self.at_end() && !self.at_kw(end) {
            if let Some(s) = self.parse_stmt() {
                body.push(s);
            }
        }
        self.eat_kw(end);

        Some(Pou {
            name,
            kind,
            vars,
            body,
            line,
        })
    }

    fn var_section_kw(&self) -> Option<VarSection> {
        match self.peek() {
            Tok::Kw(K::Var) => Some(VarSection::Var),
            Tok::Kw(K::VarInput) => Some(VarSection::Input),
            Tok::Kw(K::VarOutput) => Some(VarSection::Output),
            Tok::Kw(K::VarInOut) => Some(VarSection::InOut),
            Tok::Kw(K::VarGlobal) => Some(VarSection::Global),
            Tok::Kw(K::VarTemp) => Some(VarSection::Temp),
            Tok::Kw(K::VarExternal) => Some(VarSection::External),
            _ => None,
        }
    }

    fn parse_var_decls(&mut self, section: VarSection, out: &mut Vec<VarDecl>) {
        while !self.at_end() && !self.at_kw(K::EndVar) {
            let line = self.line();
            // names: a, b, c
            let mut names = Vec::new();
            match self.ident() {
                Some(n) => names.push(n),
                None => {
                    // Not a declaration we understand — skip to next ; or END_VAR.
                    self.sync_decl();
                    continue;
                }
            }
            while self.eat(&Tok::Comma) {
                if let Some(n) = self.ident() {
                    names.push(n);
                }
            }
            if !self.eat(&Tok::Colon) {
                self.sync_decl();
                continue;
            }
            let (type_name, array_bounds) = self.parse_type();
            let init = if self.eat(&Tok::Assign) {
                Some(self.parse_expr())
            } else {
                None
            };
            self.eat(&Tok::Semi);
            for n in names {
                out.push(VarDecl {
                    name: n,
                    section,
                    type_name: type_name.clone(),
                    array_bounds,
                    init: init.clone(),
                    line,
                });
            }
        }
        self.eat_kw(K::EndVar);
    }

    /// Parse a (possibly ARRAY) type, returning its rendered name and literal
    /// bounds when present.
    fn parse_type(&mut self) -> (String, Option<(i64, i64)>) {
        if self.eat_kw(K::Array) {
            let mut bounds = None;
            if self.eat(&Tok::LBrack) {
                let lo = self.int_lit();
                self.eat(&Tok::DotDot);
                let hi = self.int_lit();
                if let (Some(lo), Some(hi)) = (lo, hi) {
                    bounds = Some((lo, hi));
                }
                // Skip any further dimensions / tokens to the closing bracket.
                while !self.at_end() && !self.eat(&Tok::RBrack) {
                    self.advance();
                }
            }
            self.eat_kw(K::Of);
            let elem = self.type_ident();
            (format!("ARRAY OF {elem}"), bounds)
        } else {
            (self.type_ident(), None)
        }
    }

    fn type_ident(&mut self) -> String {
        // Types can be qualified idents; keep it simple: one token, plus any
        // string-length suffix like STRING[80].
        let base = match self.advance() {
            Tok::Ident(s) => s,
            Tok::Kw(_) => "TYPE".to_string(),
            other => format!("{other:?}"),
        };
        if self.eat(&Tok::LBrack) {
            while !self.at_end() && !self.eat(&Tok::RBrack) {
                self.advance();
            }
        }
        base
    }

    fn int_lit(&mut self) -> Option<i64> {
        match self.peek() {
            Tok::Int(n) => {
                let n = *n;
                self.advance();
                Some(n)
            }
            Tok::Minus => {
                self.advance();
                if let Tok::Int(n) = self.peek() {
                    let n = -*n;
                    self.advance();
                    Some(n)
                } else {
                    None
                }
            }
            _ => None,
        }
    }

    fn sync_decl(&mut self) {
        while !self.at_end() && !self.eat(&Tok::Semi) && !self.at_kw(K::EndVar) {
            self.advance();
        }
    }
    fn sync_stmt(&mut self) {
        while !self.at_end() && !self.eat(&Tok::Semi) {
            // Stop at block terminators so recovery doesn't swallow structure.
            if matches!(
                self.peek(),
                Tok::Kw(
                    K::EndIf
                        | K::EndFor
                        | K::EndWhile
                        | K::EndCase
                        | K::EndRepeat
                        | K::EndProgram
                        | K::EndFunction
                        | K::EndFunctionBlock
                        | K::Else
                        | K::Elsif
                )
            ) {
                return;
            }
            self.advance();
        }
    }

    // ── statements ─────────────────────────────────────────────────
    fn parse_stmt(&mut self) -> Option<Stmt> {
        let line = self.line();
        match self.peek().clone() {
            Tok::Semi => {
                self.advance();
                None
            }
            Tok::Kw(K::If) => self.parse_if(),
            Tok::Kw(K::Case) => self.parse_case(),
            Tok::Kw(K::For) => self.parse_for(),
            Tok::Kw(K::While) => self.parse_while(),
            Tok::Kw(K::Repeat) => self.parse_repeat(),
            Tok::Kw(K::Return) => {
                self.advance();
                self.eat(&Tok::Semi);
                Some(Stmt::Return { line })
            }
            Tok::Kw(K::Exit) => {
                self.advance();
                self.eat(&Tok::Semi);
                Some(Stmt::Exit { line })
            }
            Tok::Kw(K::Jmp) => {
                self.advance();
                let label = self.ident().unwrap_or_default();
                self.eat(&Tok::Semi);
                Some(Stmt::Jump { label, line })
            }
            Tok::Ident(name) => {
                // Could be `label:`, `call(...)`, or an assignment.
                // Lookahead: ident ':' (not ':=') → label.
                if matches!(
                    self.toks.get(self.pos + 1).map(|t| &t.kind),
                    Some(Tok::Colon)
                ) && !matches!(self.toks.get(self.pos + 2).map(|t| &t.kind), Some(Tok::Eq))
                {
                    self.advance(); // ident
                    self.advance(); // ':'
                    return Some(Stmt::Label { name, line });
                }
                let lhs = self.parse_expr();
                if self.eat(&Tok::Assign) {
                    let value = self.parse_expr();
                    self.eat(&Tok::Semi);
                    Some(Stmt::Assign {
                        target: lhs,
                        value,
                        line,
                    })
                } else if let Expr::Call { callee, args, .. } = lhs {
                    self.eat(&Tok::Semi);
                    Some(Stmt::Call { callee, args, line })
                } else {
                    // Bare expression / FB invocation without args recognized —
                    // skip to the terminator.
                    self.sync_stmt();
                    None
                }
            }
            _ => {
                self.sync_stmt();
                None
            }
        }
    }

    fn parse_block_until(&mut self, terms: &[K]) -> Vec<Stmt> {
        let mut body = Vec::new();
        while !self.at_end() && !terms.iter().any(|k| self.at_kw(*k)) {
            if let Some(s) = self.parse_stmt() {
                body.push(s);
            }
        }
        body
    }

    fn parse_if(&mut self) -> Option<Stmt> {
        let line = self.line();
        self.eat_kw(K::If);
        let mut branches = Vec::new();
        let cond = self.parse_expr();
        self.eat_kw(K::Then);
        let body = self.parse_block_until(&[K::Elsif, K::Else, K::EndIf]);
        branches.push((cond, body));
        while self.eat_kw(K::Elsif) {
            let c = self.parse_expr();
            self.eat_kw(K::Then);
            let b = self.parse_block_until(&[K::Elsif, K::Else, K::EndIf]);
            branches.push((c, b));
        }
        let else_body = if self.eat_kw(K::Else) {
            Some(self.parse_block_until(&[K::EndIf]))
        } else {
            None
        };
        self.eat_kw(K::EndIf);
        self.eat(&Tok::Semi);
        Some(Stmt::If {
            branches,
            else_body,
            line,
        })
    }

    fn parse_case(&mut self) -> Option<Stmt> {
        let line = self.line();
        self.eat_kw(K::Case);
        let selector = self.parse_expr();
        self.eat_kw(K::Of);
        let mut arms = Vec::new();
        let mut else_body = None;
        while !self.at_end() && !self.at_kw(K::EndCase) {
            if self.eat_kw(K::Else) {
                else_body = Some(self.parse_block_until(&[K::EndCase]));
                break;
            }
            // labels: expr {, expr} :
            let mut labels = vec![self.parse_expr()];
            while self.eat(&Tok::Comma) {
                labels.push(self.parse_expr());
            }
            self.eat(&Tok::Colon);
            let body = self.parse_block_until(&[K::EndCase, K::Else]);
            arms.push((labels, body));
        }
        self.eat_kw(K::EndCase);
        self.eat(&Tok::Semi);
        Some(Stmt::Case {
            selector,
            arms,
            else_body,
            line,
        })
    }

    fn parse_for(&mut self) -> Option<Stmt> {
        let line = self.line();
        self.eat_kw(K::For);
        let var = self.ident().unwrap_or_default();
        self.eat(&Tok::Assign);
        let from = self.parse_expr();
        self.eat_kw(K::To);
        let to = self.parse_expr();
        let by = if self.eat_kw(K::By) {
            Some(self.parse_expr())
        } else {
            None
        };
        self.eat_kw(K::Do);
        let body = self.parse_block_until(&[K::EndFor]);
        self.eat_kw(K::EndFor);
        self.eat(&Tok::Semi);
        Some(Stmt::For {
            var,
            from,
            to,
            by,
            body,
            line,
        })
    }

    fn parse_while(&mut self) -> Option<Stmt> {
        let line = self.line();
        self.eat_kw(K::While);
        let cond = self.parse_expr();
        self.eat_kw(K::Do);
        let body = self.parse_block_until(&[K::EndWhile]);
        self.eat_kw(K::EndWhile);
        self.eat(&Tok::Semi);
        Some(Stmt::While { cond, body, line })
    }

    fn parse_repeat(&mut self) -> Option<Stmt> {
        let line = self.line();
        self.eat_kw(K::Repeat);
        let body = self.parse_block_until(&[K::Until, K::EndRepeat]);
        self.eat_kw(K::Until);
        let until = self.parse_expr();
        self.eat_kw(K::EndRepeat);
        self.eat(&Tok::Semi);
        Some(Stmt::Repeat { body, until, line })
    }

    // ── expressions (precedence climbing) ──────────────────────────
    pub fn parse_expr(&mut self) -> Expr {
        self.parse_or()
    }

    fn parse_or(&mut self) -> Expr {
        let mut lhs = self.parse_and();
        loop {
            let op = match self.peek() {
                Tok::Kw(K::Or) => BinOp::Or,
                Tok::Kw(K::Xor) => BinOp::Xor,
                _ => break,
            };
            let line = self.line();
            self.advance();
            let rhs = self.parse_and();
            lhs = Expr::Binary {
                op,
                lhs: Box::new(lhs),
                rhs: Box::new(rhs),
                line,
            };
        }
        lhs
    }

    fn parse_and(&mut self) -> Expr {
        let mut lhs = self.parse_cmp();
        while matches!(self.peek(), Tok::Kw(K::And) | Tok::Amp) {
            let op = BinOp::And;
            let line = self.line();
            self.advance();
            let rhs = self.parse_cmp();
            lhs = Expr::Binary {
                op,
                lhs: Box::new(lhs),
                rhs: Box::new(rhs),
                line,
            };
        }
        lhs
    }

    fn parse_cmp(&mut self) -> Expr {
        let mut lhs = self.parse_add();
        loop {
            let op = match self.peek() {
                Tok::Eq => BinOp::Eq,
                Tok::Ne => BinOp::Ne,
                Tok::Lt => BinOp::Lt,
                Tok::Le => BinOp::Le,
                Tok::Gt => BinOp::Gt,
                Tok::Ge => BinOp::Ge,
                _ => break,
            };
            let line = self.line();
            self.advance();
            let rhs = self.parse_add();
            lhs = Expr::Binary {
                op,
                lhs: Box::new(lhs),
                rhs: Box::new(rhs),
                line,
            };
        }
        lhs
    }

    fn parse_add(&mut self) -> Expr {
        let mut lhs = self.parse_mul();
        loop {
            let op = match self.peek() {
                Tok::Plus => BinOp::Add,
                Tok::Minus => BinOp::Sub,
                _ => break,
            };
            let line = self.line();
            self.advance();
            let rhs = self.parse_mul();
            lhs = Expr::Binary {
                op,
                lhs: Box::new(lhs),
                rhs: Box::new(rhs),
                line,
            };
        }
        lhs
    }

    fn parse_mul(&mut self) -> Expr {
        let mut lhs = self.parse_unary();
        loop {
            let op = match self.peek() {
                Tok::Star => BinOp::Mul,
                Tok::Slash => BinOp::Div,
                Tok::Kw(K::Mod) => BinOp::Mod,
                Tok::Power => BinOp::Pow,
                _ => break,
            };
            let line = self.line();
            self.advance();
            let rhs = self.parse_unary();
            lhs = Expr::Binary {
                op,
                lhs: Box::new(lhs),
                rhs: Box::new(rhs),
                line,
            };
        }
        lhs
    }

    fn parse_unary(&mut self) -> Expr {
        let line = self.line();
        match self.peek() {
            Tok::Kw(K::Not) => {
                self.advance();
                Expr::Unary {
                    op: UnOp::Not,
                    expr: Box::new(self.parse_unary()),
                    line,
                }
            }
            Tok::Minus => {
                self.advance();
                Expr::Unary {
                    op: UnOp::Neg,
                    expr: Box::new(self.parse_unary()),
                    line,
                }
            }
            _ => self.parse_postfix(),
        }
    }

    fn parse_postfix(&mut self) -> Expr {
        let mut e = self.parse_primary();
        loop {
            let line = self.line();
            match self.peek() {
                Tok::LBrack => {
                    self.advance();
                    let index = self.parse_expr();
                    self.eat(&Tok::RBrack);
                    e = Expr::Index {
                        base: Box::new(e),
                        index: Box::new(index),
                        line,
                    };
                }
                Tok::Dot => {
                    self.advance();
                    let field = self.ident().unwrap_or_default();
                    e = Expr::Member {
                        base: Box::new(e),
                        field,
                        line,
                    };
                }
                _ => break,
            }
        }
        e
    }

    fn parse_primary(&mut self) -> Expr {
        let line = self.line();
        match self.advance() {
            Tok::Int(n) => Expr::Int(n, line),
            Tok::Real(r) => Expr::Real(r, line),
            Tok::Bool(b) => Expr::Bool(b, line),
            Tok::Str(s) => Expr::Str(s, line),
            Tok::Time(t) => Expr::Time(t, line),
            Tok::LParen => {
                let e = self.parse_expr();
                self.eat(&Tok::RParen);
                e
            }
            Tok::Ident(name) => {
                if self.eat(&Tok::LParen) {
                    let args = self.parse_call_args();
                    Expr::Call {
                        callee: name,
                        args,
                        line,
                    }
                } else {
                    Expr::Ident(name, line)
                }
            }
            // Unrecognized start of expression — yield a placeholder identifier.
            _ => Expr::Ident(String::new(), line),
        }
    }

    fn parse_call_args(&mut self) -> Vec<CallArg> {
        let mut args = Vec::new();
        if self.eat(&Tok::RParen) {
            return args;
        }
        loop {
            // Named arg: ident := expr  (peek two tokens).
            if let Tok::Ident(name) = self.peek().clone() {
                if matches!(
                    self.toks.get(self.pos + 1).map(|t| &t.kind),
                    Some(Tok::Assign)
                ) {
                    self.advance(); // ident
                    self.advance(); // :=
                    let value = self.parse_expr();
                    args.push(CallArg {
                        name: Some(name),
                        value,
                    });
                    if self.eat(&Tok::Comma) {
                        continue;
                    }
                    break;
                }
            }
            let value = self.parse_expr();
            args.push(CallArg { name: None, value });
            if self.eat(&Tok::Comma) {
                continue;
            }
            break;
        }
        self.eat(&Tok::RParen);
        args
    }
}

/// Parse ST source into its POUs.
pub fn parse(src: &str) -> Vec<Pou> {
    let toks = super::lexer::lex(src);
    Parser::new(toks).parse_units()
}

#[cfg(test)]
mod tests {
    use super::*;

    const SAMPLE: &str = r#"
PROGRAM Main
VAR
    idx : INT;
    pw : STRING := 'admin123';
    buf : ARRAY[0..9] OF INT;
    ok : BOOL := FALSE;
END_VAR
    // a comment
    IF idx > 0 THEN
        buf[idx] := idx * 2;
    ELSE
        JMP done;
    END_IF;
    Comm(IP := '10.0.0.1', PORT := 502);
done:
    ok := TRUE;
END_PROGRAM
"#;

    #[test]
    fn parses_program_vars_and_body() {
        let pous = parse(SAMPLE);
        assert_eq!(pous.len(), 1, "one POU");
        let p = &pous[0];
        assert_eq!(p.name, "Main");
        assert_eq!(p.kind, PouKind::Program);
        // vars: idx, pw, buf, ok
        assert_eq!(p.vars.len(), 4);
        let pw = p.vars.iter().find(|v| v.name == "pw").expect("pw");
        assert!(matches!(&pw.init, Some(Expr::Str(s, _)) if s == "admin123"));
        let buf = p.vars.iter().find(|v| v.name == "buf").expect("buf");
        assert_eq!(buf.array_bounds, Some((0, 9)));
        // body has an IF, a Call, a Label, and an Assign
        assert!(p.body.iter().any(|s| matches!(s, Stmt::If { .. })));
        assert!(p
            .body
            .iter()
            .any(|s| matches!(s, Stmt::Call { callee, .. } if callee == "Comm")));
        assert!(p
            .body
            .iter()
            .any(|s| matches!(s, Stmt::Label { name, .. } if name == "done")));
    }

    #[test]
    fn jmp_inside_if_is_captured() {
        let pous = parse(SAMPLE);
        let p = &pous[0];
        // find the IF, check its else branch has a JMP
        let has_jmp = p.body.iter().any(|s| match s {
            Stmt::If { else_body, .. } => else_body
                .as_ref()
                .map(|b| b.iter().any(|s| matches!(s, Stmt::Jump { .. })))
                .unwrap_or(false),
            _ => false,
        });
        assert!(has_jmp, "JMP should be parsed inside the ELSE branch");
    }
}
