use std::fmt;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Type {
    I32,
    I64,
    U32,
    U64,
    F64,
    Bool,
    Unit,
    String,
    Named(String),
    Ptr(Box<Type>),
    Array(Box<Type>, usize),
    Vec(Box<Type>),
    Slice(Box<Type>),
    Option(Box<Type>),
    Result(Box<Type>, Box<Type>),
}

impl Type {
    pub fn llvm(&self) -> &'static str {
        match self {
            Type::I32 => "i32",
            Type::I64 => "i64",
            Type::U32 => "i32",
            Type::U64 => "i64",
            Type::F64 => "double",
            Type::Bool => "i1",
            Type::Unit => "void",
            Type::String => "ptr",
            Type::Option(inner) if **inner == Type::I32 => "{ i1, i32 }",
            Type::Option(inner) if **inner == Type::I64 => "{ i1, i64 }",
            Type::Option(inner) if **inner == Type::U32 => "{ i1, i32 }",
            Type::Option(inner) if **inner == Type::U64 => "{ i1, i64 }",
            Type::Option(inner) if **inner == Type::F64 => "{ i1, double }",
            Type::Option(inner) if **inner == Type::Bool => "{ i1, i1 }",
            Type::Option(inner) if **inner == Type::String => "{ i1, ptr }",
            Type::Result(ok, err) if **ok == Type::I32 && **err == Type::I32 => "{ i1, i32 }",
            // exp-25 keeps `(result i64 i32)` as a single concrete family for
            // `std.string.parse_i64_result`; this is not a generic result ABI.
            Type::Result(ok, err) if **ok == Type::I64 && **err == Type::I32 => "{ i1, i64, i32 }",
            Type::Result(ok, err) if **ok == Type::U32 && **err == Type::I32 => "{ i1, i32 }",
            Type::Result(ok, err) if **ok == Type::U64 && **err == Type::I32 => "{ i1, i64, i32 }",
            // exp-28 keeps `(result f64 i32)` as a single concrete family for
            // `std.string.parse_f64_result`; this is not a generic result ABI.
            Type::Result(ok, err) if **ok == Type::F64 && **err == Type::I32 => {
                "{ i1, double, i32 }"
            }
            // exp-34 keeps `(result bool i32)` as a single concrete family for
            // `std.string.parse_bool_result`; this is not a generic result ABI.
            Type::Result(ok, err) if **ok == Type::Bool && **err == Type::I32 => "{ i1, i1, i32 }",
            Type::Result(ok, err) if **ok == Type::String && **err == Type::I32 => {
                "{ i1, ptr, i32 }"
            }
            _ => "ptr",
        }
    }
}

impl fmt::Display for Type {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Type::I32 => write!(f, "i32"),
            Type::I64 => write!(f, "i64"),
            Type::U32 => write!(f, "u32"),
            Type::U64 => write!(f, "u64"),
            Type::F64 => write!(f, "f64"),
            Type::Bool => write!(f, "bool"),
            Type::Unit => write!(f, "unit"),
            Type::String => write!(f, "string"),
            Type::Named(name) => write!(f, "{}", name),
            Type::Ptr(inner) => write!(f, "(ptr {})", inner),
            Type::Array(inner, n) => write!(f, "(array {} {})", inner, n),
            Type::Vec(inner) => write!(f, "(vec {})", inner),
            Type::Slice(inner) => write!(f, "(slice {})", inner),
            Type::Option(inner) => write!(f, "(option {})", inner),
            Type::Result(ok, err) => write!(f, "(result {} {})", ok, err),
        }
    }
}
