#[cfg(all(feature = "serde", not(feature = "ext-lambdas")))]
use serde::Serialize;
use std::{collections::HashMap, fmt::Display};

#[cfg(feature = "ext-lambdas")]
mod lambdas;
#[cfg(feature = "serde")]
mod serialize;

#[cfg(feature = "ext-lambdas")]
pub use lambdas::{Provider, Transformer};
#[cfg(feature = "serde")]
pub use serialize::to_data;

/// Data represents all the possible values that can be fed to a Mustache template
/// during compile time.
#[cfg_attr(
    all(feature = "serde", not(feature = "ext-lambdas")),
    derive(Serialize),
    serde(untagged)
)]
#[derive(Clone, Debug)]
pub enum Data {
    /// The JSON null value.
    Null,
    /// A string value.
    String(String),
    /// A boolean value.
    Bool(bool),
    /// An integer value.
    Int(i64),
    /// A floating point value.
    Float(f64),
    /// A vector of multiple values.
    List(Vec<Data>),
    /// A hash map or dictionary of values.
    Map(HashMap<String, Data>),
    #[cfg(feature = "ext-lambdas")]
    /// A lambda function that does not expect any arguments.
    Provider(Provider),
    #[cfg(feature = "ext-lambdas")]
    /// A lambda function that expects a single argument.
    Transformer(Transformer),
}

impl Data {
    /// Returns true if the value is considered "truthy".
    pub fn is_truthy(&self) -> bool {
        match self {
            Data::Null => false,
            Data::String(s) => !s.is_empty(),
            Data::Bool(b) => *b,
            Data::Int(i) => *i != 0,
            Data::Float(fl) => *fl != 0.0,
            Data::List(list) => !list.is_empty(),
            Data::Map(map) => !map.is_empty(),
            #[cfg(feature = "ext-lambdas")]
            Data::Provider(_) | Data::Transformer(_) => true,
        }
    }

    /// This method is a shorthand for `Data::Map(HashMap::from(value))`.
    pub fn map_from<T>(value: T) -> Self
    where
        HashMap<String, Data>: From<T>,
    {
        Self::Map(HashMap::from(value))
    }
}

impl Display for Data {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Data::Null => Ok(()),
            Data::String(str) => f.write_str(str),
            Data::Bool(b) => write!(f, "{b}"),
            Data::Int(i) => write!(f, "{i}"),
            Data::Float(fl) => write!(f, "{fl}"),
            Data::List(data) => {
                f.write_str("[")?;
                for (i, d) in data.iter().enumerate() {
                    write!(f, "{}{d}", if i != 0 { ", " } else { "" })?;
                }
                Ok(())
            }
            Data::Map(hash_map) => {
                f.write_str("{")?;
                for (i, (k, v)) in hash_map.iter().enumerate() {
                    write!(f, "{}'{k}' = {v}", if i != 0 { ", " } else { "" })?;
                }
                Ok(())
            }
            #[cfg(feature = "ext-lambdas")]
            Data::Transformer(l) => write!(f, "{l}"),
            #[cfg(feature = "ext-lambdas")]
            Data::Provider(l) => write!(f, "{l}"),
        }
    }
}

impl From<String> for Data {
    fn from(value: String) -> Self {
        Self::String(value)
    }
}
impl From<&str> for Data {
    fn from(value: &str) -> Self {
        Self::String(value.to_owned())
    }
}
impl From<i64> for Data {
    fn from(value: i64) -> Self {
        Self::Int(value)
    }
}
impl From<f64> for Data {
    fn from(value: f64) -> Self {
        Self::Float(value)
    }
}
impl From<bool> for Data {
    fn from(value: bool) -> Self {
        Self::Bool(value)
    }
}
