use crate::match_utils::coerce_value_to_shape::CoerceError;
use serde_json::Value;

pub fn coerce_value_to_number(a: Value) -> Result<Value, CoerceError> {
    match a {
        Value::Number(n) => Ok(Value::Number(n)),

        Value::String(s) => {
            let trimmed = s.trim();

            let n: serde_json::Number = trimmed
                .parse::<f64>()
                .ok()
                .and_then(serde_json::Number::from_f64)
                .ok_or_else(|| CoerceError::CannotCoerce {
                    from: Value::String(s.clone()), // preserve original
                    to: "number",
                })?;

            Ok(Value::Number(n))
        }

        other => Err(CoerceError::CannotCoerce {
            from: other,
            to: "number",
        }),
    }
}

#[cfg(test)]
mod tests {
    use super::coerce_value_to_number;
    use crate::match_utils::coerce_value_to_shape::CoerceError;
    use serde_json::{Value, json};

    fn assert_cannot_coerce(err: CoerceError) {
        match err {
            CoerceError::CannotCoerce { from: _, to } => {
                assert_eq!(to, "number");
            }
        }
    }

    #[test]
    fn passes_through_number() {
        assert_eq!(coerce_value_to_number(json!(0)).unwrap(), json!(0));
        assert_eq!(coerce_value_to_number(json!(42)).unwrap(), json!(42));
        assert_eq!(coerce_value_to_number(json!(-7)).unwrap(), json!(-7));
        assert_eq!(coerce_value_to_number(json!(3.14)).unwrap(), json!(3.14));
    }

    #[test]
    fn parses_numeric_strings() {
        // Note: parsing uses f64 + serde_json::Number::from_f64, so integers come back as floats.
        assert_eq!(coerce_value_to_number(json!("10")).unwrap(), json!(10.0));
        assert_eq!(coerce_value_to_number(json!("-10")).unwrap(), json!(-10.0));
        assert_eq!(coerce_value_to_number(json!("3.14")).unwrap(), json!(3.14));
        assert_eq!(coerce_value_to_number(json!("1e3")).unwrap(), json!(1000.0));
        assert_eq!(
            coerce_value_to_number(json!("  1e3  ")).unwrap(),
            json!(1000.0)
        );
    }

    #[test]
    fn invalid_numeric_strings_error() {
        let err = coerce_value_to_number(json!("")).unwrap_err();
        assert_cannot_coerce(err);

        let err = coerce_value_to_number(json!("nope")).unwrap_err();
        assert_cannot_coerce(err);

        // Non-finite floats parse, but serde_json::Number cannot represent them.
        let err = coerce_value_to_number(json!("NaN")).unwrap_err();
        assert_cannot_coerce(err);

        let err = coerce_value_to_number(json!("inf")).unwrap_err();
        assert_cannot_coerce(err);
    }

    #[test]
    fn non_string_non_number_error() {
        let err = coerce_value_to_number(Value::Null).unwrap_err();
        assert_cannot_coerce(err);

        let err = coerce_value_to_number(json!(true)).unwrap_err();
        assert_cannot_coerce(err);

        let err = coerce_value_to_number(json!([1, 2, 3])).unwrap_err();
        assert_cannot_coerce(err);

        let err = coerce_value_to_number(json!({"x": 1})).unwrap_err();
        assert_cannot_coerce(err);
    }
}
