//! AST-Definitionen: Module, Prozeduren, Anweisungen, Ausdrücke,
//! Deklarationen und benutzerdefinierte Typen.

use crate::lexer::Suffix;
use crate::SourcePos;

/// Typangabe in `AS`-Klauseln.
#[derive(Debug, Clone, PartialEq)]
pub enum TypeName {
    Integer,
    Long,
    Single,
    Double,
    Currency,
    Str,
    FixedStr(i64),
    Form,
    Control,
    Udt(String),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnOp {
    Neg,
    Not,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinOp {
    Pow,
    Mul,
    Div,
    IntDiv,
    Mod,
    Add,
    Sub,
    Eq,
    Ne,
    Lt,
    Le,
    Gt,
    Ge,
    And,
    Or,
    Xor,
    Eqv,
    Imp,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
    IntLit(i16, SourcePos),
    LongLit(i32, SourcePos),
    SingleLit(f32, SourcePos),
    DoubleLit(f64, SourcePos),
    CurrencyLit(i64, SourcePos),
    StrLit(String, SourcePos),
    /// Benannter Zugriff: Variable, Arrayelement, Funktionsaufruf oder
    /// Konstante — Auflösung erfolgt in der Semantik.
    Name {
        name: String,
        suffix: Option<Suffix>,
        args: Option<Vec<Expr>>,
        pos: SourcePos,
    },
    Unary {
        op: UnOp,
        operand: Box<Expr>,
        pos: SourcePos,
    },
    Binary {
        op: BinOp,
        lhs: Box<Expr>,
        rhs: Box<Expr>,
        pos: SourcePos,
    },
    /// Geklammerter Ausdruck. Semantisch transparent, aber als Argument
    /// erzwingt die Klammer Wertübergabe (BYVAL) statt BYREF.
    Paren(Box<Expr>),
    /// `TYPEOF ausdruck IS klasse`.
    TypeOf {
        value: Box<Expr>,
        class: String,
        pos: SourcePos,
    },
    /// Ausgelassenes Argument (`LOCATE , 5`).
    Missing,
}

impl Expr {
    pub fn pos(&self) -> SourcePos {
        match self {
            Expr::IntLit(_, pos)
            | Expr::LongLit(_, pos)
            | Expr::SingleLit(_, pos)
            | Expr::DoubleLit(_, pos)
            | Expr::CurrencyLit(_, pos)
            | Expr::StrLit(_, pos)
            | Expr::Name { pos, .. }
            | Expr::Unary { pos, .. }
            | Expr::Binary { pos, .. }
            | Expr::TypeOf { pos, .. } => *pos,
            Expr::Paren(e) => e.pos(),
            _ => SourcePos::default(),
        }
    }
}

/// Sprungziel: alphanumerisches Label oder Zeilennummer.
#[derive(Debug, Clone, PartialEq)]
pub enum LabelRef {
    Name(String),
    Line(u32),
}

#[derive(Debug, Clone, PartialEq)]
pub enum PrintItem {
    Expr(Expr),
    Comma,
    Semicolon,
}

#[derive(Debug, Clone, PartialEq)]
pub enum CaseSpec {
    Expr(Expr),
    Range(Expr, Expr),
    Is(BinOp, Expr),
}

#[derive(Debug, Clone, PartialEq)]
pub struct CaseArm {
    /// Leer = `CASE ELSE`.
    pub specs: Vec<CaseSpec>,
    pub body: Vec<Stmt>,
    pub pos: SourcePos,
}

#[derive(Debug, Clone, PartialEq)]
pub struct VarDecl {
    pub name: String,
    pub suffix: Option<Suffix>,
    /// `None` = Skalar; leerer Vec = Array ohne Dimensionsangabe (`a()`).
    pub dims: Option<Vec<(Option<Expr>, Expr)>>,
    pub as_type: Option<TypeName>,
    pub pos: SourcePos,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExitKind {
    For,
    Do,
    Sub,
    Function,
    Def,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OptionKind {
    Explicit,
    Base(u8),
}

#[derive(Debug, Clone, PartialEq)]
pub enum OnErrorAction {
    Goto(LabelRef),
    ResumeNext,
    Disable, // GOTO 0
}

#[derive(Debug, Clone, PartialEq)]
pub enum ResumeKind {
    Retry, // RESUME [0]
    Next,
    Label(LabelRef),
}

#[derive(Debug, Clone, PartialEq)]
pub struct Param {
    pub name: String,
    pub suffix: Option<Suffix>,
    pub array: bool,
    pub as_type: Option<TypeName>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcKind {
    Sub,
    Function,
}

#[derive(Debug, Clone, PartialEq)]
pub struct ProcSig {
    pub kind: ProcKind,
    pub name: String,
    pub suffix: Option<Suffix>,
    pub params: Vec<Param>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Proc {
    pub sig: ProcSig,
    pub is_static: bool,
    pub body: Vec<Stmt>,
    pub pos: SourcePos,
    pub end_pos: SourcePos,
}

/// `OPEN … FOR modus`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpenMode {
    Input,
    Output,
    Append,
    Random,
    Binary,
    Isam,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccessMode {
    Read,
    Write,
    ReadWrite,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LockClause {
    Shared,
    LockRead,
    LockWrite,
    LockReadWrite,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EventAction {
    On,
    Off,
    Stop,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Stmt {
    Label(String),
    LineNumber(u32, SourcePos),
    Assign {
        target: Expr,
        value: Expr,
        pos: SourcePos,
    },
    Print {
        /// LPRINT (Druckerausgabe) statt PRINT.
        printer: bool,
        file: Option<Expr>,
        using: Option<Expr>,
        items: Vec<PrintItem>,
        pos: SourcePos,
    },
    Input {
        line: bool,
        file: Option<Expr>,
        keep_cursor: bool,
        prompt: Option<(String, bool)>, // (Text, mit Fragezeichen)
        vars: Vec<Expr>,
        pos: SourcePos,
    },
    If {
        cond: Expr,
        then_body: Vec<Stmt>,
        elseifs: Vec<(Expr, Vec<Stmt>, SourcePos)>,
        else_body: Option<Vec<Stmt>>,
        pos: SourcePos,
    },
    Select {
        expr: Expr,
        arms: Vec<CaseArm>,
        pos: SourcePos,
    },
    For {
        var: Expr,
        from: Expr,
        to: Expr,
        step: Option<Expr>,
        body: Vec<Stmt>,
        pos: SourcePos,
        end_pos: SourcePos,
    },
    DoLoop {
        pre: Option<(bool, Expr)>, // (ist UNTIL, Bedingung)
        post: Option<(bool, Expr)>,
        body: Vec<Stmt>,
        pos: SourcePos,
        end_pos: SourcePos,
    },
    While {
        cond: Expr,
        body: Vec<Stmt>,
        pos: SourcePos,
        end_pos: SourcePos,
    },
    Goto {
        target: LabelRef,
        pos: SourcePos,
    },
    Gosub {
        target: LabelRef,
        pos: SourcePos,
    },
    OnGoto {
        expr: Expr,
        targets: Vec<LabelRef>,
        gosub: bool,
        pos: SourcePos,
    },
    Return {
        target: Option<LabelRef>,
        pos: SourcePos,
    },
    End(SourcePos),
    StopStmt(SourcePos),
    System(SourcePos),
    Exit {
        kind: ExitKind,
        pos: SourcePos,
    },
    Dim {
        shared: bool,
        redim: bool,
        decls: Vec<VarDecl>,
        pos: SourcePos,
    },
    /// `SHARED`-Anweisung in einer Prozedur (Zugriff auf Modulvariablen).
    SharedDecl {
        decls: Vec<VarDecl>,
        pos: SourcePos,
    },
    /// `STATIC`-Anweisung in einer Prozedur.
    StaticDecl {
        decls: Vec<VarDecl>,
        pos: SourcePos,
    },
    /// `COMMON [SHARED] [/block/] liste`.
    CommonDecl {
        shared: bool,
        block: Option<String>,
        decls: Vec<VarDecl>,
        pos: SourcePos,
    },
    Erase {
        names: Vec<Expr>,
        pos: SourcePos,
    },
    ConstDecl {
        items: Vec<(String, Option<Suffix>, Expr)>,
        pos: SourcePos,
    },
    DefType {
        ty: TypeName,
        ranges: Vec<(char, char)>,
        pos: SourcePos,
    },
    OptionStmt {
        kind: OptionKind,
        pos: SourcePos,
    },
    TypeDecl {
        name: String,
        fields: Vec<(String, TypeName)>,
        pos: SourcePos,
    },
    Declare {
        sig: ProcSig,
        pos: SourcePos,
    },
    /// Expliziter oder impliziter Prozedur-/Builtin-Aufruf als Anweisung.
    Call {
        name: String,
        suffix: Option<Suffix>,
        args: Vec<Expr>,
        pos: SourcePos,
    },
    OnError {
        local: bool,
        action: OnErrorAction,
        pos: SourcePos,
    },
    Resume {
        kind: ResumeKind,
        pos: SourcePos,
    },
    ErrorStmt {
        code: Expr,
        pos: SourcePos,
    },
    Data {
        items: Vec<String>,
        pos: SourcePos,
    },
    ReadStmt {
        vars: Vec<Expr>,
        pos: SourcePos,
    },
    Restore {
        target: Option<LabelRef>,
        pos: SourcePos,
    },
    /// Einzeilige `DEF FNname(...) = ausdruck`-Definition.
    DefFn {
        name: String,
        suffix: Option<Suffix>,
        params: Vec<Param>,
        body: Expr,
        pos: SourcePos,
    },
    /// Blockform `DEF FNname(...) … END DEF`.
    DefFnBlock {
        name: String,
        suffix: Option<Suffix>,
        params: Vec<Param>,
        body: Vec<Stmt>,
        pos: SourcePos,
    },
    // ---- Datei-E/A (Laufzeit in Phase 3, Grammatik vollständig) ----
    Open {
        file: Expr,
        mode: Option<OpenMode>,
        /// Bei `FOR ISAM typname tabellenname`.
        isam: Option<(String, String)>,
        access: Option<AccessMode>,
        lock: Option<LockClause>,
        number: Expr,
        len: Option<Expr>,
        pos: SourcePos,
    },
    /// Alte Syntax `OPEN modus$, [#]n, datei$ [, reclen]`.
    OpenLegacy {
        mode: Expr,
        number: Expr,
        file: Expr,
        len: Option<Expr>,
        pos: SourcePos,
    },
    CloseStmt {
        files: Vec<Expr>,
        pos: SourcePos,
    },
    FieldStmt {
        file: Expr,
        fields: Vec<(Expr, Expr)>,
        pos: SourcePos,
    },
    GetPut {
        put: bool,
        file: Expr,
        recnum: Option<Expr>,
        var: Option<Expr>,
        pos: SourcePos,
    },
    LsetRset {
        rset: bool,
        target: Expr,
        value: Expr,
        pos: SourcePos,
    },
    WriteStmt {
        file: Option<Expr>,
        items: Vec<Expr>,
        pos: SourcePos,
    },
    SeekStmt {
        file: Expr,
        position: Expr,
        pos: SourcePos,
    },
    LockStmt {
        unlock: bool,
        file: Expr,
        from: Option<Expr>,
        to: Option<Expr>,
        pos: SourcePos,
    },
    NameStmt {
        old: Expr,
        new: Expr,
        pos: SourcePos,
    },
    // ---- Bildschirm/Ereignisse ----
    /// `VIEW PRINT [oben TO unten]`.
    ViewPrint {
        top: Option<Expr>,
        bottom: Option<Expr>,
        pos: SourcePos,
    },
    GraphicsLine {
        from: Option<(Expr, Expr)>,
        to: (Expr, Expr),
        relative: bool,
        color: Option<Expr>,
        fill: u8,
        pos: SourcePos,
    },
    GraphicsPaint {
        point: (Expr, Expr),
        paint: Option<Expr>,
        border: Option<Expr>,
        pos: SourcePos,
    },
    GraphicsView {
        rect: Option<(Expr, Expr, Expr, Expr)>,
        fill: Option<Expr>,
        border: Option<Expr>,
        pos: SourcePos,
    },
    /// `ON TIMER(n&) GOSUB ziel`, `ON KEY(n%) GOSUB ziel`,
    /// `ON UEVENT GOSUB ziel`, `ON SIGNAL(n%) GOSUB ziel` — die
    /// klassischen Ereignis-Traps (Sprachreferenz §8). `GOSUB 0` schaltet
    /// den Trap ab.
    TrapDef {
        device: String,
        index: Option<Expr>,
        target: LabelRef,
        pos: SourcePos,
    },
    /// `TIMER ON`, `KEY(5) OFF`, `UEVENT STOP` …
    EventControl {
        device: String,
        index: Option<Expr>,
        action: EventAction,
        pos: SourcePos,
    },
    // ---- Metabefehle ----
    /// `'$INCLUDE: 'datei''` — Auflösung übernimmt der Compile-Treiber.
    Include {
        path: String,
        pos: SourcePos,
    },
    /// `'$STATIC` / `'$DYNAMIC`.
    MetaArrays {
        static_arrays: bool,
        pos: SourcePos,
    },
    MetaForm {
        pos: SourcePos,
    },
}

#[derive(Debug, Clone, PartialEq)]
pub struct Module {
    pub name: String,
    pub body: Vec<Stmt>,
    pub procs: Vec<Proc>,
}

/// Sentinel-Kennung für `ROLLBACK ALL`. Echte Sicherungspunkte zählen ab 1,
/// `0` bedeutet „letzter Sicherungspunkt bzw. Transaktionsbeginn" — der
/// negative Wert kann deshalb mit keiner Kennung kollidieren.
pub const ROLLBACK_ALL: i32 = -1;

/// ISAM-Anweisungen: Namen, die der Parser als Anweisung mit optionalem
/// `#` vor der Dateinummer liest. Die zugehörigen Funktionen (`GETINDEX$`,
/// `SAVEPOINT`, `SETMEM`, `BOF`) stehen nicht hier — sie sind gewöhnliche
/// Ausdrücke.
pub fn ist_isam_anweisung(name: &str) -> bool {
    matches!(
        name,
        "CREATEINDEX"
            | "DELETEINDEX"
            | "SETINDEX"
            | "INSERT"
            | "RETRIEVE"
            | "UPDATE"
            | "DELETE"
            | "DELETETABLE"
            | "MOVEFIRST"
            | "MOVELAST"
            | "MOVENEXT"
            | "MOVEPREVIOUS"
            | "SEEKEQ"
            | "SEEKGT"
            | "SEEKGE"
            | "BEGINTRANS"
            | "COMMITTRANS"
            | "ROLLBACK"
    )
}
