#![cfg_attr(not(doctest), doc = include_str!("../README.md"))]
use std::collections::HashMap;
use bumpalo::Bump;
mod datetime;
mod errors;
mod evaluator;
mod parser;
pub use errors::Error;
pub use evaluator::functions::FunctionContext;
pub use evaluator::value::{ArrayFlags, Value};
use evaluator::{frame::Frame, functions::*, Evaluator};
use parser::ast::Ast;
pub type Result<T> = std::result::Result<T, Error>;
pub struct JsonAta<'a> {
    ast: Ast,
    frame: Frame<'a>,
    arena: &'a Bump,
}
impl<'a> JsonAta<'a> {
    pub fn new(expr: &str, arena: &'a Bump) -> Result<JsonAta<'a>> {
        Ok(Self {
            ast: parser::parse(expr)?,
            frame: Frame::new(),
            arena,
        })
    }
    pub fn ast(&self) -> &Ast {
        &self.ast
    }
    pub fn assign_var(&self, name: &str, value: &'a Value<'a>) {
        self.frame.bind(name, value)
    }
    pub fn register_function(
        &self,
        name: &str,
        arity: usize,
        implementation: fn(FunctionContext<'a, '_>, &[&'a Value<'a>]) -> Result<&'a Value<'a>>,
    ) {
        self.frame.bind(
            name,
            Value::nativefn(self.arena, name, arity, implementation),
        );
    }
    fn json_value_to_value(&self, json_value: &serde_json::Value) -> &'a mut Value<'a> {
        return match json_value {
            serde_json::Value::Null => Value::null(self.arena),
            serde_json::Value::Bool(b) => self.arena.alloc(Value::Bool(*b)),
            serde_json::Value::Number(n) => Value::number(self.arena, n.as_f64().unwrap()),
            serde_json::Value::String(s) => Value::string(self.arena, s),
            serde_json::Value::Array(a) => {
                let array = Value::array_with_capacity(self.arena, a.len(), ArrayFlags::empty());
                for v in a.iter() {
                    array.push(self.json_value_to_value(v))
                }
                return array;
            }
            serde_json::Value::Object(o) => {
                let object = Value::object_with_capacity(self.arena, o.len());
                for (k, v) in o.iter() {
                    object.insert(k, self.json_value_to_value(v));
                }
                return object;
            }
        };
    }
    pub fn evaluate(
        &self,
        input: Option<&str>,
        bindings: Option<&HashMap<&str, &serde_json::Value>>,
    ) -> Result<&'a Value<'a>> {
        if let Some(bindings) = bindings {
            for (key, json_value) in bindings.iter() {
                let value = self.json_value_to_value(json_value);
                self.assign_var(key, value);
            }
        };
        self.evaluate_timeboxed(input, None, None)
    }
    pub fn evaluate_timeboxed(
        &self,
        input: Option<&str>,
        max_depth: Option<usize>,
        time_limit: Option<usize>,
    ) -> Result<&'a Value<'a>> {
        let input = match input {
            Some(input) => {
                let input_ast = parser::parse(input)?;
                let evaluator = Evaluator::new(None, self.arena, None, None);
                evaluator.evaluate(&input_ast, Value::undefined(), &Frame::new())?
            }
            None => Value::undefined(),
        };
        let input = if input.is_array() {
            Value::wrap_in_array(self.arena, input, ArrayFlags::WRAPPED)
        } else {
            input
        };
        macro_rules! bind_native {
            ($name:literal, $arity:literal, $fn:ident) => {
                self.frame
                    .bind($name, Value::nativefn(&self.arena, $name, $arity, $fn));
            };
        }
        self.frame.bind("$", input);
        bind_native!("abs", 1, fn_abs);
        bind_native!("append", 2, fn_append);
        bind_native!("assert", 2, fn_assert);
        bind_native!("base64decode", 1, fn_base64_decode);
        bind_native!("base64encode", 1, fn_base64_encode);
        bind_native!("boolean", 1, fn_boolean);
        bind_native!("ceil", 1, fn_ceil);
        bind_native!("contains", 2, fn_contains);
        bind_native!("count", 1, fn_count);
        bind_native!("distinct", 1, fn_distinct);
        bind_native!("each", 2, fn_each);
        bind_native!("error", 1, fn_error);
        bind_native!("exists", 1, fn_exists);
        bind_native!("fromMillis", 3, from_millis);
        bind_native!("toMillis", 2, to_millis);
        bind_native!("single", 2, single);
        bind_native!("filter", 2, fn_filter);
        bind_native!("floor", 1, fn_floor);
        bind_native!("join", 2, fn_join);
        bind_native!("keys", 1, fn_keys);
        bind_native!("length", 1, fn_length);
        bind_native!("lookup", 2, fn_lookup);
        bind_native!("lowercase", 1, fn_lowercase);
        bind_native!("map", 2, fn_map);
        bind_native!("max", 1, fn_max);
        bind_native!("merge", 1, fn_merge);
        bind_native!("min", 1, fn_min);
        bind_native!("not", 1, fn_not);
        bind_native!("now", 2, fn_now);
        bind_native!("number", 1, fn_number);
        bind_native!("pad", 2, fn_pad);
        bind_native!("power", 2, fn_power);
        bind_native!("random", 0, fn_random);
        bind_native!("reduce", 3, fn_reduce);
        bind_native!("replace", 4, fn_replace);
        bind_native!("reverse", 1, fn_reverse);
        bind_native!("round", 2, fn_round);
        bind_native!("sort", 2, fn_sort);
        bind_native!("split", 3, fn_split);
        bind_native!("sqrt", 1, fn_sqrt);
        bind_native!("string", 1, fn_string);
        bind_native!("substring", 3, fn_substring);
        bind_native!("substringBefore", 2, fn_substring_before);
        bind_native!("substringAfter", 2, fn_substring_after);
        bind_native!("sum", 1, fn_sum);
        bind_native!("trim", 1, fn_trim);
        bind_native!("uppercase", 1, fn_uppercase);
        bind_native!("zip", 1, fn_zip);
        let chain_ast = Some(parser::parse(
            "function($f, $g) { function($x){ $g($f($x)) } }",
        )?);
        let evaluator = Evaluator::new(chain_ast, self.arena, max_depth, time_limit);
        evaluator.evaluate(&self.ast, input, &self.frame)
    }
}
#[cfg(test)]
mod tests {
    use chrono::{DateTime, Offset};
    use regex::Regex;
    use super::*;
    #[test]
    fn register_function_simple() {
        let arena = Bump::new();
        let jsonata = JsonAta::new("$test()", &arena).unwrap();
        jsonata.register_function("test", 0, |ctx, _| Ok(Value::number(ctx.arena, 1)));
        let result = jsonata.evaluate(Some(r#"anything"#), None);
        assert_eq!(result.unwrap(), Value::number(&arena, 1));
    }
    #[test]
    fn register_function_override_now() {
        let arena = Bump::new();
        let jsonata = JsonAta::new("$now()", &arena).unwrap();
        jsonata.register_function("now", 0, |ctx, _| {
            Ok(Value::string(ctx.arena, "time for tea"))
        });
        let result = jsonata.evaluate(None, None);
        assert_ne!(result.unwrap().as_str(), "time for tea");
    }
    #[test]
    fn register_function_map_squareroot() {
        let arena = Bump::new();
        let jsonata = JsonAta::new("$map([1,4,9,16], $squareroot)", &arena).unwrap();
        jsonata.register_function("squareroot", 1, |ctx, args| {
            let num = &args[0];
            return Ok(Value::number(ctx.arena, (num.as_f64()).sqrt()));
        });
        let result = jsonata.evaluate(Some(r#"anything"#), None);
        assert_eq!(
            result
                .unwrap()
                .members()
                .map(|v| v.as_f64())
                .collect::<Vec<f64>>(),
            vec![1.0, 2.0, 3.0, 4.0]
        );
    }
    #[test]
    fn register_function_filter_even() {
        let arena = Bump::new();
        let jsonata = JsonAta::new("$filter([1,4,9,16], $even)", &arena).unwrap();
        jsonata.register_function("even", 1, |_ctx, args| {
            let num = &args[0];
            return Ok(Value::bool((num.as_f64()) % 2.0 == 0.0));
        });
        let result = jsonata.evaluate(Some(r#"anything"#), None);
        assert_eq!(
            result
                .unwrap()
                .members()
                .map(|v| v.as_f64())
                .collect::<Vec<f64>>(),
            vec![4.0, 16.0]
        );
    }
    #[test]
    fn evaluate_with_bindings_simple() {
        let arena = Bump::new();
        let jsonata = JsonAta::new("$a + $b", &arena).unwrap();
        let a = &serde_json::Value::Number(serde_json::Number::from(1));
        let b = &serde_json::Value::Number(serde_json::Number::from(2));
        let mut bindings = HashMap::new();
        bindings.insert("a", a);
        bindings.insert("b", b);
        let result = jsonata.evaluate(None, Some(&bindings));
        assert_eq!(result.unwrap().as_f64(), 3.0);
    }
    #[test]
    fn evaluate_with_bindings_nested() {
        let arena = Bump::new();
        let jsonata = JsonAta::new("$foo[0].a + $foo[1].b", &arena).unwrap();
        let foo_string = r#"
            [
                {
                    "a": 1
                },
                {
                    "b": 2
                }
            ]
        "#;
        let foo: serde_json::Value = serde_json::from_str(foo_string).unwrap();
        let mut bindings = HashMap::new();
        bindings.insert("foo", &foo);
        let result = jsonata.evaluate(None, Some(&bindings));
        assert_eq!(result.unwrap().as_f64(), 3.0);
    }
    #[test]
    fn evaluate_with_random() {
        let arena = Bump::new();
        let jsonata = JsonAta::new("$random()", &arena).unwrap();
        let result = jsonata.evaluate(None, None).unwrap();
        assert!(result.as_f64() >= 0.0);
        assert!(result.as_f64() < 1.0);
    }
    #[test]
    fn test_now_default_utc() {
        let arena = Bump::new();
        let jsonata = JsonAta::new("$now()", &arena).unwrap();
        let result = jsonata.evaluate(None, None).unwrap();
        let result_str = result.as_str();
        println!("test_now_default_utc {}", result_str);
        let parsed_result = DateTime::parse_from_rfc3339(&result_str)
            .expect("Should parse valid ISO 8601 timestamp");
        assert_eq!(
            parsed_result.offset().fix().local_minus_utc(),
            0,
            "Should be UTC"
        );
    }
    #[test]
    fn test_now_with_valid_timezone_and_format() {
        let arena = Bump::new();
        let jsonata = JsonAta::new(
            "$now('[M01]/[D01]/[Y0001] [h#1]:[m01][P] [z]', '-0500')",
            &arena,
        )
        .unwrap();
        let result = jsonata.evaluate(None, None).unwrap();
        let result_str = result.as_str();
        println!("test_now_with_valid_timezone_and_format {}", result_str);
        let expected_format =
            Regex::new(r"^\d{2}/\d{2}/\d{4} \d{1,2}:\d{2}(AM|PM|am|pm) GMT-05:00$").unwrap();
        assert!(
            expected_format.is_match(&result_str),
            "Expected custom formatted time with timezone"
        );
    }
    #[test]
    fn test_now_with_valid_format_but_no_timezone() {
        let arena = Bump::new();
        let jsonata = JsonAta::new("$now('[M01]/[D01]/[Y0001] [h#1]:[m01][P]')", &arena).unwrap();
        let result = jsonata.evaluate(None, None).unwrap();
        let result_str = result.as_str();
        println!("test_now_with_valid_format_but_no_timezone {}", result_str);
        assert!(
            Regex::new(r"^\d{2}/\d{2}/\d{4} \d{1,2}:\d{2}(AM|PM|am|pm)$")
                .unwrap()
                .is_match(&result_str),
            "Expected custom formatted time without timezone"
        );
    }
    #[test]
    fn test_now_with_invalid_timezone() {
        let arena = Bump::new();
        let jsonata = JsonAta::new("$now('', 'invalid')", &arena).unwrap();
        let result = jsonata.evaluate(None, None);
        assert!(
            result.is_err(),
            "Expected a runtime error for invalid timezone"
        );
        if let Err(err) = result {
            assert_eq!(
                err.to_string(),
                "T0410 @ 2: Argument 1 of function now does not match function signature"
            );
        }
    }
    #[test]
    fn test_now_with_valid_timezone_but_no_format() {
        let arena = Bump::new();
        let jsonata = JsonAta::new("$now('', '-0500')", &arena).unwrap();
        let result = jsonata.evaluate(None, None).unwrap();
        let result_str = result.as_str();
        println!("test_now_with_valid_timezone_but_no_format {}", result_str);
        assert!(
            result_str.is_empty(),
            "Expected empty string for valid timezone but no format"
        );
    }
    #[test]
    fn test_now_with_too_many_arguments() {
        let arena = Bump::new();
        let jsonata = JsonAta::new("$now('', '-0500', 'extra')", &arena).unwrap();
        let result = jsonata.evaluate(None, None);
        assert!(
            result.is_err(),
            "Expected an error due to too many arguments, but got a result"
        );
        if let Err(e) = result {
            assert!(
                matches!(e, Error::T0410ArgumentNotValid { .. }),
                "Expected TooManyArguments error, but got: {:?}",
                e
            );
        }
    }
    #[test]
    fn test_now_with_edge_case_timezones() {
        let arena = Bump::new();
        let jsonata = JsonAta::new("$now('[H01]:[m01] [z]', '+1440')", &arena).unwrap();
        let result = jsonata.evaluate(None, None).unwrap();
        let result_str = result.as_str();
        println!("test_now_with_extreme_positive_timezone {:?}", result_str);
        assert!(result_str.contains("GMT+14:40"), "Expected GMT+14:40");
        let jsonata_minimal = JsonAta::new("$now('[H01]:[m01] [z]', '-0000')", &arena).unwrap();
        let result_minimal = jsonata_minimal.evaluate(None, None).unwrap();
        let result_str_minimal = result_minimal.as_str();
        println!("test_now_with_minimal_timezone {:?}", result_str_minimal);
        assert!(
            result_str_minimal.contains("GMT+00:00"),
            "Expected GMT+00:00"
        );
    }
    #[test]
    fn test_custom_format_with_components() {
        let arena = Bump::new();
        let jsonata = JsonAta::new(
            "$now('[M01]/[D01]/[Y0001] [h#1]:[m01][P] [z]', '-0500')",
            &arena,
        )
        .unwrap();
        let result = jsonata.evaluate(None, None).unwrap();
        let result_str = result.as_str();
        println!("Formatted date: {}", result_str);
        let expected_format =
            Regex::new(r"^\d{2}/\d{2}/\d{4} \d{1,2}:\d{2}(am|pm|AM|PM) GMT-05:00$").unwrap();
        assert!(
            expected_format.is_match(&result_str),
            "Expected 12-hour format with timezone"
        );
    }
    #[test]
    fn test_now_with_invalid_timezone_should_fail() {
        let arena = Bump::new();
        let jsonata = JsonAta::new("$now('', 'invalid')", &arena).unwrap();
        let result = jsonata.evaluate(None, None);
        assert!(
            result.is_err(),
            "Expected a runtime error for invalid timezone"
        );
        let error = result.unwrap_err();
        assert_eq!(
            error.to_string(),
            "T0410 @ 2: Argument 1 of function now does not match function signature"
        );
    }
    #[test]
    fn test_now_invalid_timezone_with_format() {
        let arena = Bump::new();
        let jsonata = JsonAta::new("$now('[h]:[m]:[s]', 'xx')", &arena).unwrap();
        let result = jsonata.evaluate(None, None);
        assert!(
            result.is_err(),
            "Expected a runtime error for invalid timezone"
        );
        if let Err(err) = result {
            assert_eq!(
                err.to_string(),
                "T0410 @ 2: Argument 1 of function now does not match function signature"
            );
        }
    }
    #[test]
    fn test_now_invalid_format_with_timezone() {
        let arena = Bump::new();
        let jsonata = JsonAta::new("$now('[h01]:[m01]:[s01]', 'xx')", &arena).unwrap();
        let result = jsonata.evaluate(None, None);
        assert!(
            result.is_err(),
            "Expected a runtime error for invalid format"
        );
        if let Err(err) = result {
            assert_eq!(
                err.to_string(),
                "T0410 @ 2: Argument 1 of function now does not match function signature"
            );
        }
    }
    #[test]
    fn test_from_millis_with_custom_format() {
        let arena = Bump::new();
        let jsonata = JsonAta::new(
            "$fromMillis(1726148700000, '[M01]/[D01]/[Y0001] [H01]:[m01]:[s01] [z]')",
            &arena,
        )
        .unwrap();
        let result = jsonata.evaluate(None, None).unwrap();
        let result_str = result.as_str();
        println!("Formatted date: {}", result_str);
        let expected_format =
            Regex::new(r"^\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2} GMT\+\d{2}:\d{2}$").unwrap();
        assert!(
            expected_format.is_match(&result_str),
            "Expected custom formatted date with timezone"
        );
    }
    #[test]
    fn test_to_millis_with_custom_format() {
        let arena = Bump::new();
        let jsonata = JsonAta::new(
            "$toMillis('13/09/2024 13:45:00', '[D01]/[M01]/[Y0001] [H01]:[m01]:[s01]')",
            &arena,
        )
        .unwrap();
        let result = jsonata.evaluate(None, None);
        match result {
            Ok(value) => {
                if value.is_number() {
                    let millis = value.as_f64();
                    assert_eq!(
                        millis, 1726235100000.0,
                        "Expected milliseconds for the given date"
                    );
                } else {
                    println!("Result is not a number: {:?}", value);
                    panic!("Expected a number, but got something else.");
                }
            }
            Err(err) => {
                println!("Evaluation error: {:?}", err);
                panic!("Failed to evaluate the expression.");
            }
        }
    }
    #[test]
    fn evaluate_with_reduce() {
        let arena = Bump::new(); let jsonata = JsonAta::new(
            "$reduce([1..5], function($i, $j){$i * $j})", &arena,
        )
        .unwrap();
        let result = jsonata.evaluate(None, None).unwrap(); assert_eq!(result.as_f64(), 120.0);
    }
    #[test]
    fn evaluate_with_reduce_sum() {
        let arena = Bump::new();
        let jsonata = JsonAta::new(
            "$reduce([1..5], function($i, $j){$i + $j})", &arena,
        )
        .unwrap();
        let result = jsonata.evaluate(None, None).unwrap();
        assert_eq!(result.as_f64(), 15.0);
    }
    #[test]
    fn evaluate_with_reduce_custom_initial_value() {
        let arena = Bump::new();
        let jsonata = JsonAta::new(
            "$reduce([1..5], function($i, $j){$i * $j}, 10)", &arena,
        )
        .unwrap();
        let result = jsonata.evaluate(None, None).unwrap();
        assert_eq!(result.as_f64(), 1200.0);
    }
    #[test]
    fn evaluate_with_reduce_empty_array() {
        let arena = Bump::new();
        let jsonata = JsonAta::new(
            "$reduce([], function($i, $j){$i + $j})", &arena,
        )
        .unwrap();
        let result = jsonata.evaluate(None, None).unwrap();
        assert!(result.is_undefined());
    }
    #[test]
    fn evaluate_with_reduce_single_element() {
        let arena = Bump::new();
        let jsonata = JsonAta::new(
            "$reduce([42], function($i, $j){$i + $j})", &arena,
        )
        .unwrap();
        let result = jsonata.evaluate(None, None).unwrap();
        assert_eq!(result.as_f64(), 42.0);
    }
    #[test]
    fn evaluate_with_reduce_subtraction() {
        let arena = Bump::new();
        let jsonata = JsonAta::new(
            "$reduce([10, 3, 2], function($i, $j){$i - $j})", &arena,
        )
        .unwrap();
        let result = jsonata.evaluate(None, None).unwrap();
        assert_eq!(result.as_f64(), 5.0);
    }
    #[test]
    fn evaluate_with_reduce_initial_value_greater() {
        let arena = Bump::new();
        let jsonata = JsonAta::new(
            "$reduce([1..3], function($i, $j){$i - $j}, 10)", &arena,
        )
        .unwrap();
        let result = jsonata.evaluate(None, None).unwrap();
        assert_eq!(result.as_f64(), 4.0);
    }
    #[test]
    fn evaluate_with_reduce_single_data_element() {
        let arena = Bump::new();
        let jsonata =
            JsonAta::new("$reduce([\"data\"], function($i, $j){$i + $j})", &arena).unwrap();
        let result = jsonata.evaluate(None, None).unwrap();
        assert_eq!(result.as_str(), "data"); }
}