use ecow::EcoString;
use num_bigint::BigInt;

#[derive(Debug, PartialEq, Eq, Default)]
pub struct Module {
    pub functions: Vec<Function>,
}

#[derive(Debug, PartialEq, Eq)]
pub struct Function {
    pub name: EcoString,
    pub return_type: Type,
    pub parameters: Vec<FunctionParameter>,
    pub body: Expression,
}

#[derive(Debug, PartialEq, Eq)]
pub struct FunctionParameter {
    pub type_: Type,
    pub name: Option<EcoString>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Type {
    Int,
    Float,
    Bool,
    String,
    Struct { elements: Vec<Type> },
    List(Box<Type>),
    Generic,
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Var {
    pub name: EcoString,
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Expression {
    Block(Vec<Expression>),

    FunctionRef {
        module: EcoString,
        name: EcoString,
        arity: usize,
    },

    Var(Var),

    Int {
        value: BigInt,
    },

    Float {
        value: EcoString,
    },

    Bool {
        value: bool,
    },

    String {
        value: EcoString,
    },

    Equals {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    NotEquals {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    IntGt {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    IntGtEq {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    IntLt {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    IntLtEq {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    IntAdd {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    IntSub {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    IntMul {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    IntDiv {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    IntRem {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    FloatGt {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    FloatGtEq {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    FloatLt {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    FloatLtEq {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    FloatAdd {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    FloatSub {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    FloatMul {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    FloatDiv {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    StringConcat {
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },

    List {
        items: Vec<Expression>,
        tail: Option<Box<Expression>>,
    },

    Struct {
        tag: Option<u32>,
        items: Vec<Expression>,
    },

    StructTag {
        value: Box<Expression>,
    },

    StructAccess {
        value: Box<Expression>,
        index: u64,
    },

    Set {
        name: Var,
        value: Box<Expression>,
    },

    If {
        cond: Box<Expression>,
        then: Box<Expression>,
        else_: Box<Expression>,
    },

    Call {
        target: Box<Expression>,
        args: Vec<Expression>,
    },
}
