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

//! The types that represents the several data types available on PEL.

use std::{collections::HashMap, fmt::Debug, rc::Rc};

use crate::runtime::document::DocNode;
pub use crate::runtime::document::{DocumentBuilder, QualifiedName};
use crate::{
    runtime::{Context, RuntimeError},
    Location, Reference,
};

/// A map of string keys to values.
pub type Object = HashMap<String, Value>;

/// A sequence of values.
pub type Array = Vec<Value>;

/// A function that can be called from PEL expressions.
///
/// Implement this trait to define custom functions that can be called from within
/// PEL expressions. The function receives the call location, evaluation context,
/// and arguments when invoked.
pub trait Function {
    /// Apply this function with the given arguments in the particular context.
    fn apply(
        &self,
        location: Location,
        context: &dyn Context,
        arguments: &[Value],
    ) -> Result<Value, RuntimeError>;
}

struct DefaultFunction<F>(F);

impl<F> Function for DefaultFunction<F>
where
    F: Fn(Location, &dyn Context, &[Value]) -> Result<Value, RuntimeError>,
{
    fn apply(
        &self,
        location: Location,
        context: &dyn Context,
        arguments: &[Value],
    ) -> Result<Value, RuntimeError> {
        self.0(location, context, arguments)
    }
}

#[derive(Clone)]
pub(super) struct FunctionValue(Rc<dyn Function>);

impl PartialEq for FunctionValue {
    fn eq(&self, _: &Self) -> bool {
        false
    }
}

impl Debug for FunctionValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "NativeFunction")
    }
}

#[derive(Debug, Clone)]
/// A number value with the original string representation.
pub struct RuntimeNumber {
    value: f64,
    representation: String,
}

impl RuntimeNumber {
    /// Get the original string representation of the number.
    pub fn representation(&self) -> &str {
        &self.representation
    }
}

impl PartialEq for RuntimeNumber {
    fn eq(&self, other: &Self) -> bool {
        self.value == other.value
    }
}

#[derive(Debug, Clone)]
pub(super) enum InternalValue {
    Null,
    Bool(bool),
    Number(RuntimeNumber),
    String(String),
    Binary(Vec<u8>),
    Array(Rc<Array>),
    Object(Rc<Object>),
    Function(FunctionValue),
    Reference(Reference),
    QualifiedName(QualifiedName),
    Node(DocNode),
    #[cfg(feature = "experimental_coerced_type")]
    CoercedType(Rc<InternalValue>, Rc<String>),
}

impl PartialEq for InternalValue {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (InternalValue::Null, InternalValue::Null) => true,
            (InternalValue::Bool(first), InternalValue::Bool(second)) => first.eq(second),
            (InternalValue::Number(first), InternalValue::Number(second)) => first.eq(second),
            (InternalValue::String(first), InternalValue::String(second)) => first.eq(second),
            (InternalValue::Array(first), InternalValue::Array(second)) => first.eq(second),
            (InternalValue::Object(first), InternalValue::Object(second)) => first.eq(second),
            (InternalValue::Function(first), InternalValue::Function(second)) => first.eq(second),
            (InternalValue::Reference(first), InternalValue::Reference(second)) => first.eq(second),
            (InternalValue::Binary(first), InternalValue::Binary(second)) => first.eq(second),

            // Coerced Type
            #[cfg(feature = "experimental_coerced_type")]
            (InternalValue::CoercedType(value, coerced_value), second) => {
                value.as_ref().eq(second)
                    || match second {
                        InternalValue::String(second) => coerced_value.as_ref().eq(second),
                        _ => false,
                    }
            }
            #[cfg(feature = "experimental_coerced_type")]
            (first, InternalValue::CoercedType(value, coerced_value)) => {
                first.eq(value)
                    || match first {
                        InternalValue::String(first) => first.eq(coerced_value.as_ref()),
                        _ => false,
                    }
            }

            // DW treats the element as it internal value referenced as a leaf element, that is why we need to redefine the Partial Eq
            (InternalValue::Node(first), second) => first.compare(second),
            (first, InternalValue::Node(second)) => second.compare(first),

            (InternalValue::QualifiedName(first), InternalValue::QualifiedName(second)) => {
                first.eq(second)
            }
            _ => false,
        }
    }
}

/// Represents a value in the PEL runtime.
///
/// This can represent all possible value types that can be used in PEL expressions,
/// including primitive types, collections, and special types like functions and references.
#[derive(Debug, Clone, PartialEq)]
pub struct Value {
    pub(super) internal: InternalValue,
}

impl Value {
    /// Create a new Null value
    pub fn null() -> Self {
        Self {
            internal: InternalValue::Null,
        }
    }

    /// Create a new boolean value
    pub fn bool(b: bool) -> Self {
        Self {
            internal: InternalValue::Bool(b),
        }
    }

    /// Create a new numerical value
    pub fn number(value: f64) -> Self {
        // TODO: AGW-5356 - Improve number coercion
        Self::number_with_representation(value, value.to_string())
    }

    pub(crate) fn number_with_representation(value: f64, representation: String) -> Self {
        Self {
            internal: InternalValue::Number(RuntimeNumber {
                value,
                representation,
            }),
        }
    }

    /// Create a new string value
    pub fn string<S: Into<String>>(s: S) -> Self {
        Self {
            internal: InternalValue::String(s.into()),
        }
    }

    /// Create a new binary value
    pub fn binary<S: Into<Vec<u8>>>(s: S) -> Self {
        Self {
            internal: InternalValue::Binary(s.into()),
        }
    }

    #[cfg(feature = "experimental_coerced_type")]
    pub fn coerced_array(v: Vec<Value>, coerced: Rc<String>) -> Self {
        Self {
            internal: InternalValue::CoercedType(
                Rc::new(InternalValue::Array(Rc::new(v))),
                coerced,
            ),
        }
    }

    /// Create a new array value
    pub fn array(v: Vec<Value>) -> Self {
        Self {
            internal: InternalValue::Array(Rc::new(v)),
        }
    }

    /// Create a new object value
    pub fn object(o: Object) -> Self {
        Self {
            internal: InternalValue::Object(Rc::new(o)),
        }
    }

    #[cfg(feature = "experimental_coerced_type")]
    pub fn coerced_object(object: Object, coerced: Rc<String>) -> Self {
        Self {
            internal: InternalValue::CoercedType(
                Rc::new(InternalValue::Object(Rc::new(object))),
                coerced,
            ),
        }
    }

    pub(crate) fn qualified_name(qualified_name: QualifiedName) -> Self {
        Self {
            internal: InternalValue::QualifiedName(qualified_name),
        }
    }

    #[cfg(feature = "experimental_coerced_type")]
    pub(crate) fn coerced_node(node: DocNode, coerced: Rc<String>) -> Self {
        Self {
            internal: InternalValue::CoercedType(Rc::new(InternalValue::Node(node)), coerced),
        }
    }

    pub(crate) fn node(node: DocNode) -> Self {
        Self {
            internal: InternalValue::Node(node),
        }
    }

    /// Create a new reference value
    pub fn reference(reference: Reference) -> Self {
        Self {
            internal: InternalValue::Reference(reference),
        }
    }

    /// Create a new function value.
    pub fn function<F>(f: F) -> Self
    where
        F: 'static + Function,
    {
        Self {
            internal: InternalValue::Function(FunctionValue(Rc::new(f))),
        }
    }

    /// Create a new function value.
    pub fn function_from_fn<F>(f: F) -> Self
    where
        F: 'static + Fn(Location, &dyn Context, &[Value]) -> Result<Value, RuntimeError>,
    {
        Self::function(DefaultFunction(f))
    }

    /// Check if this value is null.
    pub fn is_null(&self) -> bool {
        match &self.internal {
            InternalValue::Null => true,
            InternalValue::Node(n) => n.is_null(),
            _ => false,
        }
    }

    /// Try to convert this value to a boolean.
    pub fn as_bool(&self) -> Option<bool> {
        match &self.internal {
            InternalValue::Bool(b) => Some(*b),
            _ => None,
        }
    }

    /// Try to convert this value to a f64.
    pub fn as_f64(&self) -> Option<f64> {
        match &self.internal {
            InternalValue::Number(n) => Some(n.value),
            _ => None,
        }
    }

    pub(super) fn as_number(&self) -> Option<&RuntimeNumber> {
        match &self.internal {
            InternalValue::Number(n) => Some(n),
            _ => None,
        }
    }

    /// Try to convert this value to a str.
    pub fn as_str(&self) -> Option<&str> {
        match &self.internal {
            InternalValue::String(s) => Some(s),
            InternalValue::Node(n) => n.as_str(),
            #[cfg(feature = "experimental_coerced_type")]
            InternalValue::CoercedType(value, coerced) => match value.as_ref() {
                InternalValue::String(s) => Some(s),
                InternalValue::Node(n) => n.as_str().or_else(|| Some(coerced)),
                _ => Some(coerced),
            },
            _ => None,
        }
    }

    /// Try to convert this value to an u8 array.
    pub fn as_binary(&self) -> Option<&[u8]> {
        match &self.internal {
            InternalValue::Binary(reference) => Some(reference),
            _ => None,
        }
    }

    pub(crate) fn as_qname(&self) -> Option<&QualifiedName> {
        match &self.internal {
            InternalValue::QualifiedName(s) => Some(s),
            _ => None,
        }
    }

    /// Try to convert this value to an array of values.
    pub fn as_slice(&self) -> Option<&[Value]> {
        match &self.internal {
            InternalValue::Array(array) => Some(array),
            #[cfg(feature = "experimental_coerced_type")]
            InternalValue::CoercedType(value, _) => match value.as_ref() {
                InternalValue::Array(array) => Some(array),
                _ => None,
            },
            _ => None,
        }
    }

    /// Try to convert this value to an object.
    pub fn as_object(&self) -> Option<&Object> {
        match &self.internal {
            InternalValue::Object(object) => Some(object),
            #[cfg(feature = "experimental_coerced_type")]
            InternalValue::CoercedType(value, _) => match value.as_ref() {
                InternalValue::Object(object) => Some(object),
                _ => None,
            },
            _ => None,
        }
    }

    /// Try to convert this value to a function.
    pub fn as_function(&self) -> Option<&dyn Function> {
        match &self.internal {
            InternalValue::Function(FunctionValue(function)) => Some(function.as_ref()),
            _ => None,
        }
    }

    /// Try to convert this value to a reference.
    pub fn as_reference(&self) -> Option<Reference> {
        match self.internal {
            InternalValue::Reference(reference) => Some(reference),
            _ => None,
        }
    }

    /// Try to convert this value to a document.
    pub fn as_doc_node(&self) -> Option<DocNode> {
        match &self.internal {
            InternalValue::Node(node) => Some(node.clone()),
            #[cfg(feature = "experimental_coerced_type")]
            InternalValue::CoercedType(value, _) => match value.as_ref() {
                InternalValue::Node(node) => Some(node.clone()),
                _ => None,
            },
            _ => None,
        }
    }

    /// Check if the value can be considered empty.
    pub fn is_empty(&self) -> bool {
        match &self.internal {
            InternalValue::Array(array) => array.is_empty(),
            InternalValue::String(s) => s.is_empty(),
            InternalValue::Object(object) => object.is_empty(),
            InternalValue::Null => true,
            InternalValue::Node(n) => n.is_empty(),
            _ => false,
        }
    }
}
