Documentation
// Copyright (c) 2026, Salesforce, Inc.,
// All rights reserved.
// For full license text, see the LICENSE.txt file

use crate::evaluator::EvaluationError;
pub use num_traits::cast::FromPrimitive;
use pel::runtime::value::Value as PelValue;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Number, Value as JsonValue};
use std::collections::HashMap;
use std::convert::TryFrom;
use std::iter::FromIterator;

/// Represents any valid script value.
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Default)]
#[serde(from = "serde_json::Value", into = "serde_json::Value")]
pub enum Value {
    /// Represents a Null value.
    #[default]
    Null,

    /// Represents a Boolean.
    Bool(bool),

    /// Represents a floating point Number.
    Number(f64),

    /// Represents a String.
    String(String),

    /// Represents an Array of script values.
    Array(Vec<Value>),

    /// Represents an Object of script values.
    Object(HashMap<String, Value>),
}

impl Value {
    /// Returns [`true`] if the [`Value`] is a Null. Returns false otherwise.
    pub fn is_null(&self) -> bool {
        matches!(self, Value::Null)
    }

    /// If the [`Value`] is a Boolean, returns the associated [`bool`]`. Returns [`None`]`
    /// otherwise.
    pub fn as_bool(&self) -> Option<bool> {
        match self {
            Value::Bool(b) => Some(*b),
            _ => None,
        }
    }

    /// If the [`Value`] is a String, returns the associated [`str`]. Returns [`None`]`
    /// otherwise.
    pub fn as_str(&self) -> Option<&str> {
        match self {
            Value::String(s) => Some(s),
            _ => None,
        }
    }

    /// If the [`Value`] is a Number, returns the associated [`f64`]. Returns [`None`]`
    /// otherwise.
    pub fn as_num(&self) -> Option<f64> {
        match self {
            Value::Number(f) => Some(*f),
            _ => None,
        }
    }

    /// If the [`Value`] is an Array, returns the associated slice. Returns [`None`]`
    /// otherwise.
    pub fn as_slice(&self) -> Option<&[Value]> {
        match self {
            Value::Array(a) => Some(a),
            _ => None,
        }
    }

    /// If the [`Value`] is an Object, returns the associated [`HashMap`]. Returns [`None`]`
    /// otherwise.
    pub fn as_object(&self) -> Option<&HashMap<String, Value>> {
        match self {
            Value::Object(o) => Some(o),
            _ => None,
        }
    }
}

impl From<JsonValue> for Value {
    fn from(value: JsonValue) -> Self {
        match value {
            JsonValue::Null => Value::Null,
            JsonValue::Bool(val) => val.into_value(),
            JsonValue::Number(num) => num.as_f64().unwrap_or_default().into_value(),
            JsonValue::String(str) => str.into_value(),
            JsonValue::Array(vec) => vec.into_value(),
            JsonValue::Object(map) => map.into_iter().collect(),
        }
    }
}

impl From<Value> for JsonValue {
    fn from(value: Value) -> Self {
        JsonValue::try_from_value(value).unwrap_or_default()
    }
}

impl TryFrom<&PelValue> for Value {
    type Error = EvaluationError;

    fn try_from(value: &PelValue) -> Result<Self, Self::Error> {
        if value.is_null() {
            return Ok(Value::Null);
        };

        if let Some(val) = value.as_bool() {
            return Ok(val.into_value());
        };

        if let Some(val) = value.as_f64() {
            return Ok(val.into_value());
        };

        if let Some(val) = value.as_str() {
            return Ok(val.into_value());
        };

        if let Some(array) = value.as_slice() {
            let mut vec = Vec::new();
            for item in array {
                vec.push(Value::try_from(item)?);
            }
            return Ok(vec.into_value());
        };

        if let Some(obj) = value.as_object() {
            let mut map = HashMap::new();
            for (key, val) in obj {
                map.insert(key.to_string(), Value::try_from(val)?);
            }
            return Ok(map.into_value());
        };

        if let Some(s) = value.as_doc_node().and_then(|d| d.content()) {
            return Ok(Value::String(s));
        }

        Err(EvaluationError::TypeMismatch)
    }
}

impl IntoValue for JsonValue {
    fn into_value(self) -> Value {
        Value::from(self)
    }
}

impl Value {
    fn array_to_pel(vec: Vec<Value>) -> pel::runtime::value::Array {
        vec.into_iter().map(Value::into_pel).collect()
    }

    fn obj_to_pel(obj: HashMap<String, Value>) -> pel::runtime::value::Object {
        obj.into_iter()
            .map(|(key, value)| (key, value.into_pel()))
            .collect()
    }

    #[cfg(feature = "experimental_coerced_type")]
    pub(crate) fn coerced_pel(self, bytes: &[u8]) -> PelValue {
        let origin = std::rc::Rc::new(String::from_utf8_lossy(bytes).to_string());
        match self {
            Value::Object(obj) => PelValue::coerced_object(Self::obj_to_pel(obj), origin),
            Value::Array(vec) => PelValue::coerced_array(Self::array_to_pel(vec), origin),
            other => other.into_pel(),
        }
    }

    pub(crate) fn into_pel(self) -> PelValue {
        match self {
            Value::Null => PelValue::null(),
            Value::Bool(val) => PelValue::bool(val),
            Value::Number(num) => PelValue::number(num),
            Value::String(val) => PelValue::string(val),
            Value::Array(vec) => PelValue::array(Self::array_to_pel(vec)),
            Value::Object(obj) => PelValue::object(Self::obj_to_pel(obj)),
        }
    }
}

/// A Rust-value to Script-value conversion that consumes the input.
pub trait IntoValue {
    /// Converts this type into the related [`Value`] variant.
    fn into_value(self) -> Value;
}

impl IntoValue for Value {
    fn into_value(self) -> Value {
        self
    }
}

impl IntoValue for &str {
    fn into_value(self) -> Value {
        Value::String(self.to_string())
    }
}

impl IntoValue for String {
    fn into_value(self) -> Value {
        Value::String(self)
    }
}

macro_rules! into_value_num {
    // Match rule that takes an argument expression
    [$($num_type:ty), +] => {
        $(
        impl IntoValue for $num_type {
            fn into_value(self) -> Value {
                Value::Number(self as f64)
            }
        }
        )*
    };
}
into_value_num![i8, i16, i32, i64, u8, u16, u32, u64, f32, f64];

impl IntoValue for bool {
    fn into_value(self) -> Value {
        Value::Bool(self)
    }
}

impl<K: IntoValue> FromIterator<K> for Value {
    fn from_iter<T: IntoIterator<Item = K>>(iter: T) -> Self {
        Value::Array(iter.into_iter().map(IntoValue::into_value).collect())
    }
}

impl<'a, K: IntoValue> FromIterator<(&'a str, K)> for Value {
    fn from_iter<T: IntoIterator<Item = (&'a str, K)>>(iter: T) -> Self {
        Value::Object(
            iter.into_iter()
                .map(|(key, val)| (key.to_string(), val.into_value()))
                .collect(),
        )
    }
}

impl<K: IntoValue> FromIterator<(String, K)> for Value {
    fn from_iter<T: IntoIterator<Item = (String, K)>>(iter: T) -> Self {
        Value::Object(
            iter.into_iter()
                .map(|(key, val)| (key, val.into_value()))
                .collect(),
        )
    }
}

impl<K: IntoValue> IntoValue for Vec<K> {
    fn into_value(self) -> Value {
        self.into_iter().collect()
    }
}

impl<K: IntoValue> IntoValue for HashMap<String, K> {
    fn into_value(self) -> Value {
        self.into_iter().collect()
    }
}

impl<K: IntoValue> IntoValue for HashMap<&str, K> {
    fn into_value(self) -> Value {
        self.into_iter().collect()
    }
}

impl<K: IntoValue> IntoValue for Option<K> {
    fn into_value(self) -> Value {
        match self {
            None => Value::Null,
            Some(k) => k.into_value(),
        }
    }
}

/// A falible Script-value to Rust-value conversion that consumes the input.
pub trait TryFromValue: Sized {
    /// Performs the conversion.
    fn try_from_value(value: Value) -> Result<Self, EvaluationError>;
}

impl TryFromValue for Value {
    fn try_from_value(value: Value) -> Result<Self, EvaluationError> {
        Ok(value)
    }
}

impl TryFromValue for bool {
    fn try_from_value(value: Value) -> Result<Self, EvaluationError> {
        match value {
            Value::Bool(val) => Ok(val),
            _ => Err(EvaluationError::TypeMismatch),
        }
    }
}
macro_rules! try_from_value_num {
    // Match rule that takes an argument expression
    [$($num_type:ty), +] => {
        $(
        impl TryFromValue for $num_type {

            fn try_from_value(value: Value) -> Result<Self, EvaluationError> {
                match value {
                    Value::Number(num) => {
                        <$num_type>::from_f64(num)
                            .ok_or(EvaluationError::TypeMismatch)
                    }
                    _ => Err(EvaluationError::TypeMismatch)
                }
            }
        }
        )*
    };
}

try_from_value_num![i8, i16, i32, i64, u8, u16, u32, u64, f32, f64];

impl TryFromValue for String {
    fn try_from_value(value: Value) -> Result<Self, EvaluationError> {
        match value {
            Value::String(val) => Ok(val),
            _ => Err(EvaluationError::TypeMismatch),
        }
    }
}

impl<K: TryFromValue> TryFromValue for Vec<K> {
    fn try_from_value(value: Value) -> Result<Self, EvaluationError> {
        match value {
            Value::Array(array) => array.into_iter().map(K::try_from_value).collect(),
            _ => Err(EvaluationError::TypeMismatch),
        }
    }
}

impl<K: TryFromValue> TryFromValue for HashMap<String, K> {
    fn try_from_value(value: Value) -> Result<Self, EvaluationError> {
        match value {
            Value::Object(obj) => obj
                .into_iter()
                .map(|(key, val)| Ok((key, K::try_from_value(val)?)))
                .collect(),
            _ => Err(EvaluationError::TypeMismatch),
        }
    }
}

impl TryFromValue for Map<String, JsonValue> {
    fn try_from_value(value: Value) -> Result<Self, EvaluationError> {
        match value {
            Value::Object(obj) => obj
                .into_iter()
                .map(|(key, val)| Ok((key, JsonValue::try_from_value(val)?)))
                .collect(),
            _ => Err(EvaluationError::TypeMismatch),
        }
    }
}

impl TryFromValue for JsonValue {
    fn try_from_value(value: Value) -> Result<Self, EvaluationError> {
        match value {
            Value::Null => Ok(JsonValue::Null),
            Value::Bool(val) => Ok(JsonValue::Bool(val)),
            Value::Number(n) => Number::from_f64(n)
                .map(JsonValue::Number)
                .ok_or(EvaluationError::TypeMismatch),
            Value::String(s) => Ok(JsonValue::String(s)),
            Value::Array(_) => Vec::<JsonValue>::try_from_value(value).map(JsonValue::Array),
            Value::Object(_) => {
                Map::<String, JsonValue>::try_from_value(value).map(JsonValue::Object)
            }
        }
    }
}