//! Parser: baut aus dem Tokenstrom den AST.
//!
//! Zeilenorientiert und fehlertolerant: Bei einem Fehler wird die Diagnose
//! gesammelt und bis zum nächsten Anweisungsende (`:` oder Zeilenende)
//! synchronisiert — wie die zeilenweise Prüfung des IDE-Vorbilds.

use crate::ast::*;
use crate::lexer::{Kw, NumValue, Token, TokenKind};
use crate::{Diagnostic, SourcePos};

pub struct ParseOutput {
    /// Ausschließlich fehlende Blockabschlüsse am Dateiende.
    pub incomplete: bool,
    pub module: Module,
    pub diagnostics: Vec<Diagnostic>,
}

pub fn parse(module_name: &str, tokens: &[Token]) -> ParseOutput {
    let mut p = P {
        toks: tokens,
        i: 0,
        diags: Vec::new(),
        at_line_start: true,
        incomplete_errors: 0,
    };
    let mut body = Vec::new();
    let mut procs = Vec::new();

    loop {
        p.skip_seps();
        match p.k() {
            TokenKind::Eof => break,
            TokenKind::Kw(Kw::Sub) | TokenKind::Kw(Kw::Function) => {
                if let Some(proc) = p.parse_proc(false) {
                    procs.push(proc);
                }
            }
            TokenKind::Kw(Kw::Static)
                if matches!(
                    p.k_at(1),
                    TokenKind::Kw(Kw::Sub) | TokenKind::Kw(Kw::Function)
                ) =>
            {
                p.advance();
                if let Some(proc) = p.parse_proc(true) {
                    procs.push(proc);
                }
            }
            _ => {
                let before = p.i;
                if let Some(s) = p.parse_statement() {
                    body.push(s);
                }
                if p.i == before {
                    p.advance(); // Notbremse gegen Endlosschleifen
                }
            }
        }
    }

    ParseOutput {
        incomplete: !p.diags.is_empty() && p.incomplete_errors == p.diags.len(),
        module: Module {
            name: module_name.to_string(),
            body,
            procs,
        },
        diagnostics: p.diags,
    }
}

struct P<'a> {
    toks: &'a [Token],
    i: usize,
    diags: Vec<Diagnostic>,
    at_line_start: bool,
    incomplete_errors: usize,
}

impl<'a> P<'a> {
    fn k(&self) -> TokenKind {
        self.toks[self.i].kind.clone()
    }
    fn k_at(&self, off: usize) -> TokenKind {
        let j = (self.i + off).min(self.toks.len() - 1);
        self.toks[j].kind.clone()
    }
    fn pos(&self) -> SourcePos {
        self.toks[self.i].pos
    }
    fn advance(&mut self) {
        if self.i < self.toks.len() - 1 {
            self.at_line_start = matches!(self.toks[self.i].kind, TokenKind::Eol);
            self.i += 1;
        }
    }
    fn eat(&mut self, kind: &TokenKind) -> bool {
        if &self.k() == kind {
            self.advance();
            true
        } else {
            false
        }
    }
    fn eat_kw(&mut self, kw: Kw) -> bool {
        self.eat(&TokenKind::Kw(kw))
    }
    fn is_kw(&self, kw: Kw) -> bool {
        self.k() == TokenKind::Kw(kw)
    }
    fn err(&mut self, msg: impl Into<String>) {
        let pos = self.pos();
        let msg = msg.into();
        if matches!(self.k(), TokenKind::Eof)
            && matches!(
                msg.as_str(),
                "Expected: END SUB"
                    | "Expected: END FUNCTION"
                    | "Expected: END IF"
                    | "Expected: END SELECT"
                    | "Expected: END TYPE"
                    | "Expected: END DEF"
                    | "Expected: NEXT"
                    | "Expected: LOOP"
                    | "Expected: WEND"
            )
        {
            self.incomplete_errors += 1;
        }
        self.diags.push(Diagnostic {
            file: None,
            pos,
            message: msg,
        });
    }
    fn expect_kw(&mut self, kw: Kw, what: &str) -> bool {
        if self.eat_kw(kw) {
            true
        } else {
            self.err(format!("Expected: {what}"));
            false
        }
    }
    /// Bis zum Anweisungsende überspringen (Fehler-Synchronisation).
    fn sync(&mut self) {
        while !self.at_stmt_end() {
            self.advance();
        }
    }
    fn at_stmt_end(&self) -> bool {
        matches!(
            self.k(),
            TokenKind::Colon
                | TokenKind::Eol
                | TokenKind::Eof
                | TokenKind::Kw(Kw::Else)
                | TokenKind::Kw(Kw::ElseIf)
        )
    }
    /// Anweisungstrenner (`:`/Zeilenende) überspringen.
    fn skip_seps(&mut self) {
        while matches!(self.k(), TokenKind::Colon | TokenKind::Eol) {
            self.advance();
        }
    }

    // ---- Blöcke -----------------------------------------------------------

    fn at_end_pair(&self, kw: Kw) -> bool {
        self.is_kw(Kw::End) && self.k_at(1) == TokenKind::Kw(kw)
    }

    fn parse_stmt_list(&mut self, stop: impl Fn(&P) -> bool) -> Vec<Stmt> {
        let mut out = Vec::new();
        loop {
            self.skip_seps();
            if matches!(self.k(), TokenKind::Eof) || stop(self) {
                break;
            }
            let before = self.i;
            if let Some(s) = self.parse_statement() {
                out.push(s);
            }
            if self.i == before {
                self.advance();
            }
        }
        out
    }

    /// Anweisungen auf derselben Zeile (einzeiliges IF): bis Zeilenende
    /// bzw. ELSE.
    fn parse_inline_stmts(&mut self) -> Vec<Stmt> {
        let mut out = Vec::new();
        loop {
            match self.k() {
                TokenKind::Eol | TokenKind::Eof | TokenKind::Kw(Kw::Else) => break,
                TokenKind::Colon => {
                    self.advance();
                    continue;
                }
                _ => {}
            }
            let before = self.i;
            if let Some(s) = self.parse_statement() {
                out.push(s);
            }
            if self.i == before {
                self.advance();
            }
        }
        out
    }

    // ---- Anweisungen ------------------------------------------------------

    fn parse_statement(&mut self) -> Option<Stmt> {
        let pos = self.pos();
        // Zeilennummern und Labels nur am Zeilenanfang
        if self.at_line_start {
            if let TokenKind::Num(NumValue::Int(n)) = self.k() {
                if n >= 0 {
                    self.advance();
                    return Some(Stmt::LineNumber(n as u32, pos));
                }
            }
            if let TokenKind::Num(NumValue::Long(n)) = self.k() {
                if n >= 0 {
                    self.advance();
                    return Some(Stmt::LineNumber(n as u32, pos));
                }
            }
            if let TokenKind::Ident { name, suffix: None } = self.k() {
                if self.k_at(1) == TokenKind::Colon {
                    self.advance();
                    self.advance();
                    return Some(Stmt::Label(name));
                }
            }
        }

        match self.k() {
            TokenKind::MetaInclude(path) => {
                self.advance();
                if path.is_empty() {
                    self.err("Expected: file name");
                    return None;
                }
                Some(Stmt::Include { path, pos })
            }
            TokenKind::MetaStatic => {
                self.advance();
                Some(Stmt::MetaArrays {
                    static_arrays: true,
                    pos,
                })
            }
            TokenKind::MetaDynamic => {
                self.advance();
                Some(Stmt::MetaArrays {
                    static_arrays: false,
                    pos,
                })
            }
            TokenKind::MetaForm => {
                self.advance();
                Some(Stmt::MetaForm { pos })
            }
            TokenKind::Kw(Kw::Print) => self.parse_print(pos, false),
            TokenKind::Kw(Kw::Input) => {
                self.advance();
                self.parse_input(false, pos)
            }
            TokenKind::Kw(Kw::Line) => {
                self.advance();
                if self.k() == TokenKind::Kw(Kw::Input) {
                    self.advance();
                    self.parse_input(true, pos)
                } else {
                    self.parse_graphics_line(pos)
                }
            }
            TokenKind::Kw(Kw::If) => self.parse_if(pos),
            TokenKind::Kw(Kw::Select) => self.parse_select(pos),
            TokenKind::Kw(Kw::For) => self.parse_for(pos),
            TokenKind::Kw(Kw::Do) => self.parse_do(pos),
            TokenKind::Kw(Kw::While) => self.parse_while(pos),
            TokenKind::Kw(Kw::Goto) => {
                self.advance();
                let target = self.parse_label_ref()?;
                Some(Stmt::Goto { target, pos })
            }
            TokenKind::Kw(Kw::Gosub) => {
                self.advance();
                let target = self.parse_label_ref()?;
                Some(Stmt::Gosub { target, pos })
            }
            TokenKind::Kw(Kw::Return) => {
                self.advance();
                let target = if self.at_stmt_end() {
                    None
                } else {
                    self.parse_label_ref()
                };
                Some(Stmt::Return { target, pos })
            }
            TokenKind::Kw(Kw::On) => self.parse_on(pos),
            TokenKind::Kw(Kw::Resume) => {
                self.advance();
                let kind = if self.eat_kw(Kw::Next) {
                    ResumeKind::Next
                } else if self.at_stmt_end() {
                    ResumeKind::Retry
                } else if self.k() == TokenKind::Num(NumValue::Int(0)) {
                    self.advance();
                    ResumeKind::Retry
                } else {
                    ResumeKind::Label(self.parse_label_ref()?)
                };
                Some(Stmt::Resume { kind, pos })
            }
            TokenKind::Kw(Kw::Error) => {
                self.advance();
                let code = self.parse_expr()?;
                Some(Stmt::ErrorStmt { code, pos })
            }
            TokenKind::Kw(Kw::End) => {
                if matches!(
                    self.k_at(1),
                    TokenKind::Kw(Kw::If)
                        | TokenKind::Kw(Kw::Select)
                        | TokenKind::Kw(Kw::Sub)
                        | TokenKind::Kw(Kw::Function)
                        | TokenKind::Kw(Kw::Type)
                        | TokenKind::Kw(Kw::Def)
                ) {
                    self.err("Syntax error (END without matching block)");
                    self.advance();
                    self.advance();
                    return None;
                }
                self.advance();
                Some(Stmt::End(pos))
            }
            TokenKind::Kw(Kw::Stop) => {
                self.advance();
                Some(Stmt::StopStmt(pos))
            }
            TokenKind::Kw(Kw::System) => {
                self.advance();
                Some(Stmt::System(pos))
            }
            TokenKind::Kw(Kw::Exit) => {
                self.advance();
                let kind = if self.eat_kw(Kw::For) {
                    ExitKind::For
                } else if self.eat_kw(Kw::Do) {
                    ExitKind::Do
                } else if self.eat_kw(Kw::Sub) {
                    ExitKind::Sub
                } else if self.eat_kw(Kw::Function) {
                    ExitKind::Function
                } else if self.eat_kw(Kw::Def) {
                    ExitKind::Def
                } else {
                    self.err("Expected: FOR, DO, SUB, FUNCTION or DEF");
                    self.sync();
                    return None;
                };
                Some(Stmt::Exit { kind, pos })
            }
            TokenKind::Kw(Kw::Dim) => {
                self.advance();
                self.parse_dim(false, pos)
            }
            TokenKind::Kw(Kw::ReDim) => {
                self.advance();
                self.parse_dim(true, pos)
            }
            TokenKind::Kw(Kw::Erase) => {
                self.advance();
                let mut names = Vec::new();
                while let Some(e) = self.parse_name_only() {
                    names.push(e);
                    if !self.eat(&TokenKind::Comma) {
                        break;
                    }
                }
                Some(Stmt::Erase { names, pos })
            }
            TokenKind::Kw(Kw::Const) => {
                self.advance();
                let mut items = Vec::new();
                loop {
                    let (name, suffix) = match self.k() {
                        TokenKind::Ident { name, suffix } => {
                            self.advance();
                            (name, suffix)
                        }
                        _ => {
                            self.err("Expected: identifier");
                            self.sync();
                            break;
                        }
                    };
                    if !self.eat(&TokenKind::Eq) {
                        self.err("Expected: =");
                        self.sync();
                        break;
                    }
                    let value = self.parse_expr()?;
                    items.push((name, suffix, value));
                    if !self.eat(&TokenKind::Comma) {
                        break;
                    }
                }
                Some(Stmt::ConstDecl { items, pos })
            }
            TokenKind::Kw(
                k @ (Kw::DefInt | Kw::DefLng | Kw::DefSng | Kw::DefDbl | Kw::DefStr | Kw::DefCur),
            ) => {
                self.advance();
                let ty = match k {
                    Kw::DefInt => TypeName::Integer,
                    Kw::DefLng => TypeName::Long,
                    Kw::DefSng => TypeName::Single,
                    Kw::DefDbl => TypeName::Double,
                    Kw::DefStr => TypeName::Str,
                    _ => TypeName::Currency,
                };
                let mut ranges = Vec::new();
                loop {
                    let a = match self.k() {
                        TokenKind::Ident { name, suffix: None } if name.len() == 1 => {
                            self.advance();
                            name.chars().next().unwrap()
                        }
                        _ => {
                            self.err("Expected: letter");
                            self.sync();
                            break;
                        }
                    };
                    let b = if self.eat(&TokenKind::Minus) {
                        match self.k() {
                            TokenKind::Ident { name, suffix: None } if name.len() == 1 => {
                                self.advance();
                                name.chars().next().unwrap()
                            }
                            _ => {
                                self.err("Expected: letter");
                                a
                            }
                        }
                    } else {
                        a
                    };
                    ranges.push((a, b));
                    if !self.eat(&TokenKind::Comma) {
                        break;
                    }
                }
                Some(Stmt::DefType { ty, ranges, pos })
            }
            TokenKind::Kw(Kw::Option) => {
                self.advance();
                match self.k() {
                    TokenKind::Ident { name, suffix: None } if name == "EXPLICIT" => {
                        self.advance();
                        Some(Stmt::OptionStmt {
                            kind: OptionKind::Explicit,
                            pos,
                        })
                    }
                    TokenKind::Ident { name, suffix: None } if name == "BASE" => {
                        self.advance();
                        let base = match self.k() {
                            TokenKind::Num(NumValue::Int(n @ (0 | 1))) => {
                                self.advance();
                                n as u8
                            }
                            _ => {
                                self.err("Expected: 0 or 1");
                                self.sync();
                                0
                            }
                        };
                        Some(Stmt::OptionStmt {
                            kind: OptionKind::Base(base),
                            pos,
                        })
                    }
                    _ => {
                        self.err("Expected: BASE or EXPLICIT");
                        self.sync();
                        None
                    }
                }
            }
            TokenKind::Kw(Kw::Type) => self.parse_type_decl(pos),
            TokenKind::Kw(Kw::Declare) => {
                self.advance();
                let sig = self.parse_proc_sig()?;
                Some(Stmt::Declare { sig, pos })
            }
            TokenKind::Kw(Kw::Call) => {
                self.advance();
                match self.k() {
                    TokenKind::Ident { name, suffix } => {
                        self.advance();
                        let args = if self.eat(&TokenKind::LParen) {
                            let a = self.parse_arg_list(&TokenKind::RParen);
                            self.eat(&TokenKind::RParen);
                            a
                        } else {
                            Vec::new()
                        };
                        Some(Stmt::Call {
                            name,
                            suffix,
                            args,
                            pos,
                        })
                    }
                    _ => {
                        self.err("Expected: identifier");
                        self.sync();
                        None
                    }
                }
            }
            TokenKind::Kw(Kw::Data) => {
                self.advance();
                // Der Lexer liefert den Rohtext; hier wird nur an Kommas
                // außerhalb von Anführungszeichen getrennt. Unquotierte
                // Elemente verlieren nur den Leerraum an den Rändern.
                let roh = match self.k() {
                    TokenKind::DataRaw(t) => {
                        self.advance();
                        t
                    }
                    _ => String::new(),
                };
                let mut items = Vec::new();
                let mut cur = String::new();
                let mut in_quote = false;
                let mut quotiert = false;
                for c in roh.chars() {
                    match c {
                        '"' if !in_quote && cur.trim().is_empty() => {
                            in_quote = true;
                            quotiert = true;
                            cur.clear();
                        }
                        '"' if in_quote => in_quote = false,
                        ',' if !in_quote => {
                            items.push(if quotiert {
                                std::mem::take(&mut cur)
                            } else {
                                std::mem::take(&mut cur).trim().to_string()
                            });
                            quotiert = false;
                        }
                        _ => cur.push(c),
                    }
                }
                items.push(if quotiert {
                    cur
                } else {
                    cur.trim().to_string()
                });
                Some(Stmt::Data { items, pos })
            }
            TokenKind::Kw(Kw::Read) => {
                self.advance();
                let mut vars = Vec::new();
                while let Some(e) = self.parse_name_ref() {
                    vars.push(e);
                    if !self.eat(&TokenKind::Comma) {
                        break;
                    }
                }
                Some(Stmt::ReadStmt { vars, pos })
            }
            TokenKind::Kw(Kw::Restore) => {
                self.advance();
                let target = if self.at_stmt_end() {
                    None
                } else {
                    self.parse_label_ref()
                };
                Some(Stmt::Restore { target, pos })
            }
            TokenKind::Kw(Kw::Def) => self.parse_def_fn(pos),
            TokenKind::Kw(Kw::Let) => {
                self.advance();
                self.parse_assign_or_call(pos, true)
            }
            TokenKind::Kw(Kw::Open) => self.parse_open(pos),
            TokenKind::Kw(Kw::Close) => {
                self.advance();
                let mut files = Vec::new();
                while !self.at_stmt_end() {
                    self.eat(&TokenKind::Hash);
                    match self.parse_expr() {
                        Some(e) => files.push(e),
                        None => break,
                    }
                    if !self.eat(&TokenKind::Comma) {
                        break;
                    }
                }
                Some(Stmt::CloseStmt { files, pos })
            }
            TokenKind::Kw(Kw::Field) => {
                self.advance();
                self.eat(&TokenKind::Hash);
                let file = self.parse_expr()?;
                let mut fields = Vec::new();
                while self.eat(&TokenKind::Comma) {
                    let width = self.parse_expr()?;
                    self.expect_kw(Kw::As, "AS");
                    let var = self.parse_name_ref()?;
                    fields.push((width, var));
                }
                if fields.is_empty() {
                    self.err("Expected: field list");
                }
                Some(Stmt::FieldStmt { file, fields, pos })
            }
            TokenKind::Kw(k @ (Kw::Get | Kw::Put)) => {
                self.advance();
                if self.k() == TokenKind::LParen {
                    // Grafikform GET (x1,y1)-(x2,y2): deklariertes Non-Feature
                    self.err(format!(
                        "Feature unavailable: {}",
                        if k == Kw::Get { "GET" } else { "PUT" }
                    ));
                    self.sync();
                    return None;
                }
                self.eat(&TokenKind::Hash);
                let file = self.parse_expr()?;
                let mut recnum = None;
                let mut var = None;
                if self.eat(&TokenKind::Comma) {
                    if !self.at_stmt_end() && self.k() != TokenKind::Comma {
                        recnum = Some(self.parse_expr()?);
                    }
                    if self.eat(&TokenKind::Comma) {
                        var = Some(self.parse_name_ref()?);
                    }
                }
                Some(Stmt::GetPut {
                    put: k == Kw::Put,
                    file,
                    recnum,
                    var,
                    pos,
                })
            }
            TokenKind::Kw(k @ (Kw::Lset | Kw::Rset)) => {
                self.advance();
                let target = self.parse_name_ref()?;
                if !self.eat(&TokenKind::Eq) {
                    self.err("Expected: =");
                    self.sync();
                    return None;
                }
                let value = self.parse_expr()?;
                Some(Stmt::LsetRset {
                    rset: k == Kw::Rset,
                    target,
                    value,
                    pos,
                })
            }
            TokenKind::Kw(Kw::Write) => {
                self.advance();
                let file = if self.eat(&TokenKind::Hash) {
                    let e = self.parse_expr()?;
                    self.eat(&TokenKind::Comma);
                    Some(e)
                } else {
                    None
                };
                let mut items = Vec::new();
                while !self.at_stmt_end() {
                    match self.parse_expr() {
                        Some(e) => items.push(e),
                        None => break,
                    }
                    if !self.eat(&TokenKind::Comma) {
                        break;
                    }
                }
                Some(Stmt::WriteStmt { file, items, pos })
            }
            TokenKind::Kw(Kw::Seek) => {
                self.advance();
                self.eat(&TokenKind::Hash);
                let file = self.parse_expr()?;
                if !self.eat(&TokenKind::Comma) {
                    self.err("Expected: ,");
                }
                let position = self.parse_expr()?;
                Some(Stmt::SeekStmt {
                    file,
                    position,
                    pos,
                })
            }
            TokenKind::Kw(Kw::Common) => {
                self.advance();
                let shared = self.eat_kw(Kw::Shared);
                let mut block = None;
                if self.eat(&TokenKind::Slash) {
                    if let TokenKind::Ident { name, suffix: None } = self.k() {
                        self.advance();
                        block = Some(name);
                    } else {
                        self.err("Expected: identifier");
                    }
                    if !self.eat(&TokenKind::Slash) {
                        self.err("Expected: /");
                    }
                }
                let decls = self.parse_var_decls();
                Some(Stmt::CommonDecl {
                    shared,
                    block,
                    decls,
                    pos,
                })
            }
            TokenKind::Kw(Kw::Shared) => {
                self.advance();
                let decls = self.parse_var_decls();
                Some(Stmt::SharedDecl { decls, pos })
            }
            TokenKind::Kw(Kw::Static) => {
                self.advance();
                let decls = self.parse_var_decls();
                Some(Stmt::StaticDecl { decls, pos })
            }
            TokenKind::Ident { ref name, .. } if name == "LPRINT" => self.parse_print(pos, true),
            TokenKind::Ident {
                ref name,
                suffix: None,
            } if name == "VIEW" && self.k_at(1) == TokenKind::Kw(Kw::Print) => {
                self.advance(); // VIEW
                self.advance(); // PRINT
                let (mut top, mut bottom) = (None, None);
                if !self.at_stmt_end() {
                    top = Some(self.parse_expr()?);
                    self.expect_kw(Kw::To, "TO");
                    bottom = Some(self.parse_expr()?);
                }
                Some(Stmt::ViewPrint { top, bottom, pos })
            }
            TokenKind::Ident {
                ref name,
                suffix: None,
            } if name == "VIEW" => self.parse_graphics_view(pos),
            TokenKind::Ident {
                ref name,
                suffix: None,
            } if name == "PAINT" => self.parse_graphics_paint(pos),
            TokenKind::Ident {
                ref name,
                suffix: None,
            } if name == "NAME" && self.k_at(1) != TokenKind::Eq => {
                self.advance();
                let old = self.parse_expr()?;
                self.expect_kw(Kw::As, "AS");
                let new = self.parse_expr()?;
                Some(Stmt::NameStmt { old, new, pos })
            }
            TokenKind::Ident {
                ref name,
                suffix: None,
            } if (name == "LOCK" || name == "UNLOCK") && self.k_at(1) != TokenKind::Eq => {
                let unlock = name == "UNLOCK";
                self.advance();
                self.eat(&TokenKind::Hash);
                let file = self.parse_expr()?;
                let (mut from, mut to) = (None, None);
                if self.eat(&TokenKind::Comma) {
                    if !self.is_kw(Kw::To) {
                        from = Some(self.parse_expr()?);
                    }
                    if self.eat_kw(Kw::To) {
                        to = Some(self.parse_expr()?);
                    }
                }
                Some(Stmt::LockStmt {
                    unlock,
                    file,
                    from,
                    to,
                    pos,
                })
            }
            // ISAM-Anweisungen: `NAME [#]n [, arg …]`. Sie unterscheiden sich
            // von einem gewöhnlichen impliziten Aufruf nur durch das
            // erlaubte `#` vor der Dateinummer; alles Weitere (Signatur,
            // Absenkung) läuft über die Builtin-Anweisungstabelle.
            // `=` und `(` schließen die Anweisungsdeutung aus: dann ist der
            // Name eine Variable bzw. ein Array (`DELETE = 1`, `DELETE(2) = 1`).
            // Keine ISAM-Syntaxform beginnt mit einer Klammer.
            TokenKind::Ident {
                ref name,
                suffix: None,
            } if ist_isam_anweisung(name)
                && self.k_at(1) != TokenKind::Eq
                && self.k_at(1) != TokenKind::LParen =>
            {
                self.parse_isam(name.clone(), pos)
            }
            TokenKind::Ident { .. } => self.parse_assign_or_call(pos, false),
            _ => {
                self.err("Syntax error");
                self.sync();
                None
            }
        }
    }

    /// ISAM-Anweisung: `NAME [#]dateinummer [, argument …]`.
    /// Syntaxformen siehe `openspec/changes/phase-3-isam/umfang-und-signaturen.md`.
    fn parse_isam(&mut self, name: String, pos: SourcePos) -> Option<Stmt> {
        self.advance();
        // `ROLLBACK ALL` — hier ist `ALL` Schlüsselwort, kein Bezeichner.
        // Es senkt auf die Sentinel-Kennung ab, damit `ROLLBACK` eine
        // einzige Signatur behält.
        if name == "ROLLBACK" {
            if let TokenKind::Ident {
                name: ref w,
                suffix: None,
            } = self.k()
            {
                if w == "ALL" {
                    self.advance();
                    let args = vec![Expr::LongLit(ROLLBACK_ALL, pos)];
                    return Some(Stmt::Call {
                        name,
                        suffix: None,
                        args,
                        pos,
                    });
                }
            }
        }
        self.eat(&TokenKind::Hash);
        let args = if self.at_stmt_end() {
            Vec::new()
        } else {
            self.parse_arg_list_to_stmt_end()
        };
        Some(Stmt::Call {
            name,
            suffix: None,
            args,
            pos,
        })
    }

    fn parse_graphics_point(&mut self) -> Option<(Expr, Expr)> {
        if !self.eat(&TokenKind::LParen) {
            self.err("Expected: (");
            return None;
        }
        let x = self.parse_expr()?;
        if !self.eat(&TokenKind::Comma) {
            self.err("Expected: ,");
            return None;
        }
        let y = self.parse_expr()?;
        if !self.eat(&TokenKind::RParen) {
            self.err("Expected: )");
            return None;
        }
        Some((x, y))
    }

    fn parse_graphics_line(&mut self, pos: SourcePos) -> Option<Stmt> {
        let from = if self.eat(&TokenKind::Minus) {
            None
        } else {
            let point = self.parse_graphics_point()?;
            if !self.eat(&TokenKind::Minus) {
                self.err("Expected: -");
                return None;
            }
            Some(point)
        };
        let relative = self.eat_kw(Kw::Step);
        let to = self.parse_graphics_point()?;
        let mut color = None;
        let mut fill = 0;
        if self.eat(&TokenKind::Comma) {
            if !matches!(
                self.k(),
                TokenKind::Comma | TokenKind::Colon | TokenKind::Eol | TokenKind::Eof
            ) {
                color = Some(self.parse_expr()?);
            }
            if self.eat(&TokenKind::Comma) {
                if let TokenKind::Ident { name, .. } = self.k() {
                    fill = match name.as_str() {
                        "B" => 1,
                        "BF" => 2,
                        _ => {
                            self.err("Expected: B or BF");
                            return None;
                        }
                    };
                    self.advance();
                }
            }
        }
        Some(Stmt::GraphicsLine {
            from,
            to,
            relative,
            color,
            fill,
            pos,
        })
    }

    fn parse_graphics_paint(&mut self, pos: SourcePos) -> Option<Stmt> {
        self.advance();
        let point = self.parse_graphics_point()?;
        let paint = self
            .eat(&TokenKind::Comma)
            .then(|| self.parse_expr())
            .flatten();
        let border = self
            .eat(&TokenKind::Comma)
            .then(|| self.parse_expr())
            .flatten();
        Some(Stmt::GraphicsPaint {
            point,
            paint,
            border,
            pos,
        })
    }

    fn parse_graphics_view(&mut self, pos: SourcePos) -> Option<Stmt> {
        self.advance();
        if self.at_stmt_end() {
            return Some(Stmt::GraphicsView {
                rect: None,
                fill: None,
                border: None,
                pos,
            });
        }
        let (x1, y1) = self.parse_graphics_point()?;
        if !self.eat(&TokenKind::Minus) {
            self.err("Expected: -");
            return None;
        }
        let (x2, y2) = self.parse_graphics_point()?;
        let fill = self
            .eat(&TokenKind::Comma)
            .then(|| self.parse_expr())
            .flatten();
        let border = self
            .eat(&TokenKind::Comma)
            .then(|| self.parse_expr())
            .flatten();
        Some(Stmt::GraphicsView {
            rect: Some((x1, y1, x2, y2)),
            fill,
            border,
            pos,
        })
    }

    fn parse_print(&mut self, pos: SourcePos, printer: bool) -> Option<Stmt> {
        self.advance(); // PRINT bzw. LPRINT
        let file = if !printer && self.eat(&TokenKind::Hash) {
            let e = self.parse_expr()?;
            self.eat(&TokenKind::Comma);
            Some(e)
        } else {
            None
        };
        let using = if self.eat_kw(Kw::Using) {
            let f = self.parse_expr()?;
            if !self.eat(&TokenKind::Semicolon) {
                self.err("Expected: ;");
            }
            Some(f)
        } else {
            None
        };
        let mut items = Vec::new();
        loop {
            match self.k() {
                TokenKind::Comma => {
                    self.advance();
                    items.push(PrintItem::Comma);
                }
                TokenKind::Semicolon => {
                    self.advance();
                    items.push(PrintItem::Semicolon);
                }
                _ if self.at_stmt_end() => break,
                _ => match self.parse_expr() {
                    Some(e) => items.push(PrintItem::Expr(e)),
                    None => break,
                },
            }
        }
        Some(Stmt::Print {
            printer,
            file,
            using,
            items,
            pos,
        })
    }

    /// `OPEN` in beiden Syntaxformen (FOR-Klausel und Kurzform).
    fn parse_open(&mut self, pos: SourcePos) -> Option<Stmt> {
        self.advance(); // OPEN
        let first = self.parse_expr()?;
        if self.eat(&TokenKind::Comma) {
            // Alte Syntax: OPEN modus$, [#]n, datei$ [, reclen]
            self.eat(&TokenKind::Hash);
            let number = self.parse_expr()?;
            if !self.eat(&TokenKind::Comma) {
                self.err("Expected: ,");
            }
            let file = self.parse_expr()?;
            let len = if self.eat(&TokenKind::Comma) {
                Some(self.parse_expr()?)
            } else {
                None
            };
            return Some(Stmt::OpenLegacy {
                mode: first,
                number,
                file,
                len,
                pos,
            });
        }
        let mut mode = None;
        let mut isam = None;
        if self.eat_kw(Kw::For) {
            mode = Some(match self.k() {
                TokenKind::Kw(Kw::Input) => {
                    self.advance();
                    OpenMode::Input
                }
                TokenKind::Ident {
                    ref name,
                    suffix: None,
                } => {
                    let m = match name.as_str() {
                        "OUTPUT" => OpenMode::Output,
                        "APPEND" => OpenMode::Append,
                        "RANDOM" => OpenMode::Random,
                        "BINARY" => OpenMode::Binary,
                        "ISAM" => OpenMode::Isam,
                        _ => {
                            self.err("Expected: file mode");
                            OpenMode::Random
                        }
                    };
                    self.advance();
                    m
                }
                _ => {
                    self.err("Expected: file mode");
                    OpenMode::Random
                }
            });
            if mode == Some(OpenMode::Isam) {
                // OPEN datenbank$ FOR ISAM typname tabellenname AS #n
                let ty = match self.k() {
                    TokenKind::Ident { name, suffix: None } => {
                        self.advance();
                        name
                    }
                    _ => {
                        self.err("Expected: identifier");
                        String::new()
                    }
                };
                let table = match self.k() {
                    TokenKind::Ident { name, .. } => {
                        self.advance();
                        name
                    }
                    TokenKind::Str(s) => {
                        self.advance();
                        s
                    }
                    _ => {
                        self.err("Expected: table name");
                        String::new()
                    }
                };
                isam = Some((ty, table));
            }
        }
        let mut access = None;
        if let TokenKind::Ident {
            ref name,
            suffix: None,
        } = self.k()
        {
            if name == "ACCESS" {
                self.advance();
                let read = self.eat_kw(Kw::Read);
                let write = self.eat_kw(Kw::Write);
                access = Some(match (read, write) {
                    (true, true) => AccessMode::ReadWrite,
                    (true, false) => AccessMode::Read,
                    (false, true) => AccessMode::Write,
                    (false, false) => {
                        self.err("Expected: READ or WRITE");
                        AccessMode::ReadWrite
                    }
                });
            }
        }
        let mut lock = None;
        if self.eat_kw(Kw::Shared) {
            lock = Some(LockClause::Shared);
        } else if let TokenKind::Ident {
            ref name,
            suffix: None,
        } = self.k()
        {
            if name == "LOCK" {
                self.advance();
                let read = self.eat_kw(Kw::Read);
                let write = self.eat_kw(Kw::Write);
                lock = Some(match (read, write) {
                    (true, true) => LockClause::LockReadWrite,
                    (true, false) => LockClause::LockRead,
                    (false, true) => LockClause::LockWrite,
                    (false, false) => {
                        self.err("Expected: READ or WRITE");
                        LockClause::LockReadWrite
                    }
                });
            }
        }
        self.expect_kw(Kw::As, "AS");
        self.eat(&TokenKind::Hash);
        let number = self.parse_expr()?;
        let mut len = None;
        if let TokenKind::Ident {
            ref name,
            suffix: None,
        } = self.k()
        {
            if name == "LEN" {
                self.advance();
                if !self.eat(&TokenKind::Eq) {
                    self.err("Expected: =");
                }
                len = Some(self.parse_expr()?);
            }
        }
        Some(Stmt::Open {
            file: first,
            mode,
            isam,
            access,
            lock,
            number,
            len,
            pos,
        })
    }

    fn parse_input(&mut self, line: bool, pos: SourcePos) -> Option<Stmt> {
        let keep_cursor = self.eat(&TokenKind::Semicolon);
        let file = if self.eat(&TokenKind::Hash) {
            let e = self.parse_expr()?;
            self.eat(&TokenKind::Comma);
            Some(e)
        } else {
            None
        };
        let mut prompt = None;
        if file.is_none() {
            if let TokenKind::Str(s) = self.k() {
                if matches!(self.k_at(1), TokenKind::Semicolon | TokenKind::Comma) {
                    self.advance();
                    let with_question = self.k() == TokenKind::Semicolon;
                    self.advance();
                    prompt = Some((s, with_question));
                }
            }
        }
        let mut vars = Vec::new();
        while let Some(e) = self.parse_name_ref() {
            vars.push(e);
            if !self.eat(&TokenKind::Comma) {
                break;
            }
        }
        if vars.is_empty() {
            self.err("Expected: variable");
        }
        Some(Stmt::Input {
            line,
            file,
            keep_cursor,
            prompt,
            vars,
            pos,
        })
    }

    fn parse_if(&mut self, pos: SourcePos) -> Option<Stmt> {
        self.advance(); // IF
        let cond = self.parse_expr()?;
        if !self.expect_kw(Kw::Then, "THEN") {
            self.sync();
            return None;
        }
        if matches!(self.k(), TokenKind::Eol) {
            // Blockform
            let stop = |p: &P| p.is_kw(Kw::ElseIf) || p.is_kw(Kw::Else) || p.at_end_pair(Kw::If);
            let then_body = self.parse_stmt_list(stop);
            let mut elseifs = Vec::new();
            while self.is_kw(Kw::ElseIf) {
                let elseif_pos = self.pos();
                self.advance();
                let c = self.parse_expr()?;
                self.expect_kw(Kw::Then, "THEN");
                let b = self.parse_stmt_list(stop);
                elseifs.push((c, b, elseif_pos));
            }
            let else_body = if self.eat_kw(Kw::Else) {
                Some(self.parse_stmt_list(|p: &P| p.at_end_pair(Kw::If)))
            } else {
                None
            };
            if self.at_end_pair(Kw::If) {
                self.advance();
                self.advance();
            } else {
                self.err("Expected: END IF");
            }
            Some(Stmt::If {
                cond,
                then_body,
                elseifs,
                else_body,
                pos,
            })
        } else {
            // Einzeilig
            let then_body = if let Some(n) = self.line_number_target() {
                vec![Stmt::Goto { target: n, pos }]
            } else {
                self.parse_inline_stmts()
            };
            let else_body = if self.eat_kw(Kw::Else) {
                if let Some(n) = self.line_number_target() {
                    Some(vec![Stmt::Goto { target: n, pos }])
                } else {
                    Some(self.parse_inline_stmts())
                }
            } else {
                None
            };
            Some(Stmt::If {
                cond,
                then_body,
                elseifs: Vec::new(),
                else_body,
                pos,
            })
        }
    }

    fn line_number_target(&mut self) -> Option<LabelRef> {
        match self.k() {
            TokenKind::Num(NumValue::Int(n)) if n >= 0 => {
                self.advance();
                Some(LabelRef::Line(n as u32))
            }
            TokenKind::Num(NumValue::Long(n)) if n >= 0 => {
                self.advance();
                Some(LabelRef::Line(n as u32))
            }
            _ => None,
        }
    }

    fn parse_select(&mut self, pos: SourcePos) -> Option<Stmt> {
        self.advance(); // SELECT
        self.expect_kw(Kw::Case, "CASE");
        let expr = self.parse_expr()?;
        self.skip_seps();
        let mut arms = Vec::new();
        while self.is_kw(Kw::Case) {
            let arm_pos = self.pos();
            self.advance();
            let mut specs = Vec::new();
            if !self.eat_kw(Kw::Else) {
                loop {
                    if self.eat_kw(Kw::Is) {
                        let op = match self.k() {
                            TokenKind::Eq => BinOp::Eq,
                            TokenKind::Ne => BinOp::Ne,
                            TokenKind::Lt => BinOp::Lt,
                            TokenKind::Le => BinOp::Le,
                            TokenKind::Gt => BinOp::Gt,
                            TokenKind::Ge => BinOp::Ge,
                            _ => {
                                self.err("Expected: relational operator");
                                BinOp::Eq
                            }
                        };
                        self.advance();
                        let e = self.parse_expr()?;
                        specs.push(CaseSpec::Is(op, e));
                    } else {
                        let a = self.parse_expr()?;
                        if self.eat_kw(Kw::To) {
                            let b = self.parse_expr()?;
                            specs.push(CaseSpec::Range(a, b));
                        } else {
                            specs.push(CaseSpec::Expr(a));
                        }
                    }
                    if !self.eat(&TokenKind::Comma) {
                        break;
                    }
                }
            }
            let body = self.parse_stmt_list(|p: &P| p.is_kw(Kw::Case) || p.at_end_pair(Kw::Select));
            arms.push(CaseArm {
                specs,
                body,
                pos: arm_pos,
            });
        }
        if self.at_end_pair(Kw::Select) {
            self.advance();
            self.advance();
        } else {
            self.err("Expected: END SELECT");
        }
        Some(Stmt::Select { expr, arms, pos })
    }

    fn parse_for(&mut self, pos: SourcePos) -> Option<Stmt> {
        self.advance(); // FOR
        let var = self.parse_name_only()?;
        if !self.eat(&TokenKind::Eq) {
            self.err("Expected: =");
            self.sync();
            return None;
        }
        let from = self.parse_expr()?;
        self.expect_kw(Kw::To, "TO");
        let to = self.parse_expr()?;
        let step = if self.eat_kw(Kw::Step) {
            Some(self.parse_expr()?)
        } else {
            None
        };
        let body = self.parse_stmt_list(|p: &P| p.is_kw(Kw::Next));
        let end_pos = self.pos();
        if self.eat_kw(Kw::Next) {
            // Optional: Zählvariable(n) hinter NEXT
            while matches!(self.k(), TokenKind::Ident { .. }) {
                self.advance();
                if !self.eat(&TokenKind::Comma) {
                    break;
                }
            }
        } else {
            self.err("FOR without NEXT");
        }
        Some(Stmt::For {
            var,
            from,
            to,
            step,
            body,
            pos,
            end_pos,
        })
    }

    fn parse_do(&mut self, pos: SourcePos) -> Option<Stmt> {
        self.advance(); // DO
        let pre = if self.eat_kw(Kw::While) {
            Some((false, self.parse_expr()?))
        } else if self.eat_kw(Kw::Until) {
            Some((true, self.parse_expr()?))
        } else {
            None
        };
        let body = self.parse_stmt_list(|p: &P| p.is_kw(Kw::Loop));
        let end_pos = self.pos();
        let mut post = None;
        if self.eat_kw(Kw::Loop) {
            if self.eat_kw(Kw::While) {
                post = Some((false, self.parse_expr()?));
            } else if self.eat_kw(Kw::Until) {
                post = Some((true, self.parse_expr()?));
            }
        } else {
            self.err("DO without LOOP");
        }
        Some(Stmt::DoLoop {
            pre,
            post,
            body,
            pos,
            end_pos,
        })
    }

    fn parse_while(&mut self, pos: SourcePos) -> Option<Stmt> {
        self.advance(); // WHILE
        let cond = self.parse_expr()?;
        let body = self.parse_stmt_list(|p: &P| p.is_kw(Kw::Wend));
        let end_pos = self.pos();
        if !self.eat_kw(Kw::Wend) {
            self.err("WHILE without WEND");
        }
        Some(Stmt::While {
            cond,
            body,
            pos,
            end_pos,
        })
    }

    /// `ON <quelle>[(n)] GOSUB ziel`. Liefert `None` und lässt den Cursor
    /// unberührt, wenn hier kein Trap steht.
    fn parse_trap_def(&mut self, pos: SourcePos) -> Option<Stmt> {
        let start = self.i;
        let TokenKind::Ident { name, suffix: None } = self.k() else {
            return None;
        };
        if !matches!(
            name.as_str(),
            "TIMER" | "KEY" | "UEVENT" | "SIGNAL" | "COM" | "PEN" | "PLAY" | "STRIG"
        ) {
            return None;
        }
        let device = name.clone();
        self.advance();
        let index = if self.eat(&TokenKind::LParen) {
            let e = self.parse_expr();
            if e.is_none() || !self.eat(&TokenKind::RParen) {
                self.i = start;
                return None;
            }
            e
        } else {
            None
        };
        if !self.eat_kw(Kw::Gosub) {
            // Doch kein Trap — z. B. `ON TIMER GOTO …` gibt es nicht, aber
            // die Diagnose dafür gehört an die allgemeine Stelle.
            self.i = start;
            return None;
        }
        let target = self.parse_label_ref()?;
        Some(Stmt::TrapDef {
            device,
            index,
            target,
            pos,
        })
    }

    fn parse_on(&mut self, pos: SourcePos) -> Option<Stmt> {
        self.advance(); // ON
        let local = self.eat_kw(Kw::Local);
        if self.eat_kw(Kw::Error) {
            if self.eat_kw(Kw::Goto) {
                if self.k() == TokenKind::Num(NumValue::Int(0)) {
                    self.advance();
                    return Some(Stmt::OnError {
                        local,
                        action: OnErrorAction::Disable,
                        pos,
                    });
                }
                let target = self.parse_label_ref()?;
                return Some(Stmt::OnError {
                    local,
                    action: OnErrorAction::Goto(target),
                    pos,
                });
            }
            if self.eat_kw(Kw::Resume) {
                self.expect_kw(Kw::Next, "NEXT");
                return Some(Stmt::OnError {
                    local,
                    action: OnErrorAction::ResumeNext,
                    pos,
                });
            }
            self.err("Expected: GOTO or RESUME NEXT");
            self.sync();
            return None;
        }
        if local {
            self.err("Expected: ERROR");
            self.sync();
            return None;
        }
        // ON <quelle>[(n)] GOSUB ziel — klassische Ereignis-Traps (§8).
        // Muss vor dem berechneten `ON ausdruck GOSUB` stehen, sonst
        // verschwindet `ON TIMER(5) GOSUB Marke` still darin.
        if let Some(st) = self.parse_trap_def(pos) {
            return Some(st);
        }
        // ON ausdruck GOTO/GOSUB liste
        let expr = self.parse_expr()?;
        let gosub = if self.eat_kw(Kw::Goto) {
            false
        } else if self.eat_kw(Kw::Gosub) {
            true
        } else {
            self.err("Expected: GOTO or GOSUB");
            self.sync();
            return None;
        };
        let mut targets = Vec::new();
        while let Some(t) = self.parse_label_ref() {
            targets.push(t);
            if !self.eat(&TokenKind::Comma) {
                break;
            }
        }
        Some(Stmt::OnGoto {
            expr,
            targets,
            gosub,
            pos,
        })
    }

    fn parse_dim(&mut self, redim: bool, pos: SourcePos) -> Option<Stmt> {
        let shared = self.eat_kw(Kw::Shared);
        let decls = self.parse_var_decls();
        Some(Stmt::Dim {
            shared,
            redim,
            decls,
            pos,
        })
    }

    /// Deklarationsliste `name[suffix][(dims)] [AS typ], …` — gemeinsame
    /// Grammatik von DIM/REDIM/COMMON/SHARED/STATIC.
    fn parse_var_decls(&mut self) -> Vec<VarDecl> {
        let mut decls = Vec::new();
        loop {
            let dpos = self.pos();
            let (name, suffix) = match self.k() {
                TokenKind::Ident { name, suffix } => {
                    self.advance();
                    (name, suffix)
                }
                _ => {
                    self.err("Expected: identifier");
                    self.sync();
                    break;
                }
            };
            let dims = if self.eat(&TokenKind::LParen) {
                let mut ds = Vec::new();
                if !self.eat(&TokenKind::RParen) {
                    while let Some(a) = self.parse_expr() {
                        if self.eat_kw(Kw::To) {
                            match self.parse_expr() {
                                Some(b) => ds.push((Some(a), b)),
                                None => break,
                            }
                        } else {
                            ds.push((None, a));
                        }
                        if !self.eat(&TokenKind::Comma) {
                            break;
                        }
                    }
                    self.eat(&TokenKind::RParen);
                }
                Some(ds)
            } else {
                None
            };
            let as_type = if self.eat_kw(Kw::As) {
                self.parse_type_name()
            } else {
                None
            };
            decls.push(VarDecl {
                name,
                suffix,
                dims,
                as_type,
                pos: dpos,
            });
            if !self.eat(&TokenKind::Comma) {
                break;
            }
        }
        decls
    }

    fn parse_type_name(&mut self) -> Option<TypeName> {
        match self.k() {
            TokenKind::Kw(Kw::Integer) => {
                self.advance();
                Some(TypeName::Integer)
            }
            TokenKind::Kw(Kw::Long) => {
                self.advance();
                Some(TypeName::Long)
            }
            TokenKind::Kw(Kw::Single) => {
                self.advance();
                Some(TypeName::Single)
            }
            TokenKind::Kw(Kw::Double) => {
                self.advance();
                Some(TypeName::Double)
            }
            TokenKind::Kw(Kw::Currency) => {
                self.advance();
                Some(TypeName::Currency)
            }
            TokenKind::Kw(Kw::String) => {
                self.advance();
                if self.eat(&TokenKind::Star) {
                    match self.k() {
                        TokenKind::Num(NumValue::Int(n)) => {
                            self.advance();
                            Some(TypeName::FixedStr(n as i64))
                        }
                        TokenKind::Num(NumValue::Long(n)) => {
                            self.advance();
                            Some(TypeName::FixedStr(n as i64))
                        }
                        _ => {
                            self.err("Expected: constant");
                            Some(TypeName::Str)
                        }
                    }
                } else {
                    Some(TypeName::Str)
                }
            }
            TokenKind::Ident { name, suffix: None } => {
                self.advance();
                Some(match name.as_str() {
                    "FORM" => TypeName::Form,
                    "CONTROL" => TypeName::Control,
                    _ => TypeName::Udt(name),
                })
            }
            _ => {
                self.err("Expected: type");
                None
            }
        }
    }

    fn parse_type_decl(&mut self, pos: SourcePos) -> Option<Stmt> {
        self.advance(); // TYPE
        let name = match self.k() {
            TokenKind::Ident { name, suffix: None } => {
                self.advance();
                name
            }
            _ => {
                self.err("Expected: identifier");
                self.sync();
                return None;
            }
        };
        let mut fields = Vec::new();
        loop {
            self.skip_seps();
            if self.at_end_pair(Kw::Type) {
                self.advance();
                self.advance();
                break;
            }
            if matches!(self.k(), TokenKind::Eof) {
                self.err("Expected: END TYPE");
                break;
            }
            match self.k() {
                TokenKind::Ident {
                    name: fname,
                    suffix: None,
                } => {
                    self.advance();
                    if self.expect_kw(Kw::As, "AS") {
                        if let Some(t) = self.parse_type_name() {
                            fields.push((fname, t));
                        }
                    } else {
                        self.sync();
                    }
                }
                _ => {
                    self.err("Expected: identifier");
                    self.sync();
                }
            }
        }
        Some(Stmt::TypeDecl { name, fields, pos })
    }

    fn parse_proc_sig(&mut self) -> Option<ProcSig> {
        let kind = if self.eat_kw(Kw::Sub) {
            ProcKind::Sub
        } else if self.eat_kw(Kw::Function) {
            ProcKind::Function
        } else {
            self.err("Expected: SUB or FUNCTION");
            self.sync();
            return None;
        };
        let (name, suffix) = match self.k() {
            TokenKind::Ident { name, suffix } => {
                self.advance();
                (name, suffix)
            }
            _ => {
                self.err("Expected: identifier");
                self.sync();
                return None;
            }
        };
        let mut params = Vec::new();
        if self.eat(&TokenKind::LParen) && !self.eat(&TokenKind::RParen) {
            loop {
                match self.k() {
                    TokenKind::Ident {
                        name: pname,
                        suffix: psfx,
                    } => {
                        self.advance();
                        let array = if self.eat(&TokenKind::LParen) {
                            self.eat(&TokenKind::RParen);
                            true
                        } else {
                            false
                        };
                        let as_type = if self.eat_kw(Kw::As) {
                            self.parse_type_name()
                        } else {
                            None
                        };
                        params.push(Param {
                            name: pname,
                            suffix: psfx,
                            array,
                            as_type,
                        });
                    }
                    _ => {
                        self.err("Expected: identifier");
                        break;
                    }
                }
                if !self.eat(&TokenKind::Comma) {
                    break;
                }
            }
            self.eat(&TokenKind::RParen);
        }
        Some(ProcSig {
            kind,
            name,
            suffix,
            params,
        })
    }

    fn parse_proc(&mut self, leading_static: bool) -> Option<Proc> {
        let pos = self.pos();
        let sig = self.parse_proc_sig()?;
        let is_static = leading_static || self.eat_kw(Kw::Static);
        let end_kw = match sig.kind {
            ProcKind::Sub => Kw::Sub,
            ProcKind::Function => Kw::Function,
        };
        let body = self.parse_stmt_list(|p: &P| p.at_end_pair(end_kw));
        let end_pos = self.pos();
        if self.at_end_pair(end_kw) {
            self.advance();
            self.advance();
        } else {
            self.err(match sig.kind {
                ProcKind::Sub => "Expected: END SUB",
                ProcKind::Function => "Expected: END FUNCTION",
            });
        }
        Some(Proc {
            sig,
            is_static,
            body,
            pos,
            end_pos,
        })
    }

    fn parse_def_fn(&mut self, pos: SourcePos) -> Option<Stmt> {
        self.advance(); // DEF
                        // `DEF SEG` ist Segmentadressierung und damit deklariertes
                        // Non-Feature — an der Syntax erkennbar, weil `DEF` sonst nur
                        // `DEF FNname` einleitet.
        if matches!(self.k(), TokenKind::Ident { ref name, suffix: None } if name == "SEG") {
            self.err("Feature unavailable: DEF SEG");
            self.sync();
            return None;
        }
        let (name, suffix) = match self.k() {
            TokenKind::Ident { name, suffix } if name.starts_with("FN") => {
                self.advance();
                (name, suffix)
            }
            _ => {
                self.err("Expected: FN identifier");
                self.sync();
                return None;
            }
        };
        let mut params = Vec::new();
        if self.eat(&TokenKind::LParen) && !self.eat(&TokenKind::RParen) {
            loop {
                match self.k() {
                    TokenKind::Ident {
                        name: pname,
                        suffix: psfx,
                    } => {
                        self.advance();
                        params.push(Param {
                            name: pname,
                            suffix: psfx,
                            array: false,
                            as_type: None,
                        });
                    }
                    _ => {
                        self.err("Expected: identifier");
                        break;
                    }
                }
                if !self.eat(&TokenKind::Comma) {
                    break;
                }
            }
            self.eat(&TokenKind::RParen);
        }
        if self.eat(&TokenKind::Eq) {
            let body = self.parse_expr()?;
            Some(Stmt::DefFn {
                name,
                suffix,
                params,
                body,
                pos,
            })
        } else {
            // Blockform: DEF FNname(...) … [EXIT DEF] … END DEF
            let body = self.parse_stmt_list(|p: &P| p.at_end_pair(Kw::Def));
            if self.at_end_pair(Kw::Def) {
                self.advance();
                self.advance();
            } else {
                self.err("Expected: END DEF");
            }
            Some(Stmt::DefFnBlock {
                name,
                suffix,
                params,
                body,
                pos,
            })
        }
    }

    fn parse_assign_or_call(&mut self, pos: SourcePos, force_assign: bool) -> Option<Stmt> {
        let target = self.parse_name_ref()?;
        if self.eat(&TokenKind::Eq) {
            let value = self.parse_expr()?;
            return Some(Stmt::Assign { target, value, pos });
        }
        if force_assign {
            self.err("Expected: =");
            self.sync();
            return None;
        }
        // Ausgeschlossene Anweisungen besitzen eigene Koordinaten-/Dateisyntax.
        // Erst Zuweisungen erkennen, damit gleichnamige Variablen/Arrays gültig bleiben.
        if let Expr::Name {
            name, suffix: None, ..
        } = &target
        {
            if matches!(
                name.as_str(),
                "CIRCLE" | "WINDOW" | "IOCTL" | "PSET" | "PRESET" | "PALETTE"
            ) {
                self.err(format!("Feature unavailable: {name}"));
                self.sync();
                return None;
            }
        }
        // Ereignissteuerung: `TIMER ON`, `KEY(5) OFF`, `UEVENT STOP` …
        if let Expr::Name {
            ref name,
            suffix: None,
            ref args,
            ..
        } = target
        {
            if matches!(
                name.as_str(),
                "TIMER" | "KEY" | "COM" | "PEN" | "PLAY" | "STRIG" | "UEVENT" | "SIGNAL" | "EVENT"
            ) {
                let action = match self.k() {
                    TokenKind::Kw(Kw::On) => Some(EventAction::On),
                    TokenKind::Kw(Kw::Stop) => Some(EventAction::Stop),
                    TokenKind::Ident {
                        ref name,
                        suffix: None,
                    } if name == "OFF" => Some(EventAction::Off),
                    _ => None,
                };
                if let Some(action) = action {
                    self.advance();
                    let index = args.clone().and_then(|mut a| {
                        if a.is_empty() {
                            None
                        } else {
                            Some(a.remove(0))
                        }
                    });
                    return Some(Stmt::EventControl {
                        device: name.clone(),
                        index,
                        action,
                        pos,
                    });
                }
            }
        }
        // Impliziter Aufruf: `name [arg [, arg …]]`
        if let Expr::Name {
            name, suffix, args, ..
        } = target
        {
            // Ohne CALL-Keyword sind Klammern Wert-Klammern (BYVAL), keine
            // Argumentlisten-Klammern: `Foo (n%)` übergibt `(n%)`.
            let mut call_args: Vec<Expr> = args
                .unwrap_or_default()
                .into_iter()
                .map(|a| match a {
                    p @ Expr::Paren(_) => p,
                    other => Expr::Paren(Box::new(other)),
                })
                .collect();
            if !self.at_stmt_end() && (call_args.is_empty() || name.contains('.')) {
                call_args.extend(self.parse_arg_list_to_stmt_end());
            }
            Some(Stmt::Call {
                name,
                suffix,
                args: call_args,
                pos,
            })
        } else {
            self.err("Syntax error");
            self.sync();
            None
        }
    }

    // ---- Namen und Argumente ---------------------------------------------

    /// Name mit optionalen Klammer-Argumenten (Variable/Array/Funktion).
    fn parse_name_ref(&mut self) -> Option<Expr> {
        let pos = self.pos();
        match self.k() {
            TokenKind::Ident { mut name, suffix } => {
                self.advance();
                let mut args = if self.eat(&TokenKind::LParen) {
                    let a = self.parse_arg_list(&TokenKind::RParen);
                    self.eat(&TokenKind::RParen);
                    Some(a)
                } else {
                    None
                };
                if self.eat(&TokenKind::Dot) {
                    match self.k() {
                        TokenKind::Ident {
                            name: member,
                            suffix: None,
                        } => {
                            name.push('.');
                            name.push_str(&member);
                            self.advance();
                            if self.eat(&TokenKind::LParen) {
                                let member_args = self.parse_arg_list(&TokenKind::RParen);
                                self.eat(&TokenKind::RParen);
                                args.get_or_insert_with(Vec::new).extend(member_args);
                            }
                        }
                        _ => self.err("Expected: property"),
                    }
                }
                Some(Expr::Name {
                    name,
                    suffix,
                    args,
                    pos,
                })
            }
            _ => {
                self.err("Expected: variable");
                self.sync();
                None
            }
        }
    }

    /// Nur ein Name ohne Argumente (FOR-Variable, ERASE).
    fn parse_name_only(&mut self) -> Option<Expr> {
        let pos = self.pos();
        match self.k() {
            TokenKind::Ident { name, suffix } => {
                self.advance();
                Some(Expr::Name {
                    name,
                    suffix,
                    args: None,
                    pos,
                })
            }
            _ => {
                self.err("Expected: variable");
                self.sync();
                None
            }
        }
    }

    fn parse_label_ref(&mut self) -> Option<LabelRef> {
        match self.k() {
            TokenKind::Num(NumValue::Int(n)) if n >= 0 => {
                self.advance();
                Some(LabelRef::Line(n as u32))
            }
            TokenKind::Num(NumValue::Long(n)) if n >= 0 => {
                self.advance();
                Some(LabelRef::Line(n as u32))
            }
            TokenKind::Ident { name, suffix: None } => {
                self.advance();
                Some(LabelRef::Name(name))
            }
            _ => {
                self.err("Expected: label or line number");
                self.sync();
                None
            }
        }
    }

    /// Argumentliste bis `closer` (nicht konsumiert); leere Plätze → Missing.
    fn parse_arg_list(&mut self, closer: &TokenKind) -> Vec<Expr> {
        let mut args = Vec::new();
        loop {
            if &self.k() == closer || self.at_stmt_end() {
                if !args.is_empty() {
                    args.push(Expr::Missing);
                }
                break;
            }
            if self.k() == TokenKind::Comma {
                self.advance();
                args.push(Expr::Missing);
                continue;
            }
            self.eat(&TokenKind::Hash);
            match self.parse_expr() {
                Some(e) => args.push(e),
                None => break,
            }
            if !self.eat(&TokenKind::Comma) {
                break;
            }
            if &self.k() == closer || self.at_stmt_end() {
                args.push(Expr::Missing);
                break;
            }
        }
        args
    }

    /// Argumentliste eines impliziten Aufrufs bis zum Anweisungsende.
    fn parse_arg_list_to_stmt_end(&mut self) -> Vec<Expr> {
        let mut args = Vec::new();
        loop {
            if self.at_stmt_end() {
                break;
            }
            if self.k() == TokenKind::Comma {
                self.advance();
                args.push(Expr::Missing);
                continue;
            }
            match self.parse_expr() {
                Some(e) => args.push(e),
                None => break,
            }
            if !self.eat(&TokenKind::Comma) {
                break;
            }
            if self.at_stmt_end() {
                args.push(Expr::Missing);
                break;
            }
        }
        args
    }

    // ---- Ausdrücke ---------------------------------------------------------

    fn parse_expr(&mut self) -> Option<Expr> {
        self.parse_bin(1)
    }

    fn parse_bin(&mut self, min_prec: u8) -> Option<Expr> {
        let mut lhs = self.parse_prefix()?;
        while let Some((prec, op)) = self.infix_op() {
            if prec < min_prec {
                break;
            }
            let pos = self.pos();
            self.advance();
            let rhs = self.parse_bin(prec + 1)?;
            lhs = Expr::Binary {
                op,
                lhs: Box::new(lhs),
                rhs: Box::new(rhs),
                pos,
            };
        }
        Some(lhs)
    }

    /// Prioritäten nach Sprachreferenz §4 (hoch = bindet stärker).
    fn infix_op(&self) -> Option<(u8, BinOp)> {
        Some(match self.k() {
            TokenKind::Kw(Kw::Imp) => (1, BinOp::Imp),
            TokenKind::Kw(Kw::Eqv) => (2, BinOp::Eqv),
            TokenKind::Kw(Kw::Xor) => (3, BinOp::Xor),
            TokenKind::Kw(Kw::Or) => (4, BinOp::Or),
            TokenKind::Kw(Kw::And) => (5, BinOp::And),
            TokenKind::Eq => (7, BinOp::Eq),
            TokenKind::Ne => (7, BinOp::Ne),
            TokenKind::Lt => (7, BinOp::Lt),
            TokenKind::Le => (7, BinOp::Le),
            TokenKind::Gt => (7, BinOp::Gt),
            TokenKind::Ge => (7, BinOp::Ge),
            TokenKind::Plus => (8, BinOp::Add),
            TokenKind::Minus => (8, BinOp::Sub),
            TokenKind::Kw(Kw::Mod) => (9, BinOp::Mod),
            TokenKind::Backslash => (10, BinOp::IntDiv),
            TokenKind::Star => (11, BinOp::Mul),
            TokenKind::Slash => (11, BinOp::Div),
            TokenKind::Caret => (13, BinOp::Pow),
            _ => return None,
        })
    }

    fn parse_prefix(&mut self) -> Option<Expr> {
        let pos = self.pos();
        match self.k() {
            TokenKind::Minus => {
                self.advance();
                let operand = self.parse_bin(12)?;
                Some(Expr::Unary {
                    op: UnOp::Neg,
                    operand: Box::new(operand),
                    pos,
                })
            }
            TokenKind::Plus => {
                self.advance();
                self.parse_bin(12)
            }
            TokenKind::Kw(Kw::Not) => {
                self.advance();
                let operand = self.parse_bin(6)?;
                Some(Expr::Unary {
                    op: UnOp::Not,
                    operand: Box::new(operand),
                    pos,
                })
            }
            _ => self.parse_primary(),
        }
    }

    fn parse_primary(&mut self) -> Option<Expr> {
        // `SEEK(n)` ist zugleich Funktion und Anweisung; in Ausdrucksposition
        // gilt die Funktionsform.
        if self.k() == TokenKind::Kw(Kw::Seek) && self.k_at(1) == TokenKind::LParen {
            let pos = self.pos();
            self.advance(); // SEEK
            self.advance(); // (
            let arg = self.parse_expr()?;
            if !self.eat(&TokenKind::RParen) {
                self.err("Expected: )");
            }
            return Some(Expr::Name {
                name: "SEEK".into(),
                suffix: None,
                args: Some(vec![arg]),
                pos,
            });
        }
        if matches!(self.k(), TokenKind::Ident { ref name, suffix: None } if name == "TYPEOF") {
            let pos = self.pos();
            self.advance();
            let value = self.parse_primary()?;
            if !self.eat_kw(Kw::Is) {
                self.err("Expected: IS");
                return None;
            }
            let class = match self.k() {
                TokenKind::Ident { name, suffix: None } => {
                    self.advance();
                    name
                }
                _ => {
                    self.err("Expected: control class");
                    return None;
                }
            };
            return Some(Expr::TypeOf {
                value: Box::new(value),
                class,
                pos,
            });
        }
        match self.k() {
            TokenKind::Num(n) => {
                let pos = self.pos();
                self.advance();
                Some(match n {
                    NumValue::Int(v) => Expr::IntLit(v, pos),
                    NumValue::Long(v) => Expr::LongLit(v, pos),
                    NumValue::Single(v) => Expr::SingleLit(v, pos),
                    NumValue::Double(v) => Expr::DoubleLit(v, pos),
                    NumValue::Currency(v) => Expr::CurrencyLit(v, pos),
                })
            }
            TokenKind::Str(s) => {
                let pos = self.pos();
                self.advance();
                Some(Expr::StrLit(s, pos))
            }
            TokenKind::LParen => {
                self.advance();
                let e = self.parse_expr()?;
                if !self.eat(&TokenKind::RParen) {
                    self.err("Expected: )");
                }
                Some(Expr::Paren(Box::new(e)))
            }
            TokenKind::Ident { .. } => self.parse_name_ref(),
            _ => {
                self.err("Expected: expression");
                self.sync();
                None
            }
        }
    }
}

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

    fn parse_ok(src: &str) -> Module {
        let l = lex(src);
        assert!(l.diagnostics.is_empty(), "Lexer: {:?}", l.diagnostics);
        let p = parse("TEST", &l.tokens);
        assert!(p.diagnostics.is_empty(), "Parser: {:?}", p.diagnostics);
        p.module
    }

    #[test]
    fn zuweisung_und_aufruf() {
        let m = parse_ok("a% = 1\nCOLOR 15, 1\nCLS");
        assert_eq!(m.body.len(), 3);
        assert!(matches!(m.body[0], Stmt::Assign { .. }));
        assert!(matches!(&m.body[1], Stmt::Call { name, args, .. }
            if name == "COLOR" && args.len() == 2));
        assert!(matches!(&m.body[2], Stmt::Call { name, args, .. }
            if name == "CLS" && args.is_empty()));
    }

    #[test]
    fn print_varianten() {
        let m = parse_ok("PRINT \"a\", \"b\"; 1\nPRINT\nPRINT USING \"##.#\"; 1.5");
        assert!(matches!(&m.body[0], Stmt::Print { items, .. } if items.len() == 5));
        assert!(matches!(&m.body[1], Stmt::Print { items, .. } if items.is_empty()));
        assert!(matches!(&m.body[2], Stmt::Print { using: Some(_), .. }));
    }

    #[test]
    fn if_block_und_einzeilig() {
        let m = parse_ok(
            "IF a > 1 THEN\nPRINT 1\nELSEIF a > 0 THEN\nPRINT 2\nELSE\nPRINT 3\nEND IF\nIF b THEN PRINT 4 ELSE PRINT 5",
        );
        match &m.body[0] {
            Stmt::If {
                elseifs, else_body, ..
            } => {
                assert_eq!(elseifs.len(), 1);
                assert!(else_body.is_some());
            }
            other => panic!("erwartet IF, war {other:?}"),
        }
        assert!(matches!(
            &m.body[1],
            Stmt::If {
                else_body: Some(_),
                ..
            }
        ));
    }

    #[test]
    fn select_case() {
        let m = parse_ok(
            "SELECT CASE i%\nCASE 1: PRINT \"eins\"\nCASE 2 TO 3, IS >= 10\nPRINT \"x\"\nCASE ELSE\nPRINT \"y\"\nEND SELECT",
        );
        match &m.body[0] {
            Stmt::Select { arms, .. } => {
                assert_eq!(arms.len(), 3);
                assert_eq!(arms[1].specs.len(), 2);
                assert!(arms[2].specs.is_empty());
            }
            other => panic!("erwartet SELECT, war {other:?}"),
        }
    }

    #[test]
    fn schleifen() {
        let m = parse_ok(
            "FOR i = 1 TO 10 STEP 2\nNEXT i\nDO WHILE x\nLOOP\nDO\nLOOP UNTIL x\nWHILE x\nWEND",
        );
        assert!(matches!(&m.body[0], Stmt::For { step: Some(_), .. }));
        assert!(matches!(
            &m.body[1],
            Stmt::DoLoop {
                pre: Some((false, _)),
                post: None,
                ..
            }
        ));
        assert!(matches!(
            &m.body[2],
            Stmt::DoLoop {
                pre: None,
                post: Some((true, _)),
                ..
            }
        ));
        assert!(matches!(&m.body[3], Stmt::While { .. }));
    }

    #[test]
    fn prozeduren() {
        let m = parse_ok(
            "DECLARE SUB Foo (a%, b$)\nSUB Foo (a%, b$)\nPRINT a%; b$\nEND SUB\nFUNCTION Quad (x)\nQuad = x * x\nEND FUNCTION",
        );
        assert_eq!(m.procs.len(), 2);
        assert_eq!(m.procs[0].sig.params.len(), 2);
        assert!(matches!(m.procs[1].sig.kind, ProcKind::Function));
    }

    #[test]
    fn fehlerbehandlung() {
        let m = parse_ok(
            "ON ERROR GOTO Handler\nON LOCAL ERROR RESUME NEXT\nON ERROR GOTO 0\nHandler:\nRESUME NEXT",
        );
        assert!(matches!(
            &m.body[0],
            Stmt::OnError {
                local: false,
                action: OnErrorAction::Goto(_),
                ..
            }
        ));
        assert!(matches!(
            &m.body[1],
            Stmt::OnError {
                local: true,
                action: OnErrorAction::ResumeNext,
                ..
            }
        ));
        assert!(matches!(
            &m.body[2],
            Stmt::OnError {
                action: OnErrorAction::Disable,
                ..
            }
        ));
    }

    #[test]
    fn operator_prioritaet() {
        let m = parse_ok("x = 1 + 2 * 3 ^ 2");
        // 1 + (2 * (3 ^ 2))
        match &m.body[0] {
            Stmt::Assign {
                value:
                    Expr::Binary {
                        op: BinOp::Add,
                        rhs,
                        ..
                    },
                ..
            } => {
                assert!(matches!(**rhs, Expr::Binary { op: BinOp::Mul, .. }));
            }
            other => panic!("unerwartet: {other:?}"),
        }
    }

    #[test]
    fn typen_und_deklarationen() {
        let m = parse_ok(
            "TYPE Kunde\nName AS STRING * 30\nUmsatz AS DOUBLE\nEND TYPE\nDIM SHARED k AS Kunde, a(1 TO 10) AS INTEGER\nCONST PI = 3.14159\nDEFINT A-C, X",
        );
        assert!(matches!(&m.body[0], Stmt::TypeDecl { fields, .. } if fields.len() == 2));
        assert!(matches!(&m.body[1], Stmt::Dim { shared: true, decls, .. } if decls.len() == 2));
        assert!(matches!(&m.body[3], Stmt::DefType { ranges, .. } if ranges.len() == 2));
    }

    #[test]
    fn forms_syntax() {
        let m = parse_ok(
            "'$FORM\nDIM f AS FORM, c AS CONTROL\nIF TYPEOF c IS CommandButton THEN PRINT 1",
        );
        assert!(matches!(m.body[0], Stmt::MetaForm { .. }));
        assert!(matches!(&m.body[1], Stmt::Dim { decls, .. }
            if decls[0].as_type == Some(TypeName::Form) && decls[1].as_type == Some(TypeName::Control)));
        assert!(
            matches!(&m.body[2], Stmt::If { cond: Expr::TypeOf { class, .. }, .. }
            if class == "COMMANDBUTTON")
        );
    }
}
