//! Abstract syntax tree for IEC 61131-3 Structured Text (ST).
//!
//! This is the security-relevant subset: POUs with their variable declarations
//! and statement bodies, enough to run semantic control-logic rules over. It is
//! deliberately not a full language model — declarations we don't reason about
//! (e.g. exotic type definitions) are parsed loosely and kept as raw text.

/// A Program Organization Unit: a PROGRAM, FUNCTION, or FUNCTION_BLOCK.
#[derive(Debug, Clone)]
pub struct Pou {
    pub name: String,
    pub kind: PouKind,
    /// The declared variables, across all VAR_* sections.
    pub vars: Vec<VarDecl>,
    /// The statement body.
    pub body: Vec<Stmt>,
    /// 1-based line where the POU header appears (in the source that was parsed).
    pub line: u32,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PouKind {
    Program,
    Function,
    FunctionBlock,
}

impl PouKind {
    pub fn label(self) -> &'static str {
        match self {
            PouKind::Program => "PROGRAM",
            PouKind::Function => "FUNCTION",
            PouKind::FunctionBlock => "FUNCTION_BLOCK",
        }
    }
}

/// A single declared variable.
#[derive(Debug, Clone)]
pub struct VarDecl {
    pub name: String,
    pub section: VarSection,
    /// The declared type as written (e.g. `BOOL`, `INT`, `ARRAY[0..9] OF INT`).
    pub type_name: String,
    /// Whether the type is an ARRAY, and its declared bounds `(lo, hi)` when
    /// they are literal integers — used by the array-bounds rule.
    pub array_bounds: Option<(i64, i64)>,
    /// The initializer expression, if any (`:= <expr>`).
    pub init: Option<Expr>,
    pub line: u32,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VarSection {
    Var,
    Input,
    Output,
    InOut,
    Global,
    Temp,
    External,
}

/// A statement.
#[derive(Debug, Clone)]
pub enum Stmt {
    Assign {
        target: Expr,
        value: Expr,
        line: u32,
    },
    If {
        /// (condition, body) for IF and each ELSIF, in order.
        branches: Vec<(Expr, Vec<Stmt>)>,
        else_body: Option<Vec<Stmt>>,
        line: u32,
    },
    Case {
        selector: Expr,
        /// (label expressions, body) per CASE arm.
        arms: Vec<(Vec<Expr>, Vec<Stmt>)>,
        else_body: Option<Vec<Stmt>>,
        line: u32,
    },
    For {
        var: String,
        from: Expr,
        to: Expr,
        by: Option<Expr>,
        body: Vec<Stmt>,
        line: u32,
    },
    While {
        cond: Expr,
        body: Vec<Stmt>,
        line: u32,
    },
    Repeat {
        body: Vec<Stmt>,
        until: Expr,
        line: u32,
    },
    /// A bare call statement, e.g. `TON1(IN := x, PT := T#5s);`.
    Call {
        callee: String,
        args: Vec<CallArg>,
        line: u32,
    },
    Return {
        line: u32,
    },
    Exit {
        line: u32,
    },
    /// `JMP label;` — an unstructured jump.
    Jump {
        label: String,
        line: u32,
    },
    /// `label:` — a jump target.
    Label {
        name: String,
        line: u32,
    },
}

/// One argument in a call: positional (`name: None`) or named (`X := expr`).
#[derive(Debug, Clone)]
pub struct CallArg {
    pub name: Option<String>,
    pub value: Expr,
}

/// An expression.
#[derive(Debug, Clone)]
pub enum Expr {
    Int(i64, u32),
    Real(f64, u32),
    Bool(bool, u32),
    /// A string literal, with the unquoted contents.
    Str(String, u32),
    /// A duration / date / time literal, kept as raw text (`T#5s`, `DT#...`).
    Time(String, u32),
    Ident(String, u32),
    /// `base[index]`.
    Index {
        base: Box<Expr>,
        index: Box<Expr>,
        line: u32,
    },
    /// `base.field`.
    Member {
        base: Box<Expr>,
        field: String,
        line: u32,
    },
    Unary {
        op: UnOp,
        expr: Box<Expr>,
        line: u32,
    },
    Binary {
        op: BinOp,
        lhs: Box<Expr>,
        rhs: Box<Expr>,
        line: u32,
    },
    /// A function call used as an expression, e.g. `LIMIT(a, b, c)`.
    Call {
        callee: String,
        args: Vec<CallArg>,
        line: u32,
    },
}

impl Expr {
    /// The 1-based source line this expression starts on.
    pub fn line(&self) -> u32 {
        match self {
            Expr::Int(_, l)
            | Expr::Real(_, l)
            | Expr::Bool(_, l)
            | Expr::Str(_, l)
            | Expr::Time(_, l)
            | Expr::Ident(_, l)
            | Expr::Index { line: l, .. }
            | Expr::Member { line: l, .. }
            | Expr::Unary { line: l, .. }
            | Expr::Binary { line: l, .. }
            | Expr::Call { line: l, .. } => *l,
        }
    }

    /// If this expression is a plain identifier, its name.
    pub fn as_ident(&self) -> Option<&str> {
        match self {
            Expr::Ident(name, _) => Some(name.as_str()),
            _ => None,
        }
    }
}

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

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