use anyhow::{bail, Result};

use libesedb::Value;

pub(crate) trait FromValue
where
    Self: Sized,
{
    fn from_value(value: &Value, attribute_name: &str) -> Result<Self> {
        if matches!(value, Value::Null(())) {
            bail!("missing value in '{attribute_name}'");
        } else {
            match Self::from_value_opt(value, attribute_name)? {
                Some(x) => Ok(x),
                None => bail!("value of '{attribute_name}' has no content"),
            }
        }
    }
    fn from_value_opt(value: &Value, attribute_name: &str) -> Result<Option<Self>>;
}

impl FromValue for i32 {
    fn from_value_opt(value: &Value, attribute_name: &str) -> Result<Option<Self>> {
        match value {
            Value::I32(val) => Ok(Some(*val)),
            Value::Null(()) => Ok(None),
            _ => bail!("invalid value detected: {value:?} in attribute '{attribute_name}'"),
        }
    }
}

impl FromValue for u32 {
    fn from_value_opt(value: &Value, attribute_name: &str) -> Result<Option<Self>> {
        match value {
            Value::U8(val) => Ok(Some((*val).into())),
            Value::U16(val) => Ok(Some((*val).into())),
            Value::U32(val) => Ok(Some(*val)),
            Value::I16(val) => Ok(Some((*val).try_into()?)),
            Value::I32(val) => Ok(Some((*val).try_into()?)),
            Value::Null(()) => Ok(None),
            _ => bail!("invalid value detected: {value:?} in attribute '{attribute_name}'"),
        }
    }
}

impl FromValue for String {
    fn from_value_opt(value: &Value, attribute_name: &str) -> Result<Option<Self>> {
        match value {
            Value::Text(val) => Ok(Some(val.to_owned())),
            Value::LargeText(val) => Ok(Some(val.to_owned())),
            Value::Binary(val) | Value::LargeBinary(val) => Ok(Some(hex::encode(val))),
            Value::Null(()) => Ok(None),
            _ => bail!("invalid value detected: {value:?} in field {attribute_name}"),
        }
    }
}

