//! Type extraction implementations for `DataValue`.
//!
//! This module provides the `Extract` trait implementations for various Rust types,
//! enabling conversion from `DataValue` to native types with sensible defaults
//! for incompatible conversions.
//!
//! # Examples
//!
//! ```rust
//! use trs_data_value::{DataValue, Extract};
//!
//! let value = DataValue::U32(42);
//! let num: u32 = value.extract();
//! assert_eq!(num, 42);
//!
//! // Incompatible conversion returns default/zero
//! let text = DataValue::String("hello".into());
//! let num: u32 = text.extract();
//! assert_eq!(num, 0);
//! ```

use super::{DataValue, Extract};

macro_rules! impl_extract_nums {
    ($($t:ty),*) => {
        $(
        impl Extract for $t {
            #[inline]
            fn extract(v: &DataValue) -> $t {
                use DataValue::*;
                match v {
                    Bool(b) => (if b == &true { 1 } else { 0 })  as $t,
                    U32(i) => *i as $t,
                    I32(i) => *i as $t,
                    U8(i) => *i as $t,
                    U64(i) => *i as $t,
                    I64(i) => *i as $t,
                    F32(i) => *i as $t,
                    F64(i) => *i as $t,
                    U128(i) => *i as $t,
                    I128(i) => *i as $t,
                    EnumNumber(i) => *i as $t,
                    _ => 0 as $t,
                }
            }
        }
    )*
    };
}

impl_extract_nums!(u32, u64, i32, i64, f32, f64, u128, i128);

/// Extract implementation for `u8`.
///
/// Returns the contained `u8` value, or 0 for incompatible types.
impl Extract for u8 {
    fn extract(v: &DataValue) -> u8 {
        use DataValue::*;
        match v {
            U8(i) => *i,
            _ => 0,
        }
    }
}

/// Extract implementation for `bool`.
///
/// Converts numeric values to boolean (non-zero is true).
/// Returns false for incompatible types.
impl Extract for bool {
    fn extract(v: &DataValue) -> bool {
        match v {
            DataValue::Bool(i) => *i,
            DataValue::U8(i) => *i != 0,
            DataValue::U32(i) => *i != 0,
            DataValue::I32(i) => *i != 0,
            DataValue::U64(i) => *i != 0,
            DataValue::I64(i) => *i != 0,
            DataValue::F32(i) => *i != 0.,
            DataValue::F64(i) => *i != 0.,
            _ => false,
        }
    }
}

/// Extract implementation for `String`.
///
/// Extracts string values directly, or converts other types to their string representation.
impl Extract for String {
    fn extract(v: &DataValue) -> String {
        match v {
            DataValue::String(i) => i.to_string(),
            v => format!("{v}"),
        }
    }
}

/// Extract implementation for `Vec<T>` where `T: Extract`.
///
/// Extracts a vector by recursively extracting each element.
/// Returns an empty vector for non-Vec variants.
impl<T> Extract for Vec<T>
where
    T: Extract,
{
    fn extract(v: &DataValue) -> Vec<T> {
        use DataValue::*;
        match v {
            Vec(i) => i.iter().map(|x| T::extract(x)).collect(),
            _ => vec![],
        }
    }
}

/// Extract implementation for `HashMap<String, V>` where `V: Extract`.
///
/// Extracts a map by recursively extracting each value.
/// Returns an empty map for non-Map variants.
impl<V> Extract for std::collections::HashMap<smartstring::alias::String, V>
where
    V: Extract,
{
    fn extract(v: &DataValue) -> std::collections::HashMap<smartstring::alias::String, V> {
        use DataValue::*;
        match v {
            Map(i) => i.iter().map(|(k, v)| (k.clone(), V::extract(v))).collect(),
            _ => Default::default(),
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use rstest::*;

    #[rstest]
    #[case(DataValue::U8(1), 1)]
    #[case(DataValue::U32(1), 1)]
    #[case(DataValue::I32(1), 1)]
    #[case(DataValue::U64(1), 1)]
    #[case(DataValue::I64(1), 1)]
    #[case(DataValue::F32(1.0), 1)]
    #[case(DataValue::F64(1.0), 1)]
    #[case(DataValue::U128(1), 1)]
    #[case(DataValue::I128(1), 1)]
    #[case(DataValue::EnumNumber(1), 1)]
    #[case(DataValue::Bool(true), 1)]
    #[case(DataValue::Bool(false), 0)]
    #[case(DataValue::String("1".into()), 0)]
    fn test_extract_num(#[case] input: DataValue, #[case] expected: u32) {
        assert_eq!(u32::extract(&input), expected);
    }

    #[rstest]
    #[case(DataValue::U8(1), "1")]
    #[case(DataValue::U32(1), "1")]
    #[case(DataValue::I32(1), "1")]
    #[case(DataValue::U64(1), "1")]
    #[case(DataValue::I64(1), "1")]
    #[case(DataValue::F32(1.1), "1.1")]
    #[case(DataValue::F64(1.1), "1.1")]
    #[case(DataValue::U128(1), "1")]
    #[case(DataValue::I128(1), "1")]
    #[case(DataValue::EnumNumber(1), "1")]
    #[case(DataValue::Bool(true), "true")]
    #[case(DataValue::Bool(false), "false")]
    #[case(DataValue::String("1".into()), "1")]
    fn test_extract_string(#[case] input: DataValue, #[case] expected: &str) {
        assert_eq!(String::extract(&input), expected.to_string());
    }
    #[rstest]
    #[case(DataValue::U8(1), 1)]
    #[case(DataValue::U32(1), 1)]
    #[case(DataValue::I32(1), 1)]
    #[case(DataValue::U64(1), 1)]
    #[case(DataValue::I64(1), 1)]
    #[case(DataValue::F32(1.0), 1)]
    #[case(DataValue::F64(1.0), 1)]
    #[case(DataValue::U128(1), 1)]
    #[case(DataValue::I128(1), 1)]
    #[case(DataValue::EnumNumber(1), 1)]
    #[case(DataValue::Bool(true), 1)]
    #[case(DataValue::Bool(false), 0)]
    #[case(DataValue::String("1".into()), 0)]
    fn test_extract_u64(#[case] input: DataValue, #[case] expected: u64) {
        assert_eq!(u64::extract(&input), expected);
    }

    #[rstest]
    #[case(DataValue::U8(1), 1)]
    #[case(DataValue::U32(1), 1)]
    #[case(DataValue::I32(1), 1)]
    #[case(DataValue::U64(1), 1)]
    #[case(DataValue::I64(1), 1)]
    #[case(DataValue::F32(1.0), 1)]
    #[case(DataValue::F64(1.0), 1)]
    #[case(DataValue::U128(1), 1)]
    #[case(DataValue::I128(1), 1)]
    #[case(DataValue::EnumNumber(1), 1)]
    #[case(DataValue::Bool(true), 1)]
    #[case(DataValue::Bool(false), 0)]
    #[case(DataValue::String("1".into()), 0)]
    fn test_extract_i32(#[case] input: DataValue, #[case] expected: i32) {
        assert_eq!(i32::extract(&input), expected);
    }

    #[rstest]
    #[case(DataValue::U8(1), 1)]
    #[case(DataValue::U32(1), 1)]
    #[case(DataValue::I32(1), 1)]
    #[case(DataValue::U64(1), 1)]
    #[case(DataValue::I64(1), 1)]
    #[case(DataValue::F32(1.0), 1)]
    #[case(DataValue::F64(1.0), 1)]
    #[case(DataValue::U128(1), 1)]
    #[case(DataValue::I128(1), 1)]
    #[case(DataValue::EnumNumber(1), 1)]
    #[case(DataValue::Bool(true), 1)]
    #[case(DataValue::Bool(false), 0)]
    #[case(DataValue::String("1".into()), 0)]
    fn test_extract_i64(#[case] input: DataValue, #[case] expected: i64) {
        assert_eq!(i64::extract(&input), expected);
    }

    #[rstest]
    #[case(DataValue::U8(1), 1f64)]
    #[case(DataValue::U32(1), 1f64)]
    #[case(DataValue::I32(1), 1f64)]
    #[case(DataValue::U64(1), 1f64)]
    #[case(DataValue::I64(1), 1f64)]
    #[case(DataValue::F32(1.0), 1f64)]
    #[case(DataValue::F64(1.0), 1f64)]
    #[case(DataValue::U128(1), 1f64)]
    #[case(DataValue::I128(1), 1f64)]
    #[case(DataValue::EnumNumber(1), 1f64)]
    #[case(DataValue::Bool(true), 1f64)]
    #[case(DataValue::Bool(false), 0f64)]
    #[case(DataValue::String("1".into()), 0f64)]
    fn test_extract_f64(#[case] input: DataValue, #[case] expected: f64) {
        assert_eq!(f64::extract(&input), expected);
    }

    #[rstest]
    #[case(DataValue::U8(1), 1f32)]
    #[case(DataValue::U32(1), 1f32)]
    #[case(DataValue::I32(1), 1f32)]
    #[case(DataValue::U64(1), 1f32)]
    #[case(DataValue::I64(1), 1f32)]
    #[case(DataValue::F32(1.0), 1f32)]
    #[case(DataValue::F64(1.0), 1f32)]
    #[case(DataValue::U128(1), 1f32)]
    #[case(DataValue::I128(1), 1f32)]
    #[case(DataValue::EnumNumber(1), 1f32)]
    #[case(DataValue::Bool(true), 1f32)]
    #[case(DataValue::Bool(false), 0f32)]
    #[case(DataValue::String("1".into()), 0f32)]
    #[case(DataValue::Null, 0f32)]
    fn test_extract_f32(#[case] input: DataValue, #[case] expected: f32) {
        assert_eq!(f32::extract(&input), expected);
    }

    #[rstest]
    #[case(DataValue::Vec(vec![1.into()]), vec![1])]
    #[case(DataValue::Vec(vec![1.into(), 2.into()]), vec![1, 2])]
    #[case(DataValue::Vec(vec![1.into(), 2.into(), 3.into()]), vec![1, 2, 3])]
    fn test_extract_vec(#[case] input: DataValue, #[case] expected: Vec<u32>) {
        assert_eq!(Vec::<u32>::extract(&input), expected);
    }

    #[rstest]
    #[case(DataValue::Map(crate::stdhashmap!("a" => 1)), crate::stdhashmap!("a" => 1))]
    #[case(DataValue::Map(crate::stdhashmap!("a" => 1, "b" => 2)), crate::stdhashmap!("a" => 1, "b" => 2))]
    #[case(DataValue::Map(crate::stdhashmap!("a" => 1, "b" => 2, "c" => 3)), crate::stdhashmap!("a" => 1, "b" => 2, "c" => 3))]
    fn test_extract_map(
        #[case] input: DataValue,
        #[case] expected: std::collections::HashMap<smartstring::alias::String, i32>,
    ) {
        assert_eq!(
            std::collections::HashMap::<smartstring::alias::String, i32>::extract(&input),
            expected
        );
    }
}
