use crate::{token::Span, types::Type};

#[derive(Debug, Clone)]
pub struct Program {
    pub module: String,
    pub type_aliases: Vec<TypeAliasDecl>,
    pub enums: Vec<EnumDecl>,
    pub structs: Vec<StructDecl>,
    pub c_imports: Vec<CImportDecl>,
    pub functions: Vec<Function>,
    pub tests: Vec<Test>,
}

#[derive(Debug, Clone)]
pub struct TypeAliasDecl {
    pub name: String,
    pub name_span: Span,
    pub target: Type,
    pub target_span: Span,
    pub span: Span,
}

#[derive(Debug, Clone)]
pub struct EnumDecl {
    pub name: String,
    pub name_span: Span,
    pub variants: Vec<EnumVariantDecl>,
    pub span: Span,
}

#[derive(Debug, Clone)]
pub struct EnumVariantDecl {
    pub name: String,
    pub name_span: Span,
    pub payload_ty: Option<Type>,
    pub payload_ty_span: Option<Span>,
}

#[derive(Debug, Clone)]
pub struct StructDecl {
    pub name: String,
    pub name_span: Span,
    pub fields: Vec<StructField>,
    pub span: Span,
}

#[derive(Debug, Clone)]
pub struct StructField {
    pub name: String,
    pub name_span: Span,
    pub ty: Type,
    pub ty_span: Span,
}

#[derive(Debug, Clone)]
pub struct CImportDecl {
    pub name: String,
    pub name_span: Span,
    pub params: Vec<Param>,
    pub return_type: Type,
    pub return_type_span: Span,
    pub span: Span,
}

#[derive(Debug, Clone)]
pub struct Function {
    pub name: String,
    pub params: Vec<Param>,
    pub return_type: Type,
    pub return_type_span: Span,
    pub body: Vec<Expr>,
    pub span: Span,
}

#[derive(Debug, Clone)]
pub struct Test {
    pub name: String,
    pub name_span: Span,
    pub body: Vec<Expr>,
    pub span: Span,
}

#[derive(Debug, Clone)]
pub struct Param {
    pub name: String,
    pub name_span: Span,
    pub ty: Type,
    pub ty_span: Span,
}

#[derive(Debug, Clone)]
pub struct Expr {
    pub kind: ExprKind,
    pub span: Span,
}

#[derive(Debug, Clone)]
pub enum ExprKind {
    Int(i32),
    Int64(i64),
    UInt32(u32),
    UInt64(u64),
    Float(f64),
    Bool(bool),
    String(String),
    Var(String),
    StructInit {
        name: String,
        name_span: Span,
        fields: Vec<StructInitField>,
    },
    ArrayInit {
        elem_ty: Type,
        elem_ty_span: Span,
        elements: Vec<Expr>,
    },
    OptionSome {
        payload_ty: Type,
        payload_ty_span: Span,
        value: Box<Expr>,
    },
    OptionNone {
        payload_ty: Type,
        payload_ty_span: Span,
    },
    ResultOk {
        ok_ty: Type,
        ok_ty_span: Span,
        err_ty: Type,
        err_ty_span: Span,
        value: Box<Expr>,
    },
    ResultErr {
        ok_ty: Type,
        ok_ty_span: Span,
        err_ty: Type,
        err_ty_span: Span,
        value: Box<Expr>,
    },
    OptionIsSome {
        value: Box<Expr>,
    },
    OptionIsNone {
        value: Box<Expr>,
    },
    OptionUnwrapSome {
        value: Box<Expr>,
    },
    ResultIsOk {
        source_name: String,
        value: Box<Expr>,
    },
    ResultIsErr {
        source_name: String,
        value: Box<Expr>,
    },
    ResultUnwrapOk {
        source_name: String,
        value: Box<Expr>,
    },
    ResultUnwrapErr {
        source_name: String,
        value: Box<Expr>,
    },
    EnumVariant {
        enum_name: String,
        variant: String,
        name_span: Span,
        args: Vec<Expr>,
    },
    FieldAccess {
        value: Box<Expr>,
        field: String,
        field_span: Span,
    },
    Index {
        array: Box<Expr>,
        index: Box<Expr>,
    },
    Local {
        mutable: bool,
        name: String,
        name_span: Span,
        ty: Type,
        expr: Box<Expr>,
    },
    Set {
        name: String,
        name_span: Span,
        expr: Box<Expr>,
    },
    Binary {
        op: BinaryOp,
        left: Box<Expr>,
        right: Box<Expr>,
    },
    If {
        condition: Box<Expr>,
        then_expr: Box<Expr>,
        else_expr: Box<Expr>,
    },
    Match {
        subject: Box<Expr>,
        arms: Vec<MatchArm>,
    },
    While {
        condition: Box<Expr>,
        body: Vec<Expr>,
    },
    Unsafe {
        body: Vec<Expr>,
    },
    Call {
        name: String,
        name_span: Span,
        args: Vec<Expr>,
    },
}

#[derive(Debug, Clone)]
pub struct MatchArm {
    pub pattern: MatchPattern,
    pub body: Vec<Expr>,
    pub span: Span,
}

#[derive(Debug, Clone)]
pub struct MatchPattern {
    pub kind: MatchPatternKind,
    pub binding: Option<String>,
    pub binding_span: Option<Span>,
    pub span: Span,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MatchPatternKind {
    Some,
    None,
    Ok,
    Err,
    EnumVariant { enum_name: String, variant: String },
}

#[derive(Debug, Clone)]
pub struct StructInitField {
    pub name: String,
    pub name_span: Span,
    pub expr: Expr,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum BinaryOp {
    Add,
    Sub,
    Mul,
    Div,
    Rem,
    BitAnd,
    BitOr,
    BitXor,
    Eq,
    Lt,
    Gt,
    Le,
    Ge,
}
