use crate::frontend::sema::Ty;
use crate::frontend::token::Span;
use std::fmt::Debug;

pub trait Phase: Debug + PartialEq + Eq {
    type ReturnType: Debug + PartialEq + Eq;
    type ParamType: Debug + PartialEq + Eq;
    type ExprType: Debug + PartialEq + Eq;
    type CastType: Debug + PartialEq + Eq;
}

#[derive(Debug, PartialEq, Eq)]
pub struct Untyped;

impl Phase for Untyped {
    type ReturnType = Option<Type>;
    type ParamType = FunctionParam;
    type ExprType = ();
    type CastType = Type;
}

#[derive(Debug, PartialEq, Eq)]
pub struct Typed;

impl Phase for Typed {
    type ReturnType = Ty;
    type ParamType = (String, Ty);
    type ExprType = Ty;
    type CastType = Ty;
}

pub type TypedModule = Module<Typed>;
pub type TypedDecl = Decl<Typed>;
pub type TypedDeclKind = DeclKind<Typed>;
pub type TypedStmt = Stmt<Typed>;
pub type TypedStmtKind = StmtKind<Typed>;
pub type TypedExpr = Expr<Typed>;
pub type TypedExprKind = ExprKind<Typed>;

#[derive(Debug, PartialEq)]
pub struct Module<P: Phase = Untyped> {
    pub decls: Vec<Decl<P>>,
}

#[derive(Debug, PartialEq)]
pub struct Decl<P: Phase = Untyped> {
    pub kind: DeclKind<P>,
    pub span: Span,
}

#[derive(Debug, PartialEq)]
pub enum DeclKind<P: Phase = Untyped> {
    Function {
        name: String,
        name_span: Span,
        params: Vec<P::ParamType>,
        return_type: P::ReturnType,
        body: Stmt<P>,
    },
    ForeignFunction {
        name: String,
        name_span: Span,
        params: Vec<P::ParamType>,
        return_type: P::ReturnType,
    },
    Struct {
        name: String,
        name_span: Span,
        fields: Vec<StructField>,
    },
}

#[derive(Debug, PartialEq, Eq)]
pub struct FunctionParam {
    pub name: String,
    pub name_span: Span,
    pub ty: Type,
}

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

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Type {
    pub kind: TypeKind,
    pub span: Span,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TypeKind {
    I8,
    I16,
    I32,
    I64,
    U8,
    U16,
    U32,
    U64,
    F32,
    F64,
    Bool,
    Pointer(Box<Type>),
    Struct(String),
    Array(Box<Type>, usize),
}

#[derive(Debug, PartialEq)]
pub struct Stmt<P: Phase = Untyped> {
    pub kind: StmtKind<P>,
    pub span: Span,
}

#[derive(Debug, PartialEq)]
pub enum StmtKind<P: Phase = Untyped> {
    Compound {
        inner: Vec<Stmt<P>>,
    },
    If {
        condition: Expr<P>,
        then: Box<Stmt<P>>,
        elze: Option<Box<Stmt<P>>>,
    },
    While {
        condition: Expr<P>,
        body: Box<Stmt<P>>,
    },
    Return {
        value: Option<Expr<P>>,
    },
    Let {
        name: String,
        name_span: Span,
        ty: P::ReturnType,
        initializer: Option<Expr<P>>,
    },
    Expression {
        expr: Expr<P>,
    },
    Break,
    Continue,
}

#[derive(Debug, PartialEq)]
pub struct Expr<P: Phase = Untyped> {
    pub kind: ExprKind<P>,
    pub ty: P::ExprType,
    pub span: Span,
}

#[derive(Debug, PartialEq)]
pub enum ExprKind<P: Phase = Untyped> {
    Identifier {
        name: String,
    },
    Integer {
        value: u64,
    },
    Float {
        value: f64,
    },
    Boolean {
        value: bool,
    },
    Unary {
        op: UnaryOp,
        expr: Box<Expr<P>>,
    },
    Binary {
        op: BinaryOp,
        lhs: Box<Expr<P>>,
        rhs: Box<Expr<P>>,
    },
    Assign {
        lval: Box<Expr<P>>,
        rval: Box<Expr<P>>,
    },
    Cast {
        expr: Box<Expr<P>>,
        ty: P::CastType,
    },
    Call {
        callee: Box<Expr<P>>,
        args: Vec<Expr<P>>,
    },
    Struct {
        name: String,
        name_span: Span,
        fields: Vec<FieldValue<P>>,
    },
    FieldAccess {
        expr: Box<Expr<P>>,
        field: String,
        field_span: Span,
    },
    Array {
        elements: Vec<Expr<P>>,
    },
    Index {
        array: Box<Expr<P>>,
        index: Box<Expr<P>>,
    },
}

#[derive(Debug, PartialEq)]
pub struct FieldValue<P: Phase = Untyped> {
    pub name: String,
    pub name_span: Span,
    pub value: Expr<P>,
}

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

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinaryOp {
    Add,
    Sub,
    Mul,
    Div,
    Rem,

    Eq,
    Neq,
    Lt,
    Le,
    Gt,
    Ge,
}
