//! The [`ScriptModule`] — the Stage-10 entry point that wires scripting into an
//! [`App`].
//!
//! Following the engine's module convention, everything the scripting layer
//! contributes is registered here so it can be enabled, disabled, or removed as
//! a unit (and so an exported game that uses no scripts never compiles them in).
//! This first piece registers the [`Script`] component *type* for reflection
//! (making it dual-editable and captured by the play-mode snapshot), installs
//! the `.rhai` [`ScriptLoader`], and inserts the shared [`ScriptEngine`]. The
//! lifecycle system that compiles and runs scripts each frame is added in a
//! later piece.

use oxide_engine::app::{App, Module, Schedule};

use crate::host::run_scripts;
use crate::{Script, ScriptHost, ScriptLoader};

/// The scripting module. Add it after [`DefaultModules`] to give an app a
/// `rhai`-backed scripting layer.
///
/// [`DefaultModules`]: oxide_engine::app::DefaultModules
///
/// ```
/// use oxide_engine::app::App;
/// use oxide_script::ScriptModule;
///
/// let mut app = App::new();
/// app.add_module(ScriptModule);
/// assert!(app.has_module("script"));
/// assert!(app.types.is_registered("Script"));
/// ```
pub struct ScriptModule;

impl Module for ScriptModule {
    fn name(&self) -> &'static str {
        "script"
    }

    fn build(&self, app: &mut App) {
        // Register the component type for reflection so it round-trips through
        // RON (scripts/AI/inspector) and is captured by the play-mode snapshot.
        app.register_type::<Script>("Script");

        // Teach the asset server to load `.rhai` files as `ScriptAsset`s.
        app.add_loader(ScriptLoader);

        // The runtime that compiles and runs each entity's script, driven on
        // `Update` (after `FixedUpdate`, so scripts observe post-physics poses).
        app.insert_resource(ScriptHost::new());
        app.add_system(Schedule::Update, run_scripts);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use oxide_engine::app::DefaultModules;

    #[test]
    fn module_registers_its_type_loader_and_host() {
        let mut app = App::new();
        app.add_modules(DefaultModules);
        app.add_module(ScriptModule);

        assert!(app.has_module("script"));
        assert!(app.types.is_registered("Script"));
        assert!(app.has_resource::<ScriptHost>());
        // The per-frame run_scripts system was registered.
        assert!(app.system_count() >= 1);
    }

    #[test]
    fn removing_the_module_drops_its_type() {
        let mut app = App::new();
        app.add_module(ScriptModule);
        assert!(app.types.is_registered("Script"));

        assert!(app.remove_module("script"));
        assert!(!app.has_module("script"));
        assert!(!app.types.is_registered("Script"));
    }
}
