// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::common::{
    from_c_str, to_ref, to_regorus_result, to_regorus_string_result, to_shared_ref, RegorusResult,
    RegorusStatus,
};
use crate::compiled_policy::RegorusCompiledPolicy;
use crate::limits::RegorusExecutionTimerConfig;
use crate::limits::RegorusPolicyLengthConfig;
use crate::lock::{new_handle, read, try_read, try_write, Handle, ReadGuard, WriteGuard};
use crate::panic_guard::with_unwind_guard;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::String;
#[cfg(feature = "rvm")]
use alloc::sync::Arc;
use alloc::vec::Vec;
use anyhow::{anyhow, Result};
use core::ffi::{c_char, c_void};
use core::ptr;
#[cfg(feature = "rvm")]
use regorus::languages::rego::compiler::Compiler;
#[cfg(feature = "rvm")]
use regorus::rvm::program::Program;

/// Wrapper for `regorus::Engine`.
pub struct RegorusEngine {
    engine: Handle<::regorus::Engine>,
}

impl RegorusEngine {
    fn new(engine: ::regorus::Engine) -> Self {
        Self {
            engine: new_handle(engine),
        }
    }

    fn contention_error() -> anyhow::Error {
        anyhow!(
            "regorus engine handle is already in use; clone the engine before sharing across threads"
        )
    }

    fn try_write(&self) -> Result<WriteGuard<'_, ::regorus::Engine>> {
        try_write(&self.engine).ok_or_else(Self::contention_error)
    }

    fn try_read(&self) -> Result<ReadGuard<'_, ::regorus::Engine>> {
        try_read(&self.engine).ok_or_else(Self::contention_error)
    }
}

impl Clone for RegorusEngine {
    fn clone(&self) -> Self {
        let guard = read(&self.engine);
        Self::new((*guard).clone())
    }
}

#[cfg(all(test, feature = "contention_checks", feature = "std"))]
mod tests {
    use super::RegorusEngine;

    #[test]
    fn detects_handle_contention() {
        let engine = RegorusEngine::new(::regorus::Engine::new());

        let _first_guard = engine.try_write().expect("initial lock should succeed");
        let err = engine
            .try_write()
            .expect_err("contention detection must reject the second lock");

        assert!(
            err.to_string().contains("engine handle is already in use"),
            "unexpected error message: {err}"
        );
    }
}

#[cfg(all(test, feature = "std"))]
mod panic_tests {
    use super::{
        regorus_engine_drop, regorus_engine_eval_query, regorus_engine_get_policies,
        regorus_engine_new,
    };
    use crate::common::{regorus_result_drop, RegorusStatus};
    use crate::panic_guard::{is_poisoned, reset_poison};
    use alloc::boxed::Box;
    use regorus::Value;
    use std::ffi::{CStr, CString};

    #[test]
    fn catches_extension_panics_and_marks_poison() {
        reset_poison();

        let engine_ptr = regorus_engine_new();
        assert!(!engine_ptr.is_null(), "engine allocation must succeed");
        assert!(!is_poisoned(), "guard must start unpoisoned");

        unsafe {
            let engine = &mut *engine_ptr;
            {
                let mut guard = engine
                    .try_write()
                    .expect("exclusive access to configure engine");
                guard
                    .add_extension(
                        "panic_extension".to_string(),
                        0,
                        Box::new(|_| -> anyhow::Result<Value> { panic!("ffi extension panic") }),
                    )
                    .expect("extension registration must succeed");
                guard
                    .add_policy(
                        "panic.rego".to_string(),
                        "package panic\n\ndefault allow = false\n\nallow if {\n  panic_extension()\n}"
                            .to_string(),
                    )
                    .expect("policy registration must succeed");
            }
        }

        let query = CString::new("data.panic.allow").expect("valid query string");
        let panic_result = regorus_engine_eval_query(engine_ptr, query.as_ptr());
        assert!(matches!(panic_result.status, RegorusStatus::Panic));
        unsafe {
            assert!(
                !panic_result.error_message.is_null(),
                "panic details must be present"
            );
            let message = CStr::from_ptr(panic_result.error_message)
                .to_str()
                .expect("error message utf8");
            assert!(
                message.contains("ffi extension panic"),
                "panic payload must bubble across guard"
            );
        }
        regorus_result_drop(panic_result);
        assert!(is_poisoned(), "engine must be marked poisoned after panic");

        let poisoned_result = regorus_engine_get_policies(engine_ptr);
        assert!(matches!(poisoned_result.status, RegorusStatus::Poisoned));
        unsafe {
            assert!(
                !poisoned_result.error_message.is_null(),
                "poison message must be present"
            );
            let message = CStr::from_ptr(poisoned_result.error_message)
                .to_str()
                .expect("poison message utf8");
            assert!(
                message.contains("regorus is poisoned"),
                "poison message must inform callers"
            );
        }
        regorus_result_drop(poisoned_result);

        regorus_engine_drop(engine_ptr);
        reset_poison();
    }
}

#[no_mangle]
#[cfg(feature = "std")]
pub extern "C" fn regorus_engine_test_trigger_panic() -> RegorusResult {
    with_unwind_guard(|| panic!("regorus ffi test panic"))
}

#[no_mangle]
pub extern "C" fn regorus_engine_test_reset_poison() {
    crate::panic_guard::reset_poison();
}

#[no_mangle]
/// Construct a new Engine
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html
pub extern "C" fn regorus_engine_new() -> *mut RegorusEngine {
    let mut engine = ::regorus::Engine::new();

    // For more OPA compatibility out of the box, we ask builtins to return undefined
    // instead of raising errors in certain failure scenarios.
    engine.set_strict_builtin_errors(false);

    Box::into_raw(Box::new(RegorusEngine::new(engine)))
}

/// Clone a [`RegorusEngine`]
///
/// To avoid having to parse same policy again, the engine can be cloned
/// after policies and data have been added.
///
#[no_mangle]
pub extern "C" fn regorus_engine_clone(engine: *mut RegorusEngine) -> *mut RegorusEngine {
    match to_shared_ref(engine as *const RegorusEngine) {
        Ok(e) => Box::into_raw(Box::new(e.clone())),
        _ => ptr::null_mut(),
    }
}

#[no_mangle]
pub extern "C" fn regorus_engine_drop(engine: *mut RegorusEngine) {
    if let Ok(e) = to_ref(engine) {
        unsafe {
            let _ = Box::from_raw(ptr::from_mut(e));
        }
    }
}

/// Add a policy
///
/// The policy is parsed into AST.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_policy
///
/// * `path`: A filename to be associated with the policy.
/// * `rego`: Rego policy.
#[no_mangle]
pub extern "C" fn regorus_engine_add_policy(
    engine: *mut RegorusEngine,
    path: *const c_char,
    rego: *const c_char,
) -> RegorusResult {
    with_unwind_guard(|| {
        to_regorus_string_result(|| -> Result<String> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            guard.add_policy(from_c_str(path)?, from_c_str(rego)?)
        }())
    })
}

#[cfg(feature = "std")]
#[no_mangle]
pub extern "C" fn regorus_engine_add_policy_from_file(
    engine: *mut RegorusEngine,
    path: *const c_char,
) -> RegorusResult {
    with_unwind_guard(|| {
        to_regorus_string_result(|| -> Result<String> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            guard.add_policy_from_file(from_c_str(path)?)
        }())
    })
}

/// Add policy data.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_data
/// * `data`: JSON encoded value to be used as policy data.
#[no_mangle]
pub extern "C" fn regorus_engine_add_data_json(
    engine: *mut RegorusEngine,
    data: *const c_char,
) -> RegorusResult {
    with_unwind_guard(|| {
        to_regorus_result(|| -> Result<()> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            guard.add_data(regorus::Value::from_json_str(&from_c_str(data)?)?)
        }())
    })
}

/// Get list of loaded Rego packages as JSON.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_packages
#[no_mangle]
pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> RegorusResult {
    with_unwind_guard(|| {
        to_regorus_string_result(|| -> Result<String> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let guard = engine.try_read()?;
            serde_json::to_string_pretty(&guard.get_packages()?).map_err(anyhow::Error::msg)
        }())
    })
}

/// Get list of policies as JSON.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_policies
#[no_mangle]
pub extern "C" fn regorus_engine_get_policies(engine: *mut RegorusEngine) -> RegorusResult {
    with_unwind_guard(|| {
        to_regorus_string_result(|| -> Result<String> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let guard = engine.try_read()?;
            guard.get_policies_as_json()
        }())
    })
}

#[cfg(feature = "std")]
#[no_mangle]
pub extern "C" fn regorus_engine_add_data_from_json_file(
    engine: *mut RegorusEngine,
    path: *const c_char,
) -> RegorusResult {
    with_unwind_guard(|| {
        to_regorus_result(|| -> Result<()> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            guard.add_data(regorus::Value::from_json_file(from_c_str(path)?)?)
        }())
    })
}

/// Clear policy data.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_data
#[no_mangle]
pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> RegorusResult {
    with_unwind_guard(|| {
        to_regorus_result(|| -> Result<()> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            guard.clear_data();
            Ok(())
        }())
    })
}

/// Set input.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_input
/// * `input`: JSON encoded value to be used as input to query.
#[no_mangle]
pub extern "C" fn regorus_engine_set_input_json(
    engine: *mut RegorusEngine,
    input: *const c_char,
) -> RegorusResult {
    with_unwind_guard(|| {
        to_regorus_result(|| -> Result<()> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            guard.set_input(regorus::Value::from_json_str(&from_c_str(input)?)?);
            Ok(())
        }())
    })
}

#[cfg(feature = "std")]
#[no_mangle]
pub extern "C" fn regorus_engine_set_input_from_json_file(
    engine: *mut RegorusEngine,
    path: *const c_char,
) -> RegorusResult {
    with_unwind_guard(|| {
        to_regorus_result(|| -> Result<()> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            guard.set_input(regorus::Value::from_json_file(from_c_str(path)?)?);
            Ok(())
        }())
    })
}

/// Evaluate query.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_query
/// * `query`: Rego expression to be evaluate.
#[no_mangle]
pub extern "C" fn regorus_engine_eval_query(
    engine: *mut RegorusEngine,
    query: *const c_char,
) -> RegorusResult {
    with_unwind_guard(|| {
        let output = || -> Result<String> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            let results = guard.eval_query(from_c_str(query)?, false)?;
            Ok(serde_json::to_string_pretty(&results)?)
        }();
        match output {
            Ok(out) => RegorusResult::ok_string(out),
            Err(e) => to_regorus_result(Err(e)),
        }
    })
}

/// Evaluate specified rule.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_rule
/// * `rule`: Path to the rule.
#[no_mangle]
pub extern "C" fn regorus_engine_eval_rule(
    engine: *mut RegorusEngine,
    rule: *const c_char,
) -> RegorusResult {
    with_unwind_guard(|| {
        let output = || -> Result<String> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            guard.eval_rule(from_c_str(rule)?)?.to_json_str()
        }();
        match output {
            Ok(out) => RegorusResult::ok_string(out),
            Err(e) => to_regorus_result(Err(e)),
        }
    })
}

/// Enable/disable coverage.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_enable_coverage
/// * `enable`: Whether to enable or disable coverage.
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "C" fn regorus_engine_set_enable_coverage(
    engine: *mut RegorusEngine,
    enable: bool,
) -> RegorusResult {
    with_unwind_guard(|| {
        to_regorus_result(|| -> Result<()> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            guard.set_enable_coverage(enable);
            Ok(())
        }())
    })
}

/// Get coverage report.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_coverage_report
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "C" fn regorus_engine_get_coverage_report(engine: *mut RegorusEngine) -> RegorusResult {
    with_unwind_guard(|| {
        let output = || -> Result<String> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let guard = engine.try_read()?;
            Ok(serde_json::to_string_pretty(&guard.get_coverage_report()?)?)
        }();
        match output {
            Ok(out) => RegorusResult::ok_string(out),
            Err(e) => to_regorus_result(Err(e)),
        }
    })
}

/// Enable/disable strict builtin errors.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_strict_builtin_errors
/// * `strict`: Whether to raise errors or return undefined on certain scenarios.
#[no_mangle]
pub extern "C" fn regorus_engine_set_strict_builtin_errors(
    engine: *mut RegorusEngine,
    strict: bool,
) -> RegorusResult {
    with_unwind_guard(|| {
        to_regorus_result(|| -> Result<()> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            guard.set_strict_builtin_errors(strict);
            Ok(())
        }())
    })
}

#[no_mangle]
/// Configure the execution timer for a specific engine instance.
pub extern "C" fn regorus_engine_set_execution_timer_config(
    engine: *mut RegorusEngine,
    config: *const RegorusExecutionTimerConfig,
) -> RegorusResult {
    with_unwind_guard(|| {
        to_regorus_result(|| -> Result<()> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let config = unsafe {
                config
                    .as_ref()
                    .copied()
                    .ok_or_else(|| anyhow!("execution timer config pointer is null"))?
            };
            let mut guard = engine.try_write()?;
            guard.set_execution_timer_config(config.to_execution_timer_config()?);
            Ok(())
        }())
    })
}

#[no_mangle]
/// Clear the engine-specific execution timer configuration.
pub extern "C" fn regorus_engine_clear_execution_timer_config(
    engine: *mut RegorusEngine,
) -> RegorusResult {
    with_unwind_guard(|| {
        to_regorus_result(|| -> Result<()> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            guard.clear_execution_timer_config();
            Ok(())
        }())
    })
}

/// Set the policy length limits used when loading policies.
#[no_mangle]
pub extern "C" fn regorus_engine_set_policy_length_config(
    engine: *mut RegorusEngine,
    config: RegorusPolicyLengthConfig,
) -> RegorusResult {
    with_unwind_guard(|| {
        to_regorus_result(|| -> Result<()> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            guard.set_policy_length_config(config.to_policy_length_config()?);
            Ok(())
        }())
    })
}

/// Clear the policy length configuration, reverting to defaults.
#[no_mangle]
pub extern "C" fn regorus_engine_clear_policy_length_config(
    engine: *mut RegorusEngine,
) -> RegorusResult {
    with_unwind_guard(|| {
        to_regorus_result(|| -> Result<()> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            guard.clear_policy_length_config();
            Ok(())
        }())
    })
}

/// Get pretty printed coverage report.
///
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Report.html#method.to_string_pretty
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "C" fn regorus_engine_get_coverage_report_pretty(
    engine: *mut RegorusEngine,
) -> RegorusResult {
    with_unwind_guard(|| {
        let output = || -> Result<String> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let guard = engine.try_read()?;
            guard.get_coverage_report()?.to_string_pretty()
        }();
        match output {
            Ok(out) => RegorusResult::ok_string(out),
            Err(e) => to_regorus_result(Err(e)),
        }
    })
}

/// Clear coverage data.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_coverage_data
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "C" fn regorus_engine_clear_coverage_data(engine: *mut RegorusEngine) -> RegorusResult {
    with_unwind_guard(|| {
        to_regorus_result(|| -> Result<()> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            guard.clear_coverage_data();
            Ok(())
        }())
    })
}

/// Whether to gather output of print statements.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_gather_prints
/// * `enable`: Whether to enable or disable gathering print statements.
#[no_mangle]
pub extern "C" fn regorus_engine_set_gather_prints(
    engine: *mut RegorusEngine,
    enable: bool,
) -> RegorusResult {
    with_unwind_guard(|| {
        to_regorus_result(|| -> Result<()> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            guard.set_gather_prints(enable);
            Ok(())
        }())
    })
}

/// Take all the gathered print statements.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.take_prints
#[no_mangle]
pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> RegorusResult {
    with_unwind_guard(|| {
        let output = || -> Result<String> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            Ok(serde_json::to_string_pretty(&guard.take_prints()?)?)
        }();
        match output {
            Ok(out) => RegorusResult::ok_string(out),
            Err(e) => to_regorus_result(Err(e)),
        }
    })
}

/// Get AST of policies.
///
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Engine.html#method.get_ast_as_json
#[no_mangle]
#[cfg(feature = "ast")]
pub extern "C" fn regorus_engine_get_ast_as_json(engine: *mut RegorusEngine) -> RegorusResult {
    with_unwind_guard(|| {
        let output = || -> Result<String> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let guard = engine.try_read()?;
            guard.get_ast_as_json()
        }();
        match output {
            Ok(out) => RegorusResult::ok_string(out),
            Err(e) => to_regorus_result(Err(e)),
        }
    })
}

/// Gets the package names defined in each policy added to the engine.
///
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Engine.html#method.get_policy_package_names
#[no_mangle]
#[cfg(feature = "azure_policy")]
pub extern "C" fn regorus_engine_get_policy_package_names(
    engine: *mut RegorusEngine,
) -> RegorusResult {
    with_unwind_guard(|| {
        let output = || -> Result<String> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let guard = engine.try_read()?;
            serde_json::to_string_pretty(&guard.get_policy_package_names()?)
                .map_err(anyhow::Error::msg)
        }();
        match output {
            Ok(out) => RegorusResult::ok_string(out),
            Err(e) => to_regorus_result(Err(e)),
        }
    })
}

/// Gets the parameters defined in each policy added to the engine.
///
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Engine.html#method.get_policy_parameters
#[no_mangle]
#[cfg(feature = "azure_policy")]
pub extern "C" fn regorus_engine_get_policy_parameters(
    engine: *mut RegorusEngine,
) -> RegorusResult {
    with_unwind_guard(|| {
        let output = || -> Result<String> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let guard = engine.try_read()?;
            serde_json::to_string_pretty(&guard.get_policy_parameters()?)
                .map_err(anyhow::Error::msg)
        }();
        match output {
            Ok(out) => RegorusResult::ok_string(out),
            Err(e) => to_regorus_result(Err(e)),
        }
    })
}

/// Enable/disable rego v1.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_rego_v0
#[no_mangle]
pub extern "C" fn regorus_engine_set_rego_v0(
    engine: *mut RegorusEngine,
    enable: bool,
) -> RegorusResult {
    with_unwind_guard(|| {
        let output = || -> Result<()> {
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            guard.set_rego_v0(enable);
            Ok(())
        }();
        match output {
            Ok(()) => RegorusResult::ok_void(),
            Err(e) => to_regorus_result(Err(e)),
        }
    })
}

/// Compile a target-aware policy from the current engine state.
///
/// This method creates a compiled policy that can work with Azure Policy targets,
/// enabling resource type inference and target-specific evaluation.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.compile_for_target
#[no_mangle]
#[cfg(feature = "azure_policy")]
pub extern "C" fn regorus_engine_compile_for_target(engine: *mut RegorusEngine) -> RegorusResult {
    with_unwind_guard(|| {
        let engine = match to_shared_ref(engine as *const RegorusEngine) {
            Ok(engine) => engine,
            Err(e) => {
                return RegorusResult::err_with_message(
                    RegorusStatus::InvalidArgument,
                    format!("Failed to get engine reference: {e}"),
                )
            }
        };

        let mut guard = match engine.try_write() {
            Ok(guard) => guard,
            Err(e) => {
                return RegorusResult::err_with_message(
                    RegorusStatus::Error,
                    format!("Failed to lock engine: {e}"),
                )
            }
        };

        match guard.compile_for_target() {
            Ok(compiled_policy) => {
                let wrapped_policy = RegorusCompiledPolicy { compiled_policy };
                let boxed_policy = Box::new(wrapped_policy);
                RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
            }
            Err(e) => RegorusResult::err_with_message(
                RegorusStatus::CompilationFailed,
                format!("Failed to compile for target: {e}"),
            ),
        }
    })
}

/// Compile a policy with a specific entry point rule.
///
/// This method creates a compiled policy that evaluates a specific rule as the entry point.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.compile_with_entrypoint
/// * `rule`: The specific rule path to evaluate (e.g., "data.policy.allow")
#[no_mangle]
pub extern "C" fn regorus_engine_compile_with_entrypoint(
    engine: *mut RegorusEngine,
    rule: *const c_char,
) -> RegorusResult {
    with_unwind_guard(|| {
        let result = || -> Result<RegorusCompiledPolicy> {
            let rule_str = from_c_str(rule)?;
            let rule_rc: regorus::Rc<str> = rule_str.into();
            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            let compiled_policy = guard.compile_with_entrypoint(&rule_rc)?;
            Ok(RegorusCompiledPolicy { compiled_policy })
        }();

        match result {
            Ok(wrapped_policy) => {
                let boxed_policy = Box::new(wrapped_policy);
                RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
            }
            Err(e) => RegorusResult::err_with_message(
                RegorusStatus::CompilationFailed,
                format!("Failed to compile with entrypoint: {e}"),
            ),
        }
    })
}

/// Compile an RVM program from the engine state with entry points.
///
/// * `entry_points` - Array of entry point rule paths
/// * `entry_points_len` - Number of entry points
#[cfg(feature = "rvm")]
#[no_mangle]
pub extern "C" fn regorus_engine_compile_program_with_entrypoints(
    engine: *mut RegorusEngine,
    entry_points: *const *const c_char,
    entry_points_len: usize,
) -> RegorusResult {
    with_unwind_guard(|| {
        let result = || -> Result<Arc<Program>> {
            if entry_points_len == 0 {
                return Err(anyhow!("entry_points must contain at least one entry"));
            }

            if entry_points.is_null() && entry_points_len > 0 {
                return Err(anyhow!("null entry_points pointer"));
            }

            let mut entry_points_vec = Vec::with_capacity(entry_points_len);
            for i in 0..entry_points_len {
                unsafe {
                    let entry_ptr = entry_points.add(i);
                    if entry_ptr.is_null() {
                        return Err(anyhow!("null entry point at index {i}"));
                    }
                    let entry = from_c_str(*entry_ptr)?;
                    entry_points_vec.push(entry);
                }
            }

            let entry_points_ref: Vec<&str> = entry_points_vec.iter().map(|s| s.as_str()).collect();

            let rule = entry_points_ref
                .first()
                .ok_or_else(|| anyhow!("entry_points must contain at least one entry"))?;
            let rule_rc: regorus::Rc<str> = (*rule).into();

            let engine = to_shared_ref(engine as *const RegorusEngine)?;
            let mut guard = engine.try_write()?;
            let compiled_policy = guard.compile_with_entrypoint(&rule_rc)?;

            let program = Compiler::compile_from_policy(&compiled_policy, &entry_points_ref)?;
            Ok(program)
        }();

        match result {
            Ok(program) => {
                let wrapped = crate::rvm::RegorusProgram { program };
                let boxed = Box::new(wrapped);
                RegorusResult::ok_pointer(Box::into_raw(boxed) as *mut c_void)
            }
            Err(e) => RegorusResult::err_with_message(
                RegorusStatus::CompilationFailed,
                format!("Failed to compile RVM program: {e}"),
            ),
        }
    })
}
