use std::collections::{BTreeMap, HashMap};

use arrayvec::{ArrayString, ArrayVec};
use smallvec::SmallVec;

use super::{Fields, StructType, Typ};

pub trait GetType {
    const TYPE: Option<Typ> = None;

    #[inline]
    fn typ() -> Typ {
        Self::TYPE.unwrap()
    }
}

impl GetType for bool {
    const TYPE: Option<Typ> = Some(Typ::Bool);
}

impl GetType for u8 {
    const TYPE: Option<Typ> = Some(Typ::U8);
}

impl GetType for u16 {
    const TYPE: Option<Typ> = Some(Typ::U16);
}

impl GetType for u32 {
    const TYPE: Option<Typ> = Some(Typ::U32);
}

impl GetType for u64 {
    const TYPE: Option<Typ> = Some(Typ::U64);
}

impl GetType for usize {
    const TYPE: Option<Typ> = Some(Typ::U64);
}

impl GetType for i32 {
    const TYPE: Option<Typ> = Some(Typ::I32);
}

impl GetType for i64 {
    const TYPE: Option<Typ> = Some(Typ::I64);
}

impl GetType for f32 {
    const TYPE: Option<Typ> = Some(Typ::F32);
}

impl GetType for f64 {
    const TYPE: Option<Typ> = Some(Typ::F64);
}

impl GetType for String {
    const TYPE: Option<Typ> = Some(Typ::Str);
}

impl GetType for time::Duration {
    const TYPE: Option<Typ> = Some(Typ::I64);
}

impl GetType for time::OffsetDateTime {
    const TYPE: Option<Typ> = Some(Typ::Datetime);
}

impl GetType for time::PrimitiveDateTime {
    const TYPE: Option<Typ> = Some(Typ::Datetime);
}

impl GetType for time::Date {
    const TYPE: Option<Typ> = Some(Typ::I32);
}

impl GetType for time::Time {
    const TYPE: Option<Typ> = Some(Typ::U32);
}

impl<T: GetType> GetType for Vec<T> {
    #[inline]
    fn typ() -> Typ {
        let inner = T::TYPE.unwrap_or_else(T::typ);
        Typ::Vec(Box::new(inner))
    }
}

impl<T: GetType, const CAP: usize> GetType for ArrayVec<T, CAP> {
    #[inline]
    fn typ() -> Typ {
        let inner = T::TYPE.unwrap_or_else(T::typ);
        Typ::Vec(Box::new(inner))
    }
}

impl<const CAP: usize> GetType for ArrayString<CAP> {
    const TYPE: Option<Typ> = Some(Typ::Str);
}

impl<T: GetType, const CAP: usize> GetType for SmallVec<[T; CAP]> {
    #[inline]
    fn typ() -> Typ {
        let inner = T::TYPE.unwrap_or_else(T::typ);
        Typ::Vec(Box::new(inner))
    }
}

impl<T: GetType> GetType for Option<T> {
    #[inline]
    fn typ() -> Typ {
        let inner = T::TYPE.unwrap_or_else(T::typ);
        Typ::Optional(Box::new(inner))
    }
}

impl<T: GetType> GetType for Box<T> {
    #[inline]
    fn typ() -> Typ {
        T::TYPE.unwrap_or_else(T::typ)
    }
}

impl<T1: GetType, T2: GetType> GetType for (T1, T2) {
    #[inline]
    fn typ() -> Typ {
        let t1 = T1::TYPE.unwrap_or_else(T1::typ);
        let t2 = T2::TYPE.unwrap_or_else(T2::typ);
        let fields = vec![t1, t2];
        Typ::Struct(StructType {
            name: String::new(),
            fields: Fields::Unnamed(fields),
        })
    }
}

impl<T1: GetType, T2: GetType, T3: GetType> GetType for (T1, T2, T3) {
    #[inline]
    fn typ() -> Typ {
        let t1 = T1::TYPE.unwrap_or_else(T1::typ);
        let t2 = T2::TYPE.unwrap_or_else(T2::typ);
        let t3 = T3::TYPE.unwrap_or_else(T3::typ);
        let fields = vec![t1, t2, t3];
        Typ::Struct(StructType {
            name: String::new(),
            fields: Fields::Unnamed(fields),
        })
    }
}

impl<T1: GetType, T2: GetType, T3: GetType, T4: GetType> GetType for (T1, T2, T3, T4) {
    #[inline]
    fn typ() -> Typ {
        let t1 = T1::TYPE.unwrap_or_else(T1::typ);
        let t2 = T2::TYPE.unwrap_or_else(T2::typ);
        let t3 = T3::TYPE.unwrap_or_else(T3::typ);
        let t4 = T4::TYPE.unwrap_or_else(T4::typ);
        let fields = vec![t1, t2, t3, t4];
        Typ::Struct(StructType {
            name: String::new(),
            fields: Fields::Unnamed(fields),
        })
    }
}

#[inline]
fn get_map_typ<K: GetType, V: GetType>() -> Typ {
    let tuple = <(K, V)>::TYPE.unwrap_or_else(<(K, V)>::typ);
    let arr = Box::new(tuple);
    Typ::Vec(arr)
}

impl<K: GetType, V: GetType> GetType for HashMap<K, V> {
    #[inline]
    fn typ() -> Typ {
        get_map_typ::<K, V>()
    }
}

impl<K: GetType, V: GetType> GetType for BTreeMap<K, V> {
    #[inline]
    fn typ() -> Typ {
        get_map_typ::<K, V>()
    }
}

impl<const CAP: usize> GetType for [u8; CAP] {
    const TYPE: Option<Typ> = Some(Typ::ArrayBytes(CAP as u32));
}

// impl<const CAP: usize, T: GetType> GetType for [T; CAP] {
//     #[inline]
//     fn typ() -> Typ {
//         let inner = T::TYPE.unwrap_or_else(T::typ);
//         if matches!(inner, Typ::U8) {
//             Typ::ArrayBytes(CAP as u32)
//         } else {
//             Typ::Array(CAP as u32, Box::new(inner))
//         }
//     }
// }

// TODO: HashSet...
