/*
 * Author: Ruben Fiszel
 * Copyright: Windmill Labs, Inc 2022
 * This file and its contents are licensed under the AGPLv3 License.
 * Please see the included NOTICE for copyright information and
 * LICENSE-AGPL for a copy of the license.
 */

//! QuickJS-based JavaScript expression evaluation for flow transformations.
//!
//! This crate provides fast, lightweight JS expression evaluation using QuickJS (rquickjs).
//! It is used by both windmill-worker (for flow expression eval) and windmill-api (for batch rerun).
//!
//! ## Performance Characteristics (release mode benchmarks)
//! - **Simple expressions**: ~238μs (QuickJS) vs ~3.05ms (deno_core) = **~13x faster**
//! - **Complex expressions**: ~192μs (QuickJS) vs ~3.09ms (deno_core) = **~16x faster**
//! - **Memory**: ~2.5% of V8's footprint

use std::collections::HashMap;
use std::sync::Arc;

pub const EVAL_TIMEOUT_MS: u64 = 20000;

use lazy_static::lazy_static;
use regex::Regex;
#[cfg(feature = "quickjs")]
use rquickjs::{
    async_with,
    prelude::{Async, Func, MutFn},
    AsyncContext, AsyncRuntime, CatchResultExt, FromJs, IntoJs, Object, Value,
};
use serde_json::value::RawValue;
use uuid::Uuid;

use windmill_common::client::AuthedClient;
use windmill_common::flow_status::JobResult;

// ── Public types ──────────────────────────────────────────────────────

#[derive(Debug, Clone)]
pub struct IdContext {
    pub flow_job: Uuid,
    pub root_flow_job: Uuid,
    #[allow(dead_code)]
    pub steps_results: HashMap<String, JobResult>,
    pub previous_id: String,
}

// ── Constants ─────────────────────────────────────────────────────────

const FLOW_INPUT_PREFIX: &str = "flow_input";
const ENV_KEY_PREFIX: &str = "flow_env";
const DOT_PATTERN: &str = ".";
const START_BRACKET_PATTERN: &str = "[\"";
const END_BRACKET_PATTERN: &str = "\"]";

// ── Regex statics ─────────────────────────────────────────────────────

lazy_static! {
    // `results` is fetched lazily via the `__getResult` async proxy, so we
    // wrap each `results.X` access with `(await ...)` to drive the proxy.
    // `flow_env` used to be wrapped here too (it was an async Deno op-backed
    // proxy in the deno_core era); QuickJS now exposes flow_env as a plain
    // in-memory object, so no await is needed.
    static ref RE: Regex = Regex::new(
        r#"(?m)(?P<r>results(?:\?)?(?:(?:\.[a-zA-Z_0-9]+)|(?:\[\".*?\"\])))"#
    )
    .unwrap();
    // SQL fast-path: simple `results.X.Y[i]...` accesses are dispatched to
    // the API endpoint to fetch a specific result without spinning the eval
    // engine. flow_env is no longer dispatched here because QuickJS reads
    // it directly from the in-process global set up by `eval_quickjs_inner`.
    static ref RE_FULL: Regex = Regex::new(
        r"(?m)^results(?:\?)?\.([a-zA-Z_0-9]+)(?:\[(\d+)\])?((?:\.[a-zA-Z_0-9]+)+)?$"
    )
    .unwrap();
}

// ── Shared helper functions (quickjs-only) ──────────────────────────

#[cfg(feature = "quickjs")]
pub fn replace_with_await(expr: String, fn_name: &str) -> String {
    let sep = format!("{}(", fn_name);
    let mut split = expr.split(&sep);
    let mut s = split.next().unwrap_or("").to_string();
    for x in split {
        s.push_str(&format!("(await {}({}", fn_name, add_closing_bracket(x)))
    }
    s
}

#[cfg(feature = "quickjs")]
pub fn replace_with_await_result(expr: String) -> String {
    RE.replace_all(&expr, "(await $r)").to_string()
}

#[cfg(feature = "quickjs")]
fn add_closing_bracket(s: &str) -> String {
    let mut s = s.to_string();
    let mut level = 1;
    let mut idx = 0;
    for c in s.chars() {
        match c {
            '(' => level += 1,
            ')' => level -= 1,
            _ => (),
        };
        if level == 0 {
            break;
        }
        idx += 1;
    }
    s.insert_str(idx, ")");
    s
}

pub fn try_exact_property_access(
    expr: &str,
    flow_input: Option<&mappable_rc::Marc<HashMap<String, Box<RawValue>>>>,
    flow_env: Option<&HashMap<String, Box<RawValue>>>,
) -> Option<Box<RawValue>> {
    let obj = if expr.starts_with(FLOW_INPUT_PREFIX) {
        Some((
            FLOW_INPUT_PREFIX,
            flow_input.as_ref().map(|obj| obj.as_ref()),
        ))
    } else if expr.starts_with(ENV_KEY_PREFIX) {
        Some((ENV_KEY_PREFIX, flow_env))
    } else {
        None
    };

    if let Some((prefix, obj)) = obj {
        let access_pattern_pos = prefix.len();
        let suffix = &expr[access_pattern_pos..];
        let maybe_key_name = if suffix.starts_with(DOT_PATTERN) {
            let key_name_pos = DOT_PATTERN.len();
            Some(&suffix[key_name_pos..])
        } else if suffix.starts_with(START_BRACKET_PATTERN) {
            let key_name_pos = START_BRACKET_PATTERN.len();
            let suffix = &suffix[key_name_pos..];

            let flow_arg_name = suffix
                .ends_with(END_BRACKET_PATTERN)
                .then(|| {
                    let start_key_name_pos = access_pattern_pos + key_name_pos;
                    let end_key_name_pos = expr.len() - END_BRACKET_PATTERN.len();
                    &expr[start_key_name_pos..end_key_name_pos]
                })
                .filter(|s| s.len() > 0);
            flow_arg_name
        } else {
            None
        };

        if let Some(key_name) = maybe_key_name {
            if let Some(key_value) = obj.and_then(|obj| obj.get(key_name)) {
                return Some(key_value.clone());
            }
        }
    }
    None
}

/// JS runtime properties (not methods) that cannot be resolved by PostgreSQL's
/// #> JSON path operator. Function calls like .map(...) already don't match the
/// RE_FULL regex due to parentheses, so only property accesses need listing here.
const JS_ONLY_PROPERTIES: &[&str] = &["length"];

fn ends_with_js_only_property(rest: Option<&str>) -> bool {
    match rest {
        None => false,
        Some(rest) => {
            let last_segment = rest.rsplit('.').next().unwrap_or("");
            JS_ONLY_PROPERTIES.contains(&last_segment)
        }
    }
}

pub async fn handle_full_regex(
    expr: &str,
    authed_client: &AuthedClient,
    by_id: &IdContext,
) -> Option<anyhow::Result<Box<RawValue>>> {
    if let Some(captures) = RE_FULL.captures(&expr) {
        let obj_key = captures.get(1).unwrap().as_str();
        let idx_o = captures.get(2).map(|y| y.as_str());
        let rest = captures.get(3).map(|y| y.as_str());

        // Skip the SQL fast path when the expression accesses a JS runtime
        // property (e.g. .length) that the PostgreSQL #> operator can't resolve.
        if ends_with_js_only_property(rest) {
            return None;
        }

        let query = if let Some(idx) = idx_o {
            match rest {
                Some(rest) => Some(format!("{}{}", idx, rest)),
                None => Some(idx.to_string()),
            }
        } else {
            rest.map(|x| x.trim_start_matches('.').to_string())
        };

        let res = authed_client
            .get_result_by_id::<Option<Box<RawValue>>>(&by_id.flow_job.to_string(), obj_key, query)
            .await
            .ok()
            .flatten();
        let result = match res {
            Some(v) => Ok(v),
            None => serde_json::value::to_raw_value(&serde_json::Value::Null)
                .map_err(|e| anyhow::anyhow!("Failed to serialize null: {}", e)),
        };
        return Some(result);
    }
    None
}

#[cfg(feature = "quickjs")]
use windmill_common::utils::unsafe_raw;

// ── QuickJS evaluation ───────────────────────────────────────────────

#[cfg(feature = "quickjs")]

/// Shared state for async operations within QuickJS
#[derive(Clone)]
struct AsyncOpState {
    client: AuthedClient,
}

#[cfg(feature = "quickjs")]
pub async fn eval_timeout_quickjs(
    expr: String,
    transform_context: HashMap<String, Arc<Box<RawValue>>>,
    flow_input: Option<mappable_rc::Marc<HashMap<String, Box<RawValue>>>>,
    flow_env: Option<&HashMap<String, Box<RawValue>>>,
    authed_client: Option<&AuthedClient>,
    by_id: Option<&IdContext>,
    ctx: Option<Vec<(String, String)>>,
) -> anyhow::Result<Box<RawValue>> {
    let expr = expr.trim().to_string();

    tracing::debug!(
        "evaluating js eval (quickjs): {} with context {:?}",
        expr,
        transform_context
    );

    // Clone data for the blocking task
    let by_id_clone = by_id.cloned();
    let flow_input_clone = flow_input.clone();
    let flow_env_clone = flow_env.cloned();
    let authed_client_clone = authed_client.cloned();

    // Determine which context keys are actually used in the expression
    let p_ids = by_id.map(|x| {
        [
            format!("results.{}", x.previous_id),
            format!("results?.{}", x.previous_id),
            format!("results[\"{}\"]", x.previous_id),
            format!("results?.[\"{}\"]", x.previous_id),
        ]
    });

    let mut context_keys: Vec<String> = transform_context
        .keys()
        .filter(|x| expr.contains(&x.to_string()))
        .cloned()
        .collect();

    if (!context_keys.contains(&"previous_result".to_string())
        && p_ids.is_some()
        && p_ids.as_ref().unwrap().iter().any(|x| expr.contains(x)))
        || expr.contains("error")
    {
        context_keys.push("previous_result".to_string());
    }

    let has_flow_input = expr.contains("flow_input");
    if has_flow_input {
        context_keys.push("flow_input".to_string())
    }

    // Filter transform_context to only include used keys
    let filtered_context: HashMap<String, Arc<Box<RawValue>>> = transform_context
        .into_iter()
        .filter(|(k, _)| context_keys.contains(k))
        .collect();

    let expr_clone = expr.clone();

    // Run the QuickJS evaluation with a timeout.
    // Use the current runtime handle rather than creating an independent
    // current-thread runtime so that HTTP connections opened by the global
    // reqwest client (HTTP_CLIENT) are managed by the main runtime.
    // Creating a child runtime caused connection-pool dispatch tasks to be
    // dropped when the child runtime exited, poisoning pooled connections
    // and producing spurious "DispatchGone" / "runtime dropped the dispatch
    // task" errors on subsequent requests from the main runtime.
    let handle = tokio::runtime::Handle::current();
    tokio::time::timeout(
        std::time::Duration::from_millis(EVAL_TIMEOUT_MS),
        tokio::task::spawn_blocking(move || {
            handle.block_on(async move {
                eval_quickjs_inner(
                    &expr_clone,
                    filtered_context,
                    flow_input_clone,
                    flow_env_clone,
                    authed_client_clone,
                    by_id_clone,
                    ctx,
                    context_keys,
                )
                .await
            })
        }),
    )
    .await
    .map_err(|_| {
        anyhow::anyhow!(
            "The expression evaluation `{expr}` took too long to execute (>{EVAL_TIMEOUT_MS}ms)"
        )
    })??
}

#[cfg(feature = "quickjs")]
const QUICKJS_MEMORY_LIMIT: usize = 32 * 1024 * 1024;

#[cfg(feature = "quickjs")]
async fn eval_quickjs_inner(
    expr: &str,
    transform_context: HashMap<String, Arc<Box<RawValue>>>,
    flow_input: Option<mappable_rc::Marc<HashMap<String, Box<RawValue>>>>,
    flow_env: Option<HashMap<String, Box<RawValue>>>,
    authed_client: Option<AuthedClient>,
    by_id: Option<IdContext>,
    extra_ctx: Option<Vec<(String, String)>>,
    context_keys: Vec<String>,
) -> anyhow::Result<Box<RawValue>> {
    let runtime = AsyncRuntime::new()?;
    runtime.set_memory_limit(QUICKJS_MEMORY_LIMIT).await;
    let context = AsyncContext::full(&runtime).await?;

    // Create shared state for async ops if we have a client
    let op_state = authed_client.map(|client| Arc::new(AsyncOpState { client }));

    let op_state_clone = op_state.clone();
    let by_id_clone = by_id.clone();

    // Transform expression to add await for variable/resource/results access
    let expr_with_funcs = ["variable", "resource", "flow_user_state"]
        .into_iter()
        .fold(expr.to_string(), replace_with_await);
    let transformed_expr = replace_with_await_result(expr_with_funcs);

    async_with!(context => |ctx| {
        let globals = ctx.globals();

        // Set up context variables
        for key in &context_keys {
            if key == "flow_input" {
                if let Some(ref fi) = flow_input {
                    let json_str = serde_json::to_string(fi.as_ref())?;
                    let val: serde_json::Value = serde_json::from_str(&json_str)?;
                    let js_val = json_to_js(&ctx, &val)?;
                    globals.set(key.as_str(), js_val)?;
                } else {
                    globals.set(key.as_str(), Value::new_null(ctx.clone()))?;
                }
            } else if let Some(raw_val) = transform_context.get(key) {
                let val: serde_json::Value = serde_json::from_str(raw_val.get())?;
                let js_val = json_to_js(&ctx, &val)?;
                globals.set(key.as_str(), js_val)?;
            }
        }

        // Set up flow_env if referenced
        if expr.contains("flow_env") {
            if let Some(ref fe) = flow_env {
                let obj = Object::new(ctx.clone())?;
                for (k, v) in fe {
                    let val: serde_json::Value = serde_json::from_str(v.get())?;
                    let js_val = json_to_js(&ctx, &val)?;
                    obj.set(k.as_str(), js_val)?;
                }
                globals.set("flow_env", obj)?;
            } else {
                globals.set("flow_env", Object::new(ctx.clone())?)?;
            }
        }

        // Set up additional context variables
        if let Some(ctx_vars) = extra_ctx {
            for (k, v) in ctx_vars {
                globals.set(k.as_str(), v.as_str())?;
            }
        }

        // Set up error extraction if needed
        if expr.contains("error") && context_keys.contains(&"previous_result".to_string()) {
            let error_setup = r#"
                let error = previous_result?.error;
                if (!error) {
                    if (Array.isArray(previous_result)) {
                        const errors = previous_result.filter(item => item && typeof item === 'object' && 'error' in item);
                        if (errors.length === 1) {
                            error = errors[0].error;
                        } else if (errors.length > 1) {
                            error = {
                                name: 'MultipleErrors',
                                message: errors.map(({ error: e }, i) => `[${e.step_id || i}] ${e.message || e.name}`).join('; '),
                                errors: previous_result
                            };
                        } else {
                            error = {
                                name: 'MultipleErrors',
                                message: "Could not parse errors",
                                errors: previous_result
                            };
                        }
                    } else {
                        if (previous_result) {
                            error = { name: 'UnknownError', message: 'Could not parse the error', error: previous_result };
                        } else {
                            error = { name: 'UnknownError', message: 'No error found' };
                        }
                    }
                }
            "#;
            ctx.eval::<(), _>(error_setup).catch(&ctx).map_err(quickjs_error_to_anyhow)?;
        }

        // Set up async functions if we have a client
        if let Some(ref state) = op_state_clone {
            setup_async_ops(&ctx, &globals, state.clone())?;
        } else {
            // Set up stub functions that throw errors
            setup_stub_functions(&ctx, &globals)?;
        }

        // Set up results proxy if we have by_id context
        if let Some(ref by_id) = by_id_clone {
            setup_results_proxy(&ctx, &globals, by_id, op_state_clone.clone())?;
        }

        // The auto-await rewrite at the top of eval_timeout always rewrites
        // `flow_user_state(...)` regardless of context, so install a stub if
        // nothing above defined it (e.g. authed sleep transforms with no by_id).
        ctx.eval::<(), _>(r#"
            if (typeof flow_user_state === 'undefined') {
                globalThis.flow_user_state = function(key) {
                    return Promise.reject(new Error(`flow_user_state() is not available in this context`));
                };
            }
        "#)
        .catch(&ctx)
        .map_err(quickjs_error_to_anyhow)?;

        // Determine if we need to add return statement.
        let code = if should_add_return_quickjs(&transformed_expr) {
            format!("(async function() {{ return {}; }})().then((x) => JSON.stringify(x ?? null))", transformed_expr)
        } else {
            format!("(async function() {{ {} }})().then((x) => JSON.stringify(x ?? null))", transformed_expr)
        };

        // Evaluate the expression (returns a Promise that resolves to a JSON string)
        let promise: rquickjs::Promise = ctx.eval(code).catch(&ctx).map_err(quickjs_error_to_anyhow)?;

        // Await the promise
        let result: Value = promise.into_future().await.catch(&ctx).map_err(quickjs_error_to_anyhow)?;

        let json_str = String::from_js(&ctx, result)
            .unwrap_or_else(|_| "null".to_string());

        Ok(unsafe_raw(json_str))
    })
    .await
}

#[cfg(feature = "quickjs")]
fn setup_async_ops<'js>(
    ctx: &rquickjs::Ctx<'js>,
    globals: &Object<'js>,
    state: Arc<AsyncOpState>,
) -> anyhow::Result<()> {
    const ERR_PREFIX: &str = "\x00__WINDMILL_ERR__\x00";

    let state_for_var = state.clone();
    globals.set(
        "__fetchVariable",
        Func::from(Async(MutFn::new(move |path: String| {
            let client = state_for_var.client.clone();
            async move {
                match client.get_variable_value(&path).await {
                    Ok(value) => value,
                    Err(e) => format!("{}{}", ERR_PREFIX, e),
                }
            }
        }))),
    )?;

    let state_for_res = state.clone();
    globals.set(
        "__fetchResource",
        Func::from(Async(MutFn::new(move |path: String| {
            let client = state_for_res.client.clone();
            async move {
                match client
                    .get_resource_value_interpolated::<serde_json::Value>(&path, None)
                    .await
                {
                    Ok(value) => {
                        serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string())
                    }
                    Err(e) => format!("{}{}", ERR_PREFIX, e),
                }
            }
        }))),
    )?;

    let wrapper_code = r#"
        const __ERR_PREFIX = '\x00__WINDMILL_ERR__\x00';

        async function variable(path) {
            const result = await __fetchVariable(path);
            if (typeof result === 'string' && result.startsWith(__ERR_PREFIX)) {
                throw new Error(result.substring(__ERR_PREFIX.length));
            }
            return result;
        }

        async function resource(path) {
            const result = await __fetchResource(path);
            if (typeof result === 'string' && result.startsWith(__ERR_PREFIX)) {
                throw new Error(result.substring(__ERR_PREFIX.length));
            }
            return JSON.parse(result);
        }
    "#;

    ctx.eval::<(), _>(wrapper_code)
        .catch(ctx)
        .map_err(quickjs_error_to_anyhow)?;

    Ok(())
}

#[cfg(feature = "quickjs")]
fn setup_stub_functions<'js>(
    ctx: &rquickjs::Ctx<'js>,
    _globals: &Object<'js>,
) -> anyhow::Result<()> {
    let setup_code = r#"
        function variable(path) {
            return Promise.reject(new Error(`variable() is not available without an authenticated client`));
        }

        function resource(path) {
            return Promise.reject(new Error(`resource() is not available without an authenticated client`));
        }

        function flow_user_state(key) {
            return Promise.reject(new Error(`flow_user_state() is not available without an authenticated client`));
        }
    "#;

    ctx.eval::<(), _>(setup_code)
        .catch(ctx)
        .map_err(quickjs_error_to_anyhow)?;

    Ok(())
}

#[cfg(feature = "quickjs")]
fn setup_results_proxy<'js>(
    ctx: &rquickjs::Ctx<'js>,
    globals: &Object<'js>,
    by_id: &IdContext,
    op_state: Option<Arc<AsyncOpState>>,
) -> anyhow::Result<()> {
    globals.set("__previous_id", by_id.previous_id.clone())?;

    if let Some(state) = op_state {
        let by_id_for_result = by_id.clone();
        let state_for_user_state = state.clone();
        let root_flow_job_id_for_state = by_id.root_flow_job.to_string();
        globals.set(
            "__fetchResult",
            Func::from(Async(MutFn::new(move |step_id: String| {
                let client = state.client.clone();
                let by_id = by_id_for_result.clone();
                let step_id_clone = step_id.clone();

                let job_result = by_id.steps_results.get(&step_id).cloned();
                let flow_job_id = by_id.flow_job.to_string();

                async move {
                    const ERR_PREFIX: &str = "\x00__WINDMILL_ERR__\x00";

                    let result: Result<serde_json::Value, String> = match job_result {
                        Some(jr) => match jr {
                            JobResult::SingleJob(job_id) => client
                                .get_completed_job_result::<serde_json::Value>(
                                    &job_id.to_string(),
                                    None,
                                )
                                .await
                                .map_err(|e| {
                                    format!(
                                        "Failed to fetch result for step '{}': {}",
                                        step_id_clone, e
                                    )
                                }),
                            JobResult::ListJob(job_ids) => {
                                let futs = job_ids.iter().map(|job_id| {
                                    let client = client.clone();
                                    let job_id_str = job_id.to_string();
                                    async move {
                                        client
                                            .get_completed_job_result::<serde_json::Value>(
                                                &job_id_str,
                                                None,
                                            )
                                            .await
                                    }
                                });
                                let results: Vec<_> = futures::future::join_all(futs).await;
                                let collected: Result<Vec<_>, _> = results.into_iter().collect();
                                collected.map(serde_json::Value::Array).map_err(|e| {
                                    format!(
                                        "Failed to fetch results for step '{}': {}",
                                        step_id_clone, e
                                    )
                                })
                            }
                        },
                        None => Ok(client
                            .get_result_by_id::<serde_json::Value>(
                                &flow_job_id,
                                &step_id_clone,
                                None,
                            )
                            .await
                            .ok()
                            .unwrap_or(serde_json::Value::Null)),
                    };

                    match result {
                        Ok(value) => {
                            serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string())
                        }
                        Err(e) => format!("{}{}", ERR_PREFIX, e),
                    }
                }
            }))),
        )?;

        globals.set(
            "__fetchFlowUserState",
            Func::from(Async(MutFn::new(move |key: String| {
                let client = state_for_user_state.client.clone();
                let job_id = root_flow_job_id_for_state.clone();
                async move {
                    const ERR_PREFIX: &str = "\x00__WINDMILL_ERR__\x00";
                    match client.get_flow_user_state(&job_id, &key).await {
                        Ok(value) => {
                            serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string())
                        }
                        Err(e) => format!("{}{}", ERR_PREFIX, e),
                    }
                }
            }))),
        )?;

        let wrapper_code = r#"
            const __RESULT_ERR_PREFIX = '\x00__WINDMILL_ERR__\x00';
            function __throwOrParse(result) {
                if (typeof result === 'string' && result.startsWith(__RESULT_ERR_PREFIX)) {
                    throw new Error(result.substring(__RESULT_ERR_PREFIX.length));
                }
                return JSON.parse(result);
            }

            async function __getResult(stepId) {
                return __throwOrParse(await __fetchResult(stepId));
            }

            async function flow_user_state(key) {
                return __throwOrParse(await __fetchFlowUserState(key));
            }
        "#;
        ctx.eval::<(), _>(wrapper_code)
            .catch(ctx)
            .map_err(quickjs_error_to_anyhow)?;
    } else {
        let stub_code = r#"
            function __getResult(stepId) {
                return Promise.reject(new Error('Result fetching not available without authenticated client'));
            }

            function flow_user_state(key) {
                return Promise.reject(new Error(`flow_user_state() is not available without an authenticated client`));
            }
        "#;
        ctx.eval::<(), _>(stub_code)
            .catch(ctx)
            .map_err(quickjs_error_to_anyhow)?;
    }

    let proxy_setup = r#"
        const results = new Proxy({}, {
            get: function(target, name, receiver) {
                if (typeof name === 'symbol') {
                    return undefined;
                }
                if (name === __previous_id && typeof previous_result !== 'undefined') {
                    return Promise.resolve(previous_result);
                }
                return __getResult(name);
            }
        });
    "#;
    ctx.eval::<(), _>(proxy_setup)
        .catch(ctx)
        .map_err(quickjs_error_to_anyhow)?;

    Ok(())
}

#[cfg(feature = "quickjs")]
fn json_to_js<'js>(
    ctx: &rquickjs::Ctx<'js>,
    val: &serde_json::Value,
) -> rquickjs::Result<Value<'js>> {
    match val {
        serde_json::Value::Null => Ok(Value::new_null(ctx.clone())),
        serde_json::Value::Bool(b) => Ok(Value::new_bool(ctx.clone(), *b)),
        serde_json::Value::Number(n) => {
            if let Some(i) = n.as_i64() {
                if i >= i32::MIN as i64 && i <= i32::MAX as i64 {
                    Ok(Value::new_int(ctx.clone(), i as i32))
                } else {
                    Ok(Value::new_float(ctx.clone(), i as f64))
                }
            } else if let Some(f) = n.as_f64() {
                Ok(Value::new_float(ctx.clone(), f))
            } else {
                Ok(Value::new_float(ctx.clone(), 0.0))
            }
        }
        serde_json::Value::String(s) => s.clone().into_js(ctx),
        serde_json::Value::Array(arr) => {
            let js_arr = rquickjs::Array::new(ctx.clone())?;
            for (i, item) in arr.iter().enumerate() {
                js_arr.set(i, json_to_js(ctx, item)?)?;
            }
            Ok(js_arr.into_value())
        }
        serde_json::Value::Object(obj) => {
            let js_obj = Object::new(ctx.clone())?;
            for (k, v) in obj {
                js_obj.set(k.as_str(), json_to_js(ctx, v)?)?;
            }
            Ok(js_obj.into_value())
        }
    }
}

#[cfg(feature = "quickjs")]
fn should_add_return_quickjs(expr: &str) -> bool {
    let trimmed = expr.trim();

    if trimmed.is_empty() {
        return true;
    }

    if trimmed.starts_with("return ") || trimmed.starts_with("return;") || trimmed == "return" {
        return false;
    }

    let statement_prefixes = [
        "const ",
        "let ",
        "var ",
        "if ",
        "if(",
        "for ",
        "for(",
        "while ",
        "while(",
        "switch ",
        "switch(",
        "try ",
        "try{",
        "throw ",
        "function ",
        "class ",
        "async ",
        "await ",
    ];

    for prefix in &statement_prefixes {
        if trimmed.starts_with(prefix) {
            return false;
        }
    }

    if contains_semicolon_outside_strings(trimmed) {
        return false;
    }

    true
}

#[cfg(feature = "quickjs")]
fn contains_semicolon_outside_strings(expr: &str) -> bool {
    let mut in_single_quote = false;
    let mut in_double_quote = false;
    let mut in_template = false;
    let mut prev_char = '\0';

    for ch in expr.chars() {
        match ch {
            '\'' if prev_char != '\\' && !in_double_quote && !in_template => {
                in_single_quote = !in_single_quote;
            }
            '"' if prev_char != '\\' && !in_single_quote && !in_template => {
                in_double_quote = !in_double_quote;
            }
            '`' if prev_char != '\\' && !in_single_quote && !in_double_quote => {
                in_template = !in_template;
            }
            ';' if !in_single_quote && !in_double_quote && !in_template => {
                return true;
            }
            _ => {}
        }
        prev_char = ch;
    }

    false
}

#[cfg(feature = "quickjs")]
fn quickjs_error_to_anyhow(err: rquickjs::CaughtError<'_>) -> anyhow::Error {
    anyhow::anyhow!("QuickJS evaluation error: {}", err)
}

// ── eval_simple_js for windmill-api batch rerun ──────────────────────

#[cfg(feature = "quickjs")]
pub async fn eval_simple_js(
    expr: String,
    globals: HashMap<String, serde_json::Value>,
) -> anyhow::Result<Box<RawValue>> {
    let handle = tokio::runtime::Handle::current();
    tokio::time::timeout(
        std::time::Duration::from_millis(EVAL_TIMEOUT_MS),
        tokio::task::spawn_blocking(move || {
            handle.block_on(async move {
                let runtime = AsyncRuntime::new()?;
                runtime.set_memory_limit(QUICKJS_MEMORY_LIMIT).await;
                let context = AsyncContext::full(&runtime).await?;

                async_with!(context => |ctx| {
                    let js_globals = ctx.globals();

                    // Set up each named global
                    for (name, value) in &globals {
                        let js_val = json_to_js(&ctx, value)?;
                        js_globals.set(name.as_str(), js_val)?;
                    }

                    // Wrap expression to return JSON string
                    let code = format!("JSON.stringify(({}) ?? null)", expr);
                    let result: String = ctx.eval(code)
                        .catch(&ctx)
                        .map_err(quickjs_error_to_anyhow)?;

                    Ok(unsafe_raw(result))
                })
                .await
            })
        }),
    )
    .await
    .map_err(|_| {
        anyhow::anyhow!("The expression evaluation took too long to execute (>{EVAL_TIMEOUT_MS}ms)")
    })??
}

// ── Fallback stubs when quickjs is disabled ──────────────────────────

#[cfg(not(feature = "quickjs"))]
pub async fn eval_timeout_quickjs(
    _expr: String,
    _transform_context: HashMap<String, Arc<Box<RawValue>>>,
    _flow_input: Option<mappable_rc::Marc<HashMap<String, Box<RawValue>>>>,
    _flow_env: Option<&HashMap<String, Box<RawValue>>>,
    _authed_client: Option<&windmill_common::client::AuthedClient>,
    _by_id: Option<&IdContext>,
    _ctx: Option<Vec<(String, String)>>,
) -> anyhow::Result<Box<RawValue>> {
    anyhow::bail!("JavaScript expression evaluation requires the `quickjs` feature. Enable it with: cargo build --features quickjs")
}

#[cfg(not(feature = "quickjs"))]
pub async fn eval_simple_js(
    _expr: String,
    _globals: HashMap<String, serde_json::Value>,
) -> anyhow::Result<Box<RawValue>> {
    anyhow::bail!("JavaScript expression evaluation requires the `quickjs` feature. Enable it with: cargo build --features quickjs")
}

// ── Tests ────────────────────────────────────────────────────────────

#[cfg(all(test, feature = "quickjs"))]
mod tests {
    use super::*;
    use serde_json::json;
    use windmill_common::worker::to_raw_value;

    /// Helper: evaluate an expression with a transform context and assert against expected JSON.
    async fn assert_eval(
        expr: &str,
        ctx: HashMap<String, Arc<Box<RawValue>>>,
        expected: serde_json::Value,
    ) {
        assert_eval_full(expr, ctx, None, None, expected).await;
    }

    /// Helper: evaluate with full context (transform_context, flow_input, flow_env).
    async fn assert_eval_full(
        expr: &str,
        ctx: HashMap<String, Arc<Box<RawValue>>>,
        flow_input: Option<mappable_rc::Marc<HashMap<String, Box<RawValue>>>>,
        flow_env: Option<&HashMap<String, Box<RawValue>>>,
        expected: serde_json::Value,
    ) {
        let result = eval_timeout_quickjs(
            expr.to_string(),
            ctx,
            flow_input,
            flow_env,
            None,
            None,
            None,
        )
        .await
        .unwrap_or_else(|e| panic!("eval_timeout_quickjs failed for '{}': {}", expr, e));

        let actual: serde_json::Value = serde_json::from_str(result.get()).unwrap_or_else(|e| {
            panic!(
                "Failed to parse result '{}' for '{}': {}",
                result.get(),
                expr,
                e
            )
        });

        assert_eq!(actual, expected, "Mismatch for expression '{}'", expr);
    }

    // =====================================================================
    // HELPER FUNCTION TESTS
    // =====================================================================

    #[test]
    fn test_should_add_return_quickjs() {
        assert!(should_add_return_quickjs("5"));
        assert!(should_add_return_quickjs("x + y"));
        assert!(should_add_return_quickjs("foo()"));
        assert!(should_add_return_quickjs("obj.method()"));
        assert!(should_add_return_quickjs("a > b ? 'yes' : 'no'"));
        assert!(should_add_return_quickjs("({ key: 'value' })"));

        assert!(!should_add_return_quickjs("return 5"));
        assert!(!should_add_return_quickjs("return x + y"));
        assert!(!should_add_return_quickjs("const x = 5"));
        assert!(!should_add_return_quickjs("let y = 10"));
        assert!(!should_add_return_quickjs("if (x > 5) { return x; }"));
        assert!(!should_add_return_quickjs("let x = 5; x + 1"));
        assert!(!should_add_return_quickjs("try { return 1; } catch(e) {}"));
    }

    #[test]
    fn test_contains_semicolon_outside_strings() {
        assert!(contains_semicolon_outside_strings("a; b"));
        assert!(contains_semicolon_outside_strings("let x = 5; x + 1"));

        assert!(!contains_semicolon_outside_strings("'a;b'"));
        assert!(!contains_semicolon_outside_strings("\"a;b\""));
        assert!(!contains_semicolon_outside_strings("`a;b`"));
        assert!(!contains_semicolon_outside_strings("x + y"));
    }

    #[test]
    fn test_replace_with_await() {
        assert_eq!(
            replace_with_await("variable('test')".to_string(), "variable"),
            "(await variable('test'))"
        );
        assert_eq!(
            replace_with_await("x + variable('a') + variable('b')".to_string(), "variable"),
            "x + (await variable('a')) + (await variable('b'))"
        );
        assert_eq!(
            replace_with_await("no_match".to_string(), "variable"),
            "no_match"
        );
    }

    #[test]
    fn test_replace_with_await_result() {
        assert_eq!(
            replace_with_await_result("results.step_a".to_string()),
            "(await results.step_a)"
        );
        assert_eq!(
            replace_with_await_result("results.a + results.b".to_string()),
            "(await results.a) + (await results.b)"
        );
        assert_eq!(
            replace_with_await_result("no_results_here".to_string()),
            "no_results_here"
        );
    }

    #[test]
    fn test_try_exact_property_access_flow_input_dot() {
        let mut fi = HashMap::new();
        fi.insert("name".to_string(), to_raw_value(&json!("hello")));
        let fi = mappable_rc::Marc::new(fi);

        let result = try_exact_property_access("flow_input.name", Some(&fi), None);
        assert!(result.is_some());
        assert_eq!(result.unwrap().get(), "\"hello\"");
    }

    #[test]
    fn test_try_exact_property_access_flow_input_bracket() {
        let mut fi = HashMap::new();
        fi.insert("my_key".to_string(), to_raw_value(&json!(42)));
        let fi = mappable_rc::Marc::new(fi);

        let result = try_exact_property_access("flow_input[\"my_key\"]", Some(&fi), None);
        assert!(result.is_some());
        assert_eq!(result.unwrap().get(), "42");
    }

    #[test]
    fn test_try_exact_property_access_flow_env() {
        let mut fe = HashMap::new();
        fe.insert("ENV".to_string(), to_raw_value(&json!("production")));

        let result = try_exact_property_access("flow_env.ENV", None, Some(&fe));
        assert!(result.is_some());
        assert_eq!(result.unwrap().get(), "\"production\"");
    }

    #[test]
    fn test_try_exact_property_access_missing_key() {
        let fi = mappable_rc::Marc::new(HashMap::new());
        let result = try_exact_property_access("flow_input.missing", Some(&fi), None);
        assert!(result.is_none());
    }

    #[test]
    fn test_try_exact_property_access_no_prefix() {
        let result = try_exact_property_access("some_var.key", None, None);
        assert!(result.is_none());
    }

    // =====================================================================
    // SIMPLE ARITHMETIC
    // =====================================================================

    #[tokio::test]
    async fn test_simple_arithmetic() {
        let mut env = HashMap::new();
        env.insert("x".to_string(), Arc::new(to_raw_value(&json!(5))));
        env.insert("y".to_string(), Arc::new(to_raw_value(&json!(3))));

        assert_eval("x + y", env.clone(), json!(8)).await;
        assert_eval("x - y", env.clone(), json!(2)).await;
        assert_eval("x * y", env.clone(), json!(15)).await;
        assert_eval("x % y", env.clone(), json!(2)).await;
        assert_eval("x ** 2", env.clone(), json!(25)).await;
    }

    // =====================================================================
    // OBJECT PROPERTY ACCESS
    // =====================================================================

    #[tokio::test]
    async fn test_object_property_access() {
        let mut env = HashMap::new();
        env.insert(
            "obj".to_string(),
            Arc::new(to_raw_value(&json!({
                "name": "test",
                "value": 42,
                "nested": {"deep": {"property": "found"}}
            }))),
        );

        assert_eval("obj.name", env.clone(), json!("test")).await;
        assert_eval("obj.value", env.clone(), json!(42)).await;
        assert_eval("obj.nested.deep.property", env.clone(), json!("found")).await;
        assert_eval("obj['name']", env.clone(), json!("test")).await;
    }

    // =====================================================================
    // ARRAY OPERATIONS
    // =====================================================================

    #[tokio::test]
    async fn test_array_operations() {
        let mut env = HashMap::new();
        env.insert(
            "arr".to_string(),
            Arc::new(to_raw_value(&json!([1, 2, 3, 4, 5]))),
        );

        assert_eval("arr.length", env.clone(), json!(5)).await;
        assert_eval("arr[0]", env.clone(), json!(1)).await;
        assert_eval("arr.map(x => x * 2)", env.clone(), json!([2, 4, 6, 8, 10])).await;
        assert_eval("arr.filter(x => x > 2)", env.clone(), json!([3, 4, 5])).await;
        assert_eval("arr.reduce((a, b) => a + b, 0)", env.clone(), json!(15)).await;
        assert_eval("arr.find(x => x > 3)", env.clone(), json!(4)).await;
        assert_eval("arr.some(x => x > 4)", env.clone(), json!(true)).await;
        assert_eval("arr.every(x => x > 0)", env.clone(), json!(true)).await;
    }

    // =====================================================================
    // STRING OPERATIONS
    // =====================================================================

    #[tokio::test]
    async fn test_string_operations() {
        let mut env = HashMap::new();
        env.insert(
            "s".to_string(),
            Arc::new(to_raw_value(&json!("Hello World"))),
        );

        assert_eval("s.toLowerCase()", env.clone(), json!("hello world")).await;
        assert_eval("s.toUpperCase()", env.clone(), json!("HELLO WORLD")).await;
        assert_eval("s.length", env.clone(), json!(11)).await;
        assert_eval("s.split(' ')", env.clone(), json!(["Hello", "World"])).await;
        assert_eval(
            "s.replace('World', 'QuickJS')",
            env.clone(),
            json!("Hello QuickJS"),
        )
        .await;
        assert_eval("s.includes('World')", env.clone(), json!(true)).await;
        assert_eval("s.startsWith('Hello')", env.clone(), json!(true)).await;
        assert_eval("s.trim()", env.clone(), json!("Hello World")).await;
    }

    // =====================================================================
    // TERNARY AND CONDITIONALS
    // =====================================================================

    #[tokio::test]
    async fn test_ternary_and_conditionals() {
        let mut env = HashMap::new();
        env.insert("x".to_string(), Arc::new(to_raw_value(&json!(10))));
        env.insert("y".to_string(), Arc::new(to_raw_value(&json!(5))));

        assert_eval("x > y ? 'bigger' : 'smaller'", env.clone(), json!("bigger")).await;
        assert_eval("x === 10 ? true : false", env.clone(), json!(true)).await;
        assert_eval("x > 5 && y < 10", env.clone(), json!(true)).await;
        assert_eval("x > 20 || y < 10", env.clone(), json!(true)).await;
        assert_eval("!false", env.clone(), json!(true)).await;
    }

    // =====================================================================
    // OBJECT CREATION
    // =====================================================================

    #[tokio::test]
    async fn test_object_creation() {
        let mut env = HashMap::new();
        env.insert("name".to_string(), Arc::new(to_raw_value(&json!("test"))));
        env.insert("value".to_string(), Arc::new(to_raw_value(&json!(42))));

        assert_eval("({ foo: 'bar' })", env.clone(), json!({"foo": "bar"})).await;
        assert_eval(
            "({ name, value })",
            env.clone(),
            json!({"name": "test", "value": 42}),
        )
        .await;
        assert_eval(
            "({ ...{ a: 1 }, b: 2 })",
            env.clone(),
            json!({"a": 1, "b": 2}),
        )
        .await;
    }

    // =====================================================================
    // NULL / UNDEFINED
    // =====================================================================

    #[tokio::test]
    async fn test_null_undefined() {
        assert_eval("null", HashMap::new(), json!(null)).await;
        assert_eval("undefined", HashMap::new(), json!(null)).await;

        let mut env = HashMap::new();
        env.insert("x".to_string(), Arc::new(to_raw_value(&json!(null))));
        assert_eval("x", env.clone(), json!(null)).await;
        assert_eval("x ?? 'default'", env.clone(), json!("default")).await;
    }

    // =====================================================================
    // FLOW INPUT
    // =====================================================================

    #[tokio::test]
    async fn test_flow_input() {
        let mut fi = HashMap::new();
        fi.insert("name".to_string(), to_raw_value(&json!("test_flow")));
        fi.insert("count".to_string(), to_raw_value(&json!(100)));
        fi.insert(
            "config".to_string(),
            to_raw_value(&json!({"enabled": true})),
        );

        let fi = Some(mappable_rc::Marc::new(fi));

        assert_eval_full(
            "flow_input.name",
            HashMap::new(),
            fi.clone(),
            None,
            json!("test_flow"),
        )
        .await;
        assert_eval_full(
            "flow_input.count",
            HashMap::new(),
            fi.clone(),
            None,
            json!(100),
        )
        .await;
        assert_eval_full(
            "flow_input.config.enabled",
            HashMap::new(),
            fi.clone(),
            None,
            json!(true),
        )
        .await;
    }

    // =====================================================================
    // TEMPLATE LITERALS
    // =====================================================================

    #[tokio::test]
    async fn test_template_literals() {
        let mut env = HashMap::new();
        env.insert("name".to_string(), Arc::new(to_raw_value(&json!("World"))));
        env.insert("x".to_string(), Arc::new(to_raw_value(&json!(5))));

        assert_eval("`Hello ${name}!`", env.clone(), json!("Hello World!")).await;
        assert_eval(
            "`The answer is ${x * 2}`",
            env.clone(),
            json!("The answer is 10"),
        )
        .await;
    }

    // =====================================================================
    // JSON OPERATIONS
    // =====================================================================

    #[tokio::test]
    async fn test_json_operations() {
        let mut env = HashMap::new();
        env.insert(
            "obj".to_string(),
            Arc::new(to_raw_value(&json!({"a": 1, "b": 2}))),
        );

        // JSON.stringify produces a string result
        assert_eval(
            "JSON.stringify(obj)",
            env.clone(),
            json!("{\"a\":1,\"b\":2}"),
        )
        .await;
        assert_eval("Object.keys(obj)", env.clone(), json!(["a", "b"])).await;
        assert_eval("Object.values(obj)", env.clone(), json!([1, 2])).await;
    }

    // =====================================================================
    // MATH OPERATIONS
    // =====================================================================

    #[tokio::test]
    async fn test_math_operations() {
        let env = HashMap::new();

        assert_eval("Math.max(1, 5, 3)", env.clone(), json!(5)).await;
        assert_eval("Math.min(1, 5, 3)", env.clone(), json!(1)).await;
        assert_eval("Math.abs(-5)", env.clone(), json!(5)).await;
        assert_eval("Math.floor(3.7)", env.clone(), json!(3)).await;
        assert_eval("Math.ceil(3.2)", env.clone(), json!(4)).await;
        assert_eval("Math.round(3.5)", env.clone(), json!(4)).await;
    }

    // =====================================================================
    // TYPE COERCION
    // =====================================================================

    #[tokio::test]
    async fn test_type_coercion() {
        let mut env = HashMap::new();
        env.insert("num".to_string(), Arc::new(to_raw_value(&json!(42))));
        env.insert("str".to_string(), Arc::new(to_raw_value(&json!("123"))));

        assert_eval("String(num)", env.clone(), json!("42")).await;
        assert_eval("Number(str)", env.clone(), json!(123)).await;
        assert_eval("Boolean(num)", env.clone(), json!(true)).await;
        assert_eval("parseInt('42px')", env.clone(), json!(42)).await;
        assert_eval("parseFloat('3.14')", env.clone(), json!(3.14)).await;
    }

    // =====================================================================
    // ARRAY SPREAD
    // =====================================================================

    #[tokio::test]
    async fn test_array_spread() {
        let mut env = HashMap::new();
        env.insert(
            "arr1".to_string(),
            Arc::new(to_raw_value(&json!([1, 2, 3]))),
        );
        env.insert(
            "arr2".to_string(),
            Arc::new(to_raw_value(&json!([4, 5, 6]))),
        );

        assert_eval("[...arr1, ...arr2]", env.clone(), json!([1, 2, 3, 4, 5, 6])).await;
        assert_eval("[0, ...arr1, 99]", env.clone(), json!([0, 1, 2, 3, 99])).await;
    }

    // =====================================================================
    // MULTILINE STATEMENTS
    // =====================================================================

    #[tokio::test]
    async fn test_multiline_statements() {
        let mut env = HashMap::new();
        env.insert("x".to_string(), Arc::new(to_raw_value(&json!(5))));

        assert_eval(
            r#"let y = x * 2;
            return y + 1"#,
            env.clone(),
            json!(11),
        )
        .await;

        assert_eval(
            r#"const result = x > 3 ? 'big' : 'small';
            return result"#,
            env.clone(),
            json!("big"),
        )
        .await;
    }

    // =====================================================================
    // OPTIONAL CHAINING & NULLISH COALESCING
    // =====================================================================

    #[tokio::test]
    async fn test_optional_chaining_nullish() {
        let mut env = HashMap::new();
        env.insert(
            "obj".to_string(),
            Arc::new(to_raw_value(&json!({"a": {"b": 1}}))),
        );
        env.insert("empty".to_string(), Arc::new(to_raw_value(&json!(null))));

        assert_eval("obj?.a?.b", env.clone(), json!(1)).await;
        assert_eval("obj?.a?.c", env.clone(), json!(null)).await;
        assert_eval("obj?.x?.y", env.clone(), json!(null)).await;
        assert_eval("empty?.foo", env.clone(), json!(null)).await;

        assert_eval("null ?? 'default'", env.clone(), json!("default")).await;
        assert_eval("undefined ?? 'default'", env.clone(), json!("default")).await;
        assert_eval("0 ?? 'default'", env.clone(), json!(0)).await;
        assert_eval("'' ?? 'default'", env.clone(), json!("")).await;
        assert_eval("false ?? 'default'", env.clone(), json!(false)).await;
    }

    // =====================================================================
    // DESTRUCTURING
    // =====================================================================

    #[tokio::test]
    async fn test_destructuring() {
        let mut env = HashMap::new();
        env.insert(
            "obj".to_string(),
            Arc::new(to_raw_value(&json!({"name": "test", "value": 42}))),
        );
        env.insert(
            "arr".to_string(),
            Arc::new(to_raw_value(&json!([1, 2, 3, 4, 5]))),
        );

        assert_eval(
            "const { name, value } = obj; return { name, value }",
            env.clone(),
            json!({"name": "test", "value": 42}),
        )
        .await;

        assert_eval(
            "const [first, second, ...rest] = arr; return { first, second, rest }",
            env.clone(),
            json!({"first": 1, "second": 2, "rest": [3, 4, 5]}),
        )
        .await;

        assert_eval(
            "const { missing = 'default' } = obj; return missing",
            env.clone(),
            json!("default"),
        )
        .await;
    }

    // =====================================================================
    // NUMBER EDGE CASES
    // =====================================================================

    #[tokio::test]
    async fn test_number_edge_cases() {
        let env = HashMap::new();

        assert_eval(
            "Number.MAX_SAFE_INTEGER",
            env.clone(),
            json!(9007199254740991_i64),
        )
        .await;
        assert_eval(
            "Number.MIN_SAFE_INTEGER",
            env.clone(),
            json!(-9007199254740991_i64),
        )
        .await;
        assert_eval("Number.isInteger(5)", env.clone(), json!(true)).await;
        assert_eval("Number.isInteger(5.5)", env.clone(), json!(false)).await;
        assert_eval("Number.isFinite(Infinity)", env.clone(), json!(false)).await;
        assert_eval("Number.isNaN(NaN)", env.clone(), json!(true)).await;
        assert_eval("isNaN(NaN)", env.clone(), json!(true)).await;
        assert_eval("isFinite(100)", env.clone(), json!(true)).await;
    }

    // =====================================================================
    // REGEX BASIC
    // =====================================================================

    #[tokio::test]
    async fn test_regex_basic() {
        let mut env = HashMap::new();
        env.insert(
            "str".to_string(),
            Arc::new(to_raw_value(&json!("hello world 123"))),
        );

        assert_eval("/hello/.test(str)", env.clone(), json!(true)).await;
        assert_eval("str.match(/\\d+/)?.[0]", env.clone(), json!("123")).await;
        assert_eval(
            "str.replace(/world/, 'universe')",
            env.clone(),
            json!("hello universe 123"),
        )
        .await;
        assert_eval(
            "str.split(/\\s+/)",
            env.clone(),
            json!(["hello", "world", "123"]),
        )
        .await;
        assert_eval("'aaa'.replace(/a/g, 'b')", env.clone(), json!("bbb")).await;
        assert_eval("/HELLO/i.test(str)", env.clone(), json!(true)).await;
    }

    // =====================================================================
    // DATE OPERATIONS
    // =====================================================================

    #[tokio::test]
    async fn test_date_basic() {
        let env = HashMap::new();

        assert_eval(
            "Date.parse('2024-01-15T00:00:00.000Z')",
            env.clone(),
            json!(1705276800000_i64),
        )
        .await;
        assert_eval(
            "new Date('2024-01-15T00:00:00.000Z').getUTCFullYear()",
            env.clone(),
            json!(2024),
        )
        .await;
        assert_eval(
            "new Date('2024-01-15T00:00:00.000Z').getUTCMonth()",
            env.clone(),
            json!(0),
        )
        .await;
        assert_eval(
            "new Date('2024-01-15T00:00:00.000Z').getUTCDate()",
            env.clone(),
            json!(15),
        )
        .await;
        assert_eval(
            "new Date('2024-01-15T00:00:00.000Z').toISOString()",
            env.clone(),
            json!("2024-01-15T00:00:00.000Z"),
        )
        .await;
    }

    #[tokio::test]
    async fn test_date_serialization() {
        let env = HashMap::new();

        // Direct Date → ISO string via toJSON
        assert_eval(
            "new Date('2024-01-15T12:30:00.000Z')",
            env.clone(),
            json!("2024-01-15T12:30:00.000Z"),
        )
        .await;

        // Date within an object
        assert_eval(
            "({ date: new Date('2024-01-15T00:00:00.000Z'), name: 'test' })",
            env.clone(),
            json!({"date": "2024-01-15T00:00:00.000Z", "name": "test"}),
        )
        .await;

        // Deeply nested Date
        assert_eval(
            "({ level1: { level2: { date: new Date('2024-01-15T00:00:00.000Z') } } })",
            env.clone(),
            json!({"level1": {"level2": {"date": "2024-01-15T00:00:00.000Z"}}}),
        )
        .await;
    }

    // =====================================================================
    // SPECIAL OBJECT SERIALIZATION
    // =====================================================================

    #[tokio::test]
    async fn test_special_object_serialization() {
        let env = HashMap::new();

        // RegExp, Map, Set all serialize to {}
        assert_eval("/test/gi", env.clone(), json!({})).await;
        assert_eval("new Map([['key', 'value']])", env.clone(), json!({})).await;
        assert_eval("new Set([1, 2, 3])", env.clone(), json!({})).await;
    }

    // =====================================================================
    // ARRAY ADVANCED
    // =====================================================================

    #[tokio::test]
    async fn test_array_advanced() {
        let mut env = HashMap::new();
        env.insert(
            "arr".to_string(),
            Arc::new(to_raw_value(&json!([3, 1, 4, 1, 5, 9, 2, 6]))),
        );
        env.insert(
            "nested".to_string(),
            Arc::new(to_raw_value(&json!([[1, 2], [3, 4], [5, 6]]))),
        );

        assert_eval(
            "[...arr].sort((a, b) => a - b)",
            env.clone(),
            json!([1, 1, 2, 3, 4, 5, 6, 9]),
        )
        .await;
        assert_eval(
            "[...arr].sort((a, b) => b - a)",
            env.clone(),
            json!([9, 6, 5, 4, 3, 2, 1, 1]),
        )
        .await;
        assert_eval("nested.flat()", env.clone(), json!([1, 2, 3, 4, 5, 6])).await;
        assert_eval(
            "nested.flatMap(x => x)",
            env.clone(),
            json!([1, 2, 3, 4, 5, 6]),
        )
        .await;
        assert_eval("arr.indexOf(5)", env.clone(), json!(4)).await;
        assert_eval("arr.indexOf(99)", env.clone(), json!(-1)).await;
        assert_eval("arr.includes(9)", env.clone(), json!(true)).await;
        assert_eval("arr.slice(2, 5)", env.clone(), json!([4, 1, 5])).await;
        assert_eval("arr.slice(-3)", env.clone(), json!([9, 2, 6])).await;
    }

    // =====================================================================
    // LOGICAL OPERATORS
    // =====================================================================

    #[tokio::test]
    async fn test_logical_operators() {
        let mut env = HashMap::new();
        env.insert("a".to_string(), Arc::new(to_raw_value(&json!(true))));
        env.insert("b".to_string(), Arc::new(to_raw_value(&json!(false))));
        env.insert("x".to_string(), Arc::new(to_raw_value(&json!(5))));

        assert_eval("a && 'yes'", env.clone(), json!("yes")).await;
        assert_eval("b && 'yes'", env.clone(), json!(false)).await;
        assert_eval("b || 'no'", env.clone(), json!("no")).await;
        assert_eval("a || 'no'", env.clone(), json!(true)).await;
        assert_eval("let y = null; y ??= 10; return y", env.clone(), json!(10)).await;
        assert_eval("let y = 5; y ??= 10; return y", env.clone(), json!(5)).await;
        assert_eval("(a && x > 3) || (b && x < 3)", env.clone(), json!(true)).await;
    }

    // =====================================================================
    // TYPEOF
    // =====================================================================

    #[tokio::test]
    async fn test_typeof() {
        let mut env = HashMap::new();
        env.insert("str".to_string(), Arc::new(to_raw_value(&json!("hello"))));
        env.insert("num".to_string(), Arc::new(to_raw_value(&json!(42))));
        env.insert("arr".to_string(), Arc::new(to_raw_value(&json!([1, 2, 3]))));
        env.insert("obj".to_string(), Arc::new(to_raw_value(&json!({"a": 1}))));
        env.insert("n".to_string(), Arc::new(to_raw_value(&json!(null))));

        assert_eval("typeof str", env.clone(), json!("string")).await;
        assert_eval("typeof num", env.clone(), json!("number")).await;
        assert_eval("typeof arr", env.clone(), json!("object")).await;
        assert_eval("typeof obj", env.clone(), json!("object")).await;
        assert_eval("typeof n", env.clone(), json!("object")).await;
        assert_eval("typeof undefined", env.clone(), json!("undefined")).await;
        assert_eval("Array.isArray(arr)", env.clone(), json!(true)).await;
        assert_eval("Array.isArray(obj)", env.clone(), json!(false)).await;
    }

    // =====================================================================
    // COMPLEX MULTILINE EXPRESSIONS
    // =====================================================================

    #[tokio::test]
    async fn test_multiline_complex_logic() {
        let mut env = HashMap::new();
        env.insert(
            "users".to_string(),
            Arc::new(to_raw_value(&json!([
                {"name": "Alice", "age": 30, "role": "admin"},
                {"name": "Bob", "age": 25, "role": "user"},
                {"name": "Charlie", "age": 35, "role": "admin"},
                {"name": "Diana", "age": 28, "role": "user"}
            ]))),
        );

        assert_eval(
            r#"
            const admins = users.filter(u => u.role === 'admin');
            const names = admins.map(u => u.name);
            return names.join(', ')
            "#,
            env.clone(),
            json!("Alice, Charlie"),
        )
        .await;

        assert_eval(
            r#"
            const totalAge = users.reduce((sum, u) => sum + u.age, 0);
            const avgAge = totalAge / users.length;
            return Math.round(avgAge)
            "#,
            env.clone(),
            json!(30),
        )
        .await;

        assert_eval(
            r#"
            const grouped = users.reduce((acc, u) => {
                if (!acc[u.role]) acc[u.role] = [];
                acc[u.role].push(u.name);
                return acc;
            }, {});
            return grouped
            "#,
            env.clone(),
            json!({"admin": ["Alice", "Charlie"], "user": ["Bob", "Diana"]}),
        )
        .await;
    }

    // =====================================================================
    // DATA TRANSFORMATION
    // =====================================================================

    #[tokio::test]
    async fn test_multiline_data_transformation() {
        let mut env = HashMap::new();
        env.insert(
            "data".to_string(),
            Arc::new(to_raw_value(&json!({
                "items": [
                    {"id": 1, "price": 100, "quantity": 2},
                    {"id": 2, "price": 50, "quantity": 5},
                    {"id": 3, "price": 75, "quantity": 3}
                ],
                "discount": 0.1
            }))),
        );

        assert_eval(
            r#"
            const subtotals = data.items.map(item => item.price * item.quantity);
            const total = subtotals.reduce((a, b) => a + b, 0);
            const discounted = total * (1 - data.discount);
            return { subtotals, total, discounted }
            "#,
            env.clone(),
            json!({"subtotals": [200, 250, 225], "total": 675, "discounted": 607.5}),
        )
        .await;
    }

    // =====================================================================
    // TRY-CATCH
    // =====================================================================

    #[tokio::test]
    async fn test_try_catch() {
        let env = HashMap::new();

        assert_eval(
            r#"
            try {
                return JSON.parse('{"valid": true}');
            } catch (e) {
                return { problem: e.message };
            }
            "#,
            env.clone(),
            json!({"valid": true}),
        )
        .await;

        assert_eval(
            r#"
            try {
                return JSON.parse('invalid json');
            } catch (e) {
                return { problem: 'parse_failed' };
            }
            "#,
            env.clone(),
            json!({"problem": "parse_failed"}),
        )
        .await;

        assert_eval(
            r#"
            let result = 'initial';
            try {
                result = 'try';
            } catch (e) {
                result = 'catch';
            } finally {
                result = result + '_finally';
            }
            return result
            "#,
            env.clone(),
            json!("try_finally"),
        )
        .await;
    }

    // =====================================================================
    // OBJECT ADVANCED
    // =====================================================================

    #[tokio::test]
    async fn test_object_advanced() {
        let mut env = HashMap::new();
        env.insert(
            "config".to_string(),
            Arc::new(to_raw_value(&json!({
                "server": {"host": "localhost", "port": 8080},
                "database": {"host": "db.local", "port": 5432},
                "features": ["auth", "logging", "cache"]
            }))),
        );

        assert_eval(
            "Object.assign({}, config.server, { secure: true })",
            env.clone(),
            json!({"host": "localhost", "port": 8080, "secure": true}),
        )
        .await;

        assert_eval(
            "({ ...config.server, port: 443, secure: true })",
            env.clone(),
            json!({"host": "localhost", "port": 443, "secure": true}),
        )
        .await;

        assert_eval(
            "JSON.parse(JSON.stringify(config))",
            env.clone(),
            json!({
                "server": {"host": "localhost", "port": 8080},
                "database": {"host": "db.local", "port": 5432},
                "features": ["auth", "logging", "cache"]
            }),
        )
        .await;

        assert_eval(
            r#"
            const key = 'dynamic';
            return { [key]: 'value', [`${key}_2`]: 'value2' }
            "#,
            env.clone(),
            json!({"dynamic": "value", "dynamic_2": "value2"}),
        )
        .await;
    }

    // =====================================================================
    // STRING ADVANCED
    // =====================================================================

    #[tokio::test]
    async fn test_string_advanced() {
        let mut env = HashMap::new();
        env.insert(
            "text".to_string(),
            Arc::new(to_raw_value(&json!("  Hello, World!  "))),
        );
        env.insert(
            "path".to_string(),
            Arc::new(to_raw_value(&json!("/api/v1/users/123/profile"))),
        );

        assert_eval("text.trim()", env.clone(), json!("Hello, World!")).await;
        assert_eval("text.trimStart()", env.clone(), json!("Hello, World!  ")).await;
        assert_eval("text.trimEnd()", env.clone(), json!("  Hello, World!")).await;
        assert_eval("'42'.padStart(5, '0')", env.clone(), json!("00042")).await;
        assert_eval("'42'.padEnd(5, '-')", env.clone(), json!("42---")).await;
        assert_eval("'ab'.repeat(3)", env.clone(), json!("ababab")).await;
        assert_eval(
            "path.split('/').filter(p => p.length > 0)",
            env.clone(),
            json!(["api", "v1", "users", "123", "profile"]),
        )
        .await;
    }

    // =====================================================================
    // ARRAY MANIPULATION
    // =====================================================================

    #[tokio::test]
    async fn test_array_manipulation() {
        let mut env = HashMap::new();
        env.insert(
            "items".to_string(),
            Arc::new(to_raw_value(&json!([
                {"id": 1, "name": "Apple", "category": "fruit"},
                {"id": 2, "name": "Carrot", "category": "vegetable"},
                {"id": 3, "name": "Banana", "category": "fruit"},
                {"id": 4, "name": "Broccoli", "category": "vegetable"}
            ]))),
        );

        assert_eval(
            "items.find(i => i.name === 'Banana')",
            env.clone(),
            json!({"id": 3, "name": "Banana", "category": "fruit"}),
        )
        .await;
        assert_eval(
            "items.findIndex(i => i.name === 'Banana')",
            env.clone(),
            json!(2),
        )
        .await;
        assert_eval(
            "items.filter(i => i.category === 'fruit').map(i => i.name).sort()",
            env.clone(),
            json!(["Apple", "Banana"]),
        )
        .await;
        assert_eval(
            "Array.from({length: 5}, (_, i) => i * 2)",
            env.clone(),
            json!([0, 2, 4, 6, 8]),
        )
        .await;
        assert_eval("Array(3).fill(0)", env.clone(), json!([0, 0, 0])).await;
        assert_eval(
            "[1, 2].concat([3, 4], [5, 6])",
            env.clone(),
            json!([1, 2, 3, 4, 5, 6]),
        )
        .await;
        assert_eval(
            "items.map(i => i.name).join(' | ')",
            env.clone(),
            json!("Apple | Carrot | Banana | Broccoli"),
        )
        .await;
    }

    // =====================================================================
    // SET AND MAP OPERATIONS
    // =====================================================================

    #[tokio::test]
    async fn test_set_operations() {
        let mut env = HashMap::new();
        env.insert(
            "arr".to_string(),
            Arc::new(to_raw_value(&json!([1, 2, 2, 3, 3, 3, 4]))),
        );

        assert_eval("[...new Set(arr)]", env.clone(), json!([1, 2, 3, 4])).await;
        assert_eval("new Set(arr).size", env.clone(), json!(4)).await;
        assert_eval("new Set(arr).has(3)", env.clone(), json!(true)).await;
        assert_eval("new Set(arr).has(99)", env.clone(), json!(false)).await;
    }

    #[tokio::test]
    async fn test_map_operations() {
        let env = HashMap::new();

        assert_eval(
            r#"
            const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
            return Object.fromEntries(map)
            "#,
            env.clone(),
            json!({"a": 1, "b": 2, "c": 3}),
        )
        .await;

        assert_eval(
            r#"
            const map = new Map();
            map.set('key1', 'value1');
            map.set('key2', 'value2');
            return map.get('key1')
            "#,
            env.clone(),
            json!("value1"),
        )
        .await;

        assert_eval(
            r#"
            const map = new Map([['a', 1], ['b', 2]]);
            return map.size
            "#,
            env.clone(),
            json!(2),
        )
        .await;
    }

    // =====================================================================
    // COMPARISONS
    // =====================================================================

    #[tokio::test]
    async fn test_comparisons() {
        let env = HashMap::new();

        assert_eval("1 === 1", env.clone(), json!(true)).await;
        assert_eval("1 === '1'", env.clone(), json!(false)).await;
        assert_eval("null === undefined", env.clone(), json!(false)).await;
        assert_eval("null === null", env.clone(), json!(true)).await;
        assert_eval("1 == '1'", env.clone(), json!(true)).await;
        assert_eval("null == undefined", env.clone(), json!(true)).await;
        assert_eval("5 !== '5'", env.clone(), json!(true)).await;
        assert_eval("5 > 3", env.clone(), json!(true)).await;
        assert_eval("5 >= 5", env.clone(), json!(true)).await;
        assert_eval("3 < 5", env.clone(), json!(true)).await;
        assert_eval("5 <= 5", env.clone(), json!(true)).await;
        assert_eval("'apple' < 'banana'", env.clone(), json!(true)).await;
    }

    // =====================================================================
    // BITWISE OPERATIONS
    // =====================================================================

    #[tokio::test]
    async fn test_bitwise() {
        let env = HashMap::new();

        assert_eval("5 & 3", env.clone(), json!(1)).await;
        assert_eval("5 | 3", env.clone(), json!(7)).await;
        assert_eval("5 ^ 3", env.clone(), json!(6)).await;
        assert_eval("~5", env.clone(), json!(-6)).await;
        assert_eval("5 << 2", env.clone(), json!(20)).await;
        assert_eval("20 >> 2", env.clone(), json!(5)).await;
    }

    // =====================================================================
    // REAL-WORLD FLOW PATTERNS
    // =====================================================================

    #[tokio::test]
    async fn test_flow_patterns_api_response() {
        let mut env = HashMap::new();
        env.insert(
            "previous_result".to_string(),
            Arc::new(to_raw_value(&json!({
                "status": 200,
                "data": {
                    "users": [
                        {"id": 1, "email": "alice@example.com", "active": true},
                        {"id": 2, "email": "bob@example.com", "active": false},
                        {"id": 3, "email": "charlie@example.com", "active": true}
                    ],
                    "pagination": {"page": 1, "total": 50, "per_page": 10}
                }
            }))),
        );

        assert_eval(
            "previous_result.data.users.filter(u => u.active).map(u => u.email)",
            env.clone(),
            json!(["alice@example.com", "charlie@example.com"]),
        )
        .await;

        assert_eval(
            r#"
            const { page, total, per_page } = previous_result.data.pagination;
            return page * per_page < total
            "#,
            env.clone(),
            json!(true),
        )
        .await;
    }

    #[tokio::test]
    async fn test_flow_patterns_batch_processing() {
        let mut env = HashMap::new();
        env.insert(
            "jobs".to_string(),
            Arc::new(to_raw_value(&json!([
                {"id": 1, "status": "completed", "result": 100},
                {"id": 2, "status": "failed", "reason": "timeout"},
                {"id": 3, "status": "completed", "result": 200},
                {"id": 4, "status": "failed", "reason": "connection"},
                {"id": 5, "status": "completed", "result": 150}
            ]))),
        );

        assert_eval(
            r#"
            const completed = jobs.filter(j => j.status === 'completed');
            const failed = jobs.filter(j => j.status === 'failed');
            const totalResult = completed.reduce((sum, j) => sum + j.result, 0);
            return {
                totalJobs: jobs.length,
                completedCount: completed.length,
                failedCount: failed.length,
                successRate: completed.length / jobs.length,
                totalResult,
                failureReasons: failed.map(j => j.reason)
            }
            "#,
            env.clone(),
            json!({
                "totalJobs": 5,
                "completedCount": 3,
                "failedCount": 2,
                "successRate": 0.6,
                "totalResult": 450,
                "failureReasons": ["timeout", "connection"]
            }),
        )
        .await;
    }

    #[tokio::test]
    async fn test_flow_patterns_config_merge() {
        let mut env = HashMap::new();
        env.insert(
            "defaults".to_string(),
            Arc::new(to_raw_value(&json!({
                "timeout": 5000,
                "retries": 3,
                "headers": {"Content-Type": "application/json"},
                "features": {"logging": true, "caching": false}
            }))),
        );
        env.insert(
            "overrides".to_string(),
            Arc::new(to_raw_value(&json!({
                "timeout": 10000,
                "headers": {"Authorization": "Bearer token"},
                "features": {"caching": true}
            }))),
        );

        assert_eval(
            r#"({
                ...defaults,
                ...overrides,
                headers: { ...defaults.headers, ...overrides.headers },
                features: { ...defaults.features, ...overrides.features }
            })"#,
            env.clone(),
            json!({
                "timeout": 10000,
                "retries": 3,
                "headers": {"Content-Type": "application/json", "Authorization": "Bearer token"},
                "features": {"logging": true, "caching": true}
            }),
        )
        .await;
    }

    // =====================================================================
    // FLOW SIMULATION: Multi-step flow with various step results
    // =====================================================================

    fn create_multi_step_flow_context() -> (
        HashMap<String, Arc<Box<RawValue>>>,
        Option<mappable_rc::Marc<HashMap<String, Box<RawValue>>>>,
        HashMap<String, Box<RawValue>>,
    ) {
        let mut ctx = HashMap::new();
        ctx.insert("a".to_string(), Arc::new(to_raw_value(&json!(42))));
        ctx.insert(
            "b".to_string(),
            Arc::new(to_raw_value(&json!({
                "status": "success",
                "data": {
                    "users": [
                        {"id": 1, "name": "Alice", "active": true, "roles": ["admin", "user"]},
                        {"id": 2, "name": "Bob", "active": false, "roles": ["user"]},
                        {"id": 3, "name": "Charlie", "active": true, "roles": ["moderator", "user"]}
                    ],
                    "total": 3,
                    "metadata": {"page": 1, "hasMore": true}
                }
            }))),
        );
        ctx.insert(
            "c".to_string(),
            Arc::new(to_raw_value(&json!([10, 20, 30, 40, 50]))),
        );
        ctx.insert("d".to_string(), Arc::new(to_raw_value(&json!(null))));
        ctx.insert(
            "f".to_string(),
            Arc::new(to_raw_value(&json!({
                "level1": {"level2": {"level3": {"level4": {"value": "deeply_nested"}}}}
            }))),
        );
        ctx.insert(
            "previous_result".to_string(),
            Arc::new(to_raw_value(&json!([
                "string", 123, true, null, {"key": "value"}, [1, 2, 3]
            ]))),
        );

        let mut fi = HashMap::new();
        fi.insert("name".to_string(), to_raw_value(&json!("test_flow")));
        fi.insert("count".to_string(), to_raw_value(&json!(100)));
        fi.insert("enabled".to_string(), to_raw_value(&json!(true)));
        fi.insert(
            "config".to_string(),
            to_raw_value(&json!({
                "timeout": 30, "retries": 3, "options": ["fast", "secure"]
            })),
        );
        fi.insert(
            "items".to_string(),
            to_raw_value(&json!([
                {"id": 1, "value": "first"},
                {"id": 2, "value": "second"},
                {"id": 3, "value": "third"}
            ])),
        );

        let mut fe = HashMap::new();
        fe.insert("ENV".to_string(), to_raw_value(&json!("production")));
        fe.insert("DEBUG".to_string(), to_raw_value(&json!(false)));
        fe.insert("VERSION".to_string(), to_raw_value(&json!("1.2.3")));

        (ctx, Some(mappable_rc::Marc::new(fi)), fe)
    }

    #[tokio::test]
    async fn test_flow_step_references() {
        let (ctx, fi, fe) = create_multi_step_flow_context();

        assert_eval_full("a", ctx.clone(), fi.clone(), Some(&fe), json!(42)).await;
        assert_eval_full(
            "b.status",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!("success"),
        )
        .await;
        assert_eval_full("b.data.total", ctx.clone(), fi.clone(), Some(&fe), json!(3)).await;
        assert_eval_full(
            "b.data.users[0].name",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!("Alice"),
        )
        .await;
        assert_eval_full("c[0]", ctx.clone(), fi.clone(), Some(&fe), json!(10)).await;
        assert_eval_full(
            "c.map(x => x * 2)",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!([20, 40, 60, 80, 100]),
        )
        .await;
        assert_eval_full(
            "f.level1.level2.level3.level4.value",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!("deeply_nested"),
        )
        .await;
    }

    #[tokio::test]
    async fn test_flow_input_nested_and_combined() {
        let (ctx, fi, fe) = create_multi_step_flow_context();

        assert_eval_full(
            "flow_input.name",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!("test_flow"),
        )
        .await;
        assert_eval_full(
            "flow_input.config.timeout",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!(30),
        )
        .await;
        assert_eval_full(
            "flow_input.config.options[0]",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!("fast"),
        )
        .await;
        assert_eval_full(
            "flow_input.items.length",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!(3),
        )
        .await;
        assert_eval_full(
            "flow_input.items[0].id",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!(1),
        )
        .await;
        assert_eval_full(
            "flow_input.items.map(i => i.value)",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!(["first", "second", "third"]),
        )
        .await;

        // Combining flow_input with step results
        assert_eval_full(
            "flow_input.count + a",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!(142),
        )
        .await;
        assert_eval_full(
            "flow_input.enabled ? b.data.users : []",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!([
                {"id": 1, "name": "Alice", "active": true, "roles": ["admin", "user"]},
                {"id": 2, "name": "Bob", "active": false, "roles": ["user"]},
                {"id": 3, "name": "Charlie", "active": true, "roles": ["moderator", "user"]}
            ]),
        )
        .await;
    }

    #[tokio::test]
    async fn test_flow_env_access() {
        let (ctx, fi, fe) = create_multi_step_flow_context();

        assert_eval_full(
            "flow_env.ENV",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!("production"),
        )
        .await;
        assert_eval_full(
            "flow_env.DEBUG",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!(false),
        )
        .await;
        assert_eval_full(
            "flow_env.VERSION",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!("1.2.3"),
        )
        .await;
    }

    #[tokio::test]
    async fn test_flow_branch_conditions() {
        let (ctx, fi, fe) = create_multi_step_flow_context();

        assert_eval_full("a > 40", ctx.clone(), fi.clone(), Some(&fe), json!(true)).await;
        assert_eval_full(
            "b.status === 'success'",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!(true),
        )
        .await;
        assert_eval_full(
            "flow_input.enabled",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!(true),
        )
        .await;
        assert_eval_full(
            "a > 40 && b.status === 'success'",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!(true),
        )
        .await;
        assert_eval_full(
            "b.data.users.length > 0",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!(true),
        )
        .await;
        assert_eval_full(
            "b.data.users.some(u => u.active)",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!(true),
        )
        .await;
        assert_eval_full(
            "c.includes(30)",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!(true),
        )
        .await;
    }

    #[tokio::test]
    async fn test_flow_complex_data_extraction() {
        let (ctx, fi, fe) = create_multi_step_flow_context();

        assert_eval_full(
            "b.data.users.filter(u => u.active).map(u => u.name)",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!(["Alice", "Charlie"]),
        )
        .await;

        assert_eval_full(
            "b.data.users.filter(u => u.roles.includes('admin'))[0]?.name",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!("Alice"),
        )
        .await;

        assert_eval_full(
            "b.data.users.reduce((acc, u) => acc + (u.active ? 1 : 0), 0)",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!(2),
        )
        .await;

        // Combining multiple step results
        assert_eval_full("a + c[0]", ctx.clone(), fi.clone(), Some(&fe), json!(52)).await;
        assert_eval_full(
            "a * b.data.total",
            ctx.clone(),
            fi.clone(),
            Some(&fe),
            json!(126),
        )
        .await;
    }

    #[tokio::test]
    async fn test_flow_forloop_inner_expressions() {
        let mut ctx = HashMap::new();
        ctx.insert(
            "previous_result".to_string(),
            Arc::new(to_raw_value(&json!({"value": 42, "index": 2}))),
        );

        let mut fi = HashMap::new();
        fi.insert(
            "iter".to_string(),
            to_raw_value(&json!({"index": 2, "value": {"id": 3, "name": "test_item"}})),
        );
        fi.insert("name".to_string(), to_raw_value(&json!("parent_flow")));
        let fi = Some(mappable_rc::Marc::new(fi));

        assert_eval_full(
            "flow_input.iter.index",
            ctx.clone(),
            fi.clone(),
            None,
            json!(2),
        )
        .await;
        assert_eval_full(
            "flow_input.iter.value.id",
            ctx.clone(),
            fi.clone(),
            None,
            json!(3),
        )
        .await;
        assert_eval_full(
            "flow_input.iter.value.name",
            ctx.clone(),
            fi.clone(),
            None,
            json!("test_item"),
        )
        .await;
        assert_eval_full(
            "`Item ${flow_input.iter.index} of ${flow_input.name}`",
            ctx.clone(),
            fi.clone(),
            None,
            json!("Item 2 of parent_flow"),
        )
        .await;
    }

    // =====================================================================
    // EDGE CASES
    // =====================================================================

    #[tokio::test]
    async fn test_edge_cases_empty_values() {
        let mut env = HashMap::new();
        env.insert("emptyArray".to_string(), Arc::new(to_raw_value(&json!([]))));
        env.insert(
            "emptyObject".to_string(),
            Arc::new(to_raw_value(&json!({}))),
        );
        env.insert(
            "emptyString".to_string(),
            Arc::new(to_raw_value(&json!(""))),
        );
        env.insert("zero".to_string(), Arc::new(to_raw_value(&json!(0))));

        assert_eval("emptyArray.length", env.clone(), json!(0)).await;
        assert_eval("emptyArray.map(x => x * 2)", env.clone(), json!([])).await;
        assert_eval(
            "emptyArray.reduce((a, b) => a + b, 100)",
            env.clone(),
            json!(100),
        )
        .await;
        assert_eval("Object.keys(emptyObject)", env.clone(), json!([])).await;
        assert_eval("emptyString.length", env.clone(), json!(0)).await;
        assert_eval("emptyString || 'default'", env.clone(), json!("default")).await;
        assert_eval("emptyString ?? 'default'", env.clone(), json!("")).await;
        assert_eval("zero || 'default'", env.clone(), json!("default")).await;
        assert_eval("zero ?? 'default'", env.clone(), json!(0)).await;
    }

    #[tokio::test]
    async fn test_edge_cases_deep_access() {
        let mut env = HashMap::new();
        env.insert(
            "deep".to_string(),
            Arc::new(to_raw_value(
                &json!({"a": {"b": {"c": {"d": {"e": "found!"}}}}}),
            )),
        );

        assert_eval("deep.a.b.c.d.e", env.clone(), json!("found!")).await;
        assert_eval("deep?.a?.b?.c?.d?.e", env.clone(), json!("found!")).await;
        assert_eval("deep?.a?.b?.x?.y?.z", env.clone(), json!(null)).await;
        assert_eval(
            "deep?.a?.b?.x?.y?.z ?? 'not found'",
            env.clone(),
            json!("not found"),
        )
        .await;
    }

    #[tokio::test]
    async fn test_edge_cases_special_characters() {
        let mut env = HashMap::new();
        env.insert(
            "data".to_string(),
            Arc::new(to_raw_value(&json!({
                "key-with-dash": "value1",
                "key.with.dots": "value2",
                "key with spaces": "value3"
            }))),
        );

        assert_eval("data['key-with-dash']", env.clone(), json!("value1")).await;
        assert_eval("data['key.with.dots']", env.clone(), json!("value2")).await;
        assert_eval("data['key with spaces']", env.clone(), json!("value3")).await;
    }

    #[tokio::test]
    async fn test_edge_cases_boolean_coercion() {
        let env = HashMap::new();

        assert_eval("Boolean(0)", env.clone(), json!(false)).await;
        assert_eval("Boolean('')", env.clone(), json!(false)).await;
        assert_eval("Boolean(null)", env.clone(), json!(false)).await;
        assert_eval("Boolean(undefined)", env.clone(), json!(false)).await;
        assert_eval("Boolean(NaN)", env.clone(), json!(false)).await;
        assert_eval("Boolean(1)", env.clone(), json!(true)).await;
        assert_eval("Boolean('hello')", env.clone(), json!(true)).await;
        assert_eval("Boolean([])", env.clone(), json!(true)).await;
        assert_eval("Boolean({})", env.clone(), json!(true)).await;
        assert_eval("!!0", env.clone(), json!(false)).await;
        assert_eval("!!1", env.clone(), json!(true)).await;
    }

    // =====================================================================
    // PROMISE RESOLUTION
    // =====================================================================

    #[tokio::test]
    async fn test_promise_resolve() {
        let env = HashMap::new();

        assert_eval("Promise.resolve(42)", env.clone(), json!(42)).await;
        assert_eval(
            "Promise.resolve({ key: 'value' })",
            env.clone(),
            json!({"key": "value"}),
        )
        .await;
        assert_eval(
            "Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)])",
            env.clone(),
            json!([1, 2, 3]),
        )
        .await;
    }

    // =====================================================================
    // eval_simple_js TESTS
    // =====================================================================

    #[tokio::test]
    async fn test_eval_simple_js_basic() {
        let mut globals = HashMap::new();
        globals.insert("job".to_string(), json!({"id": "abc", "input": {"x": 42}}));

        let result = eval_simple_js("job.input.x".to_string(), globals)
            .await
            .unwrap();
        assert_eq!(result.get(), "42");
    }

    #[tokio::test]
    async fn test_eval_simple_js_string() {
        let mut globals = HashMap::new();
        globals.insert("name".to_string(), json!("World"));

        let result = eval_simple_js("`Hello ${name}!`".to_string(), globals)
            .await
            .unwrap();
        assert_eq!(result.get(), "\"Hello World!\"");
    }

    #[tokio::test]
    async fn test_eval_simple_js_array_transform() {
        let mut globals = HashMap::new();
        globals.insert("data".to_string(), json!([1, 2, 3, 4, 5]));

        let result = eval_simple_js(
            "data.filter(x => x > 2).map(x => x * 10)".to_string(),
            globals,
        )
        .await
        .unwrap();
        let actual: serde_json::Value = serde_json::from_str(result.get()).unwrap();
        assert_eq!(actual, json!([30, 40, 50]));
    }

    #[tokio::test]
    async fn test_eval_simple_js_null_handling() {
        let mut globals = HashMap::new();
        globals.insert("x".to_string(), json!(null));

        let result = eval_simple_js("x ?? 'default'".to_string(), globals)
            .await
            .unwrap();
        assert_eq!(result.get(), "\"default\"");
    }

    #[tokio::test]
    async fn test_eval_simple_js_multiple_globals() {
        let mut globals = HashMap::new();
        globals.insert("a".to_string(), json!(10));
        globals.insert("b".to_string(), json!(20));
        globals.insert("prefix".to_string(), json!("result"));

        let result = eval_simple_js(
            "({ label: `${prefix}_sum`, value: a + b })".to_string(),
            globals,
        )
        .await
        .unwrap();
        let actual: serde_json::Value = serde_json::from_str(result.get()).unwrap();
        assert_eq!(actual, json!({"label": "result_sum", "value": 30}));
    }

    #[tokio::test]
    async fn test_eval_simple_js_error_handling() {
        let globals = HashMap::new();

        // Invalid JS should return an error
        let result = eval_simple_js("this is not valid js @#$".to_string(), globals).await;
        assert!(result.is_err());
    }
}
