use std::collections::HashMap;

use anyhow::bail;
use core_shared::result::SomeResult;
use serde::Deserialize;
use serde_bytes::ByteBuf;

use crate::{
    base_objects::AnyBaseObject,
    rgba::Rgba,
    vector::{Vector2, Vector3},
};

/// An enum that maps alt:V [SDK MValue](https://docs.altv.mp/sdk/mvalues.html) to Rust types as closely as possible.
///
/// # Examples
///
/// Log content of any mvalue.
/// ```rust
/// # mod altv { pub use altv_internal_core_resource::exports::*; }
/// # use altv::mvalue::DeserializeMValueArgs;
/// # fn test() -> altv::VoidResult {
/// altv::events::on("any_mvalue", |event| {
///     let (any,): (altv::mvalue::AnyMValue,) = event.args.deserialize()?;
///     dbg!(any);
///     Ok(())
/// });
/// # Ok(()) }
/// ```
///
/// Receive number (double or integer) from JS
/// (it won't work for bigint though, bigint is serialized as `AnyMValue::UInt`).
/// ```rust
/// # mod altv { pub use altv_internal_core_resource::exports::*; }
/// # use altv::mvalue::DeserializeMValueArgs;
/// # fn test() -> altv::VoidResult {
/// altv::events::on("any_mvalue", |event| {
///     let (js_number,): (altv::mvalue::AnyMValue,) = event.args.deserialize()?;
///     let num: f64 = js_number.as_f64()?;
///     Ok(())
/// });
/// # Ok(()) }
/// ```
#[derive(Debug, Deserialize)]
#[serde(rename = "___altv_any_enum_mvalue")] // see ANY_MVALUE_ENUM in mvalue crate
pub enum AnyMValue {
    None,
    Nil,
    Bool(bool),
    Int(i64),
    UInt(u64),
    Double(f64),
    String(String),
    List(Vec<AnyMValue>),
    Dict(HashMap<String, AnyMValue>),
    BaseObject(AnyBaseObject),
    /// Not supported (yet?)
    Function,
    Vector3(Vector3),
    Rgba(Rgba),
    ByteArray(ByteBuf),
    Vector2(Vector2),
}

impl AnyMValue {
    pub fn as_f64(&self) -> SomeResult<f64> {
        match self {
            AnyMValue::Double(v) => Ok(*v),
            AnyMValue::Int(v) => Ok(*v as _),
            _ => bail!("Expected AnyMValue::Double or AnyMValue::Int, received: {self:?}"),
        }
    }
}
