//! `agent(opts)` and `parallel(items, mapFn)` — the core scheduling primitives.
//!
//! Both build [`AgentTask`](luft_core::contract::backend::AgentTask)s from Lua
//! opts tables and, when a [`JournalStore`](luft_core::journal::JournalStore)
//! is present, consult the journal for cached results (M1 resume) before
//! submitting to the scheduler, recording outputs back keyed by cache key.
//!
//! Each primitive has its own registrar ([`single`] / [`parallel`]); the shared
//! resume/record plumbing lives in [`journal`].

mod journal;
mod parallel;
mod pmap;
mod single;

use crate::sdk::SdkContext;
use mlua::Lua;

/// Register `agent`, `parallel`, and `pmap` as Lua globals.
pub(crate) fn register_agent_sdk(lua: &Lua, cx: &SdkContext) -> mlua::Result<()> {
    single::register(lua, cx)?;
    parallel::register(lua, cx)?;
    pmap::register(lua, cx)?;
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::sdk::test_support::make_sdk_context;
    use mlua::Lua;

    #[tokio::test]
    async fn register_agent_sdk_registers_three_globals() {
        let lua = Lua::new();
        let cx = make_sdk_context();
        register_agent_sdk(&lua, &cx).expect("register_agent_sdk should succeed");

        let globals = lua.globals();
        assert!(
            globals.get::<mlua::Function>("agent").is_ok(),
            "agent global must be registered as a function"
        );
        assert!(
            globals.get::<mlua::Function>("parallel").is_ok(),
            "parallel global must be registered as a function"
        );
        assert!(
            globals.get::<mlua::Function>("pmap").is_ok(),
            "pmap global must be registered as a function"
        );
    }

    #[tokio::test]
    async fn register_agent_sdk_overwrites_existing_globals_without_panic() {
        let lua = Lua::new();
        let globals = lua.globals();
        globals
            .set("agent", lua.create_function(|_, _: ()| Ok(())).unwrap())
            .unwrap();
        globals
            .set("parallel", lua.create_function(|_, _: ()| Ok(())).unwrap())
            .unwrap();

        let cx = make_sdk_context();
        // Re-registration must succeed without error since globals.set overwrites.
        register_agent_sdk(&lua, &cx).unwrap();

        // After overwriting, `agent` is bound to the real single::register
        // closure (which expects a Table). We don't invoke it — we just confirm
        // the binding is still a function and the underlying function pointer
        // has changed.
        let _: mlua::Function = globals.get("agent").unwrap();
        let _: mlua::Function = globals.get("parallel").unwrap();
        let _: mlua::Function = globals.get("pmap").unwrap();
    }

    #[tokio::test]
    async fn registered_agent_global_is_callable_from_lua() {
        let lua = Lua::new();
        let cx = make_sdk_context();
        register_agent_sdk(&lua, &cx).unwrap();
        // After registration, calling `agent` with a non-table opts should fail at
        // the binding layer (which calls build_task → expects a Table). The test
        // simply verifies the function resolves, not that it succeeds.
        let script = r#"local _ = agent"#;
        lua.load(script).exec().expect("agent must be a callable global");
    }

    #[tokio::test]
    async fn registered_parallel_and_pmap_globals_are_callable_from_lua() {
        let lua = Lua::new();
        let cx = make_sdk_context();
        register_agent_sdk(&lua, &cx).unwrap();
        let script = r#"local _ = parallel; local __ = pmap"#;
        lua.load(script)
            .exec()
            .expect("parallel and pmap must be callable globals");
    }
}
