use crate::{
    WasmValue,
    value::ValueEnum,
    wasm::{WasmType, WasmTypeKind},
};

use super::{Type, Value};

pub fn from_wasm_type(ty: &impl WasmType) -> Option<Type> {
    if let Some(ty) = Type::simple(ty.kind()) {
        return Some(ty);
    }
    Some(match ty.kind() {
        WasmTypeKind::List => Type::list(from_wasm_type(&ty.list_element_type()?)?),
        WasmTypeKind::Record => Type::record(
            ty.record_fields()
                .map(|(name, ty)| Some((name, from_wasm_type(&ty)?)))
                .collect::<Option<Vec<_>>>()?,
        )?,
        WasmTypeKind::Tuple => Type::tuple(
            ty.tuple_element_types()
                .map(|ty| from_wasm_type(&ty))
                .collect::<Option<Vec<_>>>()?,
        )?,
        WasmTypeKind::Variant => Type::variant(
            ty.variant_cases()
                .map(|(name, payload)| Some((name, from_optional_wasm_type(payload)?)))
                .collect::<Option<Vec<_>>>()?,
        )?,
        WasmTypeKind::Enum => Type::enum_ty(ty.enum_cases())?,
        WasmTypeKind::Option => Type::option(from_wasm_type(&ty.option_some_type()?)?),
        WasmTypeKind::Result => {
            let (ok, err) = ty.result_types()?;
            Type::result(from_optional_wasm_type(ok)?, from_optional_wasm_type(err)?)
        }
        WasmTypeKind::Flags => Type::flags(ty.flags_names())?,
        _ => return None,
    })
}

fn from_optional_wasm_type(ty: Option<impl WasmType>) -> Option<Option<Type>> {
    Some(match ty {
        Some(ty) => Some(from_wasm_type(&ty)?),
        None => None,
    })
}

trait ValueTyped {
    fn value_type() -> Type;
}

macro_rules! impl_primitives {
    ($Self:ty, $(($case:ident, $ty:ty)),*) => {
        $(
            impl ValueTyped for $ty {
                fn value_type() -> Type {
                    Type::must_simple(WasmTypeKind::$case)
                }
            }

            impl From<$ty> for $Self {
                fn from(value: $ty) -> Self {
                    Self(ValueEnum::$case(value))
                }
            }
        )*
    };
}

impl_primitives!(
    Value,
    (Bool, bool),
    (S8, i8),
    (S16, i16),
    (S32, i32),
    (S64, i64),
    (U8, u8),
    (U16, u16),
    (U32, u32),
    (U64, u64),
    (F32, f32),
    (F64, f64),
    (Char, char)
);

impl ValueTyped for String {
    fn value_type() -> Type {
        Type::STRING
    }
}

impl From<String> for Value {
    fn from(value: String) -> Self {
        Self(ValueEnum::String(value.into()))
    }
}

impl ValueTyped for &str {
    fn value_type() -> Type {
        String::value_type()
    }
}

impl<'a> From<&'a str> for Value {
    fn from(value: &'a str) -> Self {
        value.to_string().into()
    }
}

impl<const N: usize, T: ValueTyped> ValueTyped for [T; N] {
    fn value_type() -> Type {
        Type::list(T::value_type())
    }
}

impl<const N: usize, T: ValueTyped + Into<Value>> From<[T; N]> for Value {
    fn from(values: [T; N]) -> Self {
        let ty = Vec::<T>::value_type();
        let values = values.into_iter().map(Into::into);
        Value::make_list(&ty, values).unwrap()
    }
}

impl<T: ValueTyped> ValueTyped for Vec<T> {
    fn value_type() -> Type {
        Type::list(T::value_type())
    }
}

impl<T: ValueTyped + Into<Value>> From<Vec<T>> for Value {
    fn from(values: Vec<T>) -> Self {
        let ty = Vec::<T>::value_type();
        let values = values.into_iter().map(Into::into);
        Value::make_list(&ty, values).unwrap()
    }
}

impl<T: ValueTyped> ValueTyped for Option<T> {
    fn value_type() -> Type {
        Type::option(T::value_type())
    }
}

impl<T: ValueTyped + Into<Value>> From<Option<T>> for Value {
    fn from(value: Option<T>) -> Self {
        let ty = Option::<T>::value_type();
        Value::make_option(&ty, value.map(Into::into)).unwrap()
    }
}

impl<T: ValueTyped, U: ValueTyped> ValueTyped for Result<T, U> {
    fn value_type() -> Type {
        Type::result(Some(T::value_type()), Some(U::value_type()))
    }
}

impl<T: ValueTyped + Into<Value>, U: ValueTyped + Into<Value>> From<Result<T, U>> for Value {
    fn from(value: Result<T, U>) -> Self {
        let ty = Result::<T, U>::value_type();
        let value = match value {
            Ok(ok) => Ok(Some(ok.into())),
            Err(err) => Err(Some(err.into())),
        };
        Value::make_result(&ty, value).unwrap()
    }
}

macro_rules! impl_tuple {
    ($(($($var:ident),*)),*) => {
        $(
            impl<$($var: ValueTyped),*> ValueTyped for ($($var),*,) {
                fn value_type() -> Type {
                    Type::tuple(vec![$($var::value_type()),*]).unwrap()
                }
            }

            #[allow(non_snake_case)]
            impl<$($var: ValueTyped + Into<Value>),*> From<($($var),*,)> for Value {
                fn from(($($var),*,): ($($var),*,)) -> Value {
                    let ty = <($($var),*,)>::value_type();
                    $(
                        let $var = $var.into();
                    )*
                    Value::make_tuple(&ty, vec![$($var),*]).unwrap()
                }
            }

        )*
    };
}

impl_tuple!(
    (T1),
    (T1, T2),
    (T1, T2, T3),
    (T1, T2, T3, T4),
    (T1, T2, T3, T4, T5),
    (T1, T2, T3, T4, T5, T6),
    (T1, T2, T3, T4, T5, T6, T7),
    (T1, T2, T3, T4, T5, T6, T7, T8),
    (T1, T2, T3, T4, T5, T6, T7, T8, T9),
    (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10),
    (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11),
    (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12),
    (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13),
    (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14),
    (
        T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15
    ),
    (
        T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16
    )
);

#[cfg(test)]
mod tests {
    use crate::value::{Type, Value};

    #[test]
    fn type_conversion_round_trips() {
        for ty in [
            Type::BOOL,
            Type::U8,
            Type::F32,
            Type::STRING,
            Type::list(Type::BOOL),
            Type::record([("a", Type::BOOL)]).unwrap(),
            Type::tuple([Type::BOOL]).unwrap(),
            Type::variant([("a", None), ("b", Some(Type::BOOL))]).unwrap(),
            Type::enum_ty(["north", "south"]).unwrap(),
            Type::option(Type::BOOL),
            Type::result(Some(Type::BOOL), None),
            Type::flags(["read", "write"]).unwrap(),
        ] {
            let got = Type::from_wasm_type(&ty).unwrap();
            assert_eq!(got, ty);
        }
    }

    #[test]
    fn value_conversions() {
        for (val, expect) in [
            (1u8.into(), "1"),
            ((-123i8).into(), "-123"),
            (f32::NAN.into(), "nan"),
            (f64::NEG_INFINITY.into(), "-inf"),
            ('x'.into(), "'x'"),
            ("str".into(), "\"str\""),
            (vec![1, 2, 3].into(), "[1, 2, 3]"),
            ([1, 2, 3].into(), "[1, 2, 3]"),
            (['a'; 3].into(), "['a', 'a', 'a']"),
            (Some(1).into(), "some(1)"),
            (None::<u8>.into(), "none"),
            (Ok::<u8, String>(1).into(), "ok(1)"),
            (Err::<u8, String>("oops".into()).into(), "err(\"oops\")"),
            ((1,).into(), "(1)"),
            ((1, "str", [9; 2]).into(), "(1, \"str\", [9, 9])"),
        ] {
            let val: Value = val;
            let got = crate::to_string(&val).unwrap();
            assert_eq!(got, expect);
        }
    }
}
