//! The [`Script`] component — attaches a `rhai` script to an entity.
//!
//! Like every Oxide component, `Script` is plain serializable data with
//! `#[derive(Reflect)]`, so it is editable from the inspector and from scripts
//! with no per-type editor code, and is captured by the play-mode snapshot. It
//! holds only *authoring* inputs — which script to run and whether it is active.
//! The compiled AST and per-entity runtime state are **not** stored here; they
//! live in the host (added in a later piece) keyed by entity, so a live reload
//! can recompile without disturbing the authored scene.

use oxide_engine::asset::AssetRef;
use oxide_engine::reflect::Reflect;
use serde::{Deserialize, Serialize};

use crate::ScriptAsset;

/// Component: a `rhai` script driving an entity.
///
/// Attach it and point [`source`](Self::source) at a `.rhai` asset; once the
/// [`ScriptModule`](crate::ScriptModule) is running, the script's lifecycle
/// hooks (`init`, `update(dt)`, …) execute for this entity. Clear
/// [`enabled`](Self::enabled) to suspend it without detaching.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, Reflect)]
pub struct Script {
    /// The `.rhai` script asset this entity runs. Empty until one is assigned
    /// (in the inspector, pick a script from the `scripts/` folder).
    pub source: AssetRef<ScriptAsset>,
    /// Whether the script runs. `true` by default; clear to suspend it while
    /// keeping the component attached.
    pub enabled: bool,
}

impl Script {
    /// A script component pointing at `source`, enabled.
    pub fn new(source: AssetRef<ScriptAsset>) -> Self {
        Self {
            source,
            enabled: true,
        }
    }
}

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

    #[test]
    fn default_is_empty_and_disabled_until_constructed() {
        // Derived Default: no source, and `enabled` is the bool default (false).
        let s = Script::default();
        assert!(!s.source.is_some());
        assert!(!s.enabled);
    }

    #[test]
    fn new_points_at_a_source_and_is_enabled() {
        let s = Script::new(AssetRef::new(AssetUid(7)));
        assert_eq!(s.source.uid(), Some(AssetUid(7)));
        assert!(s.enabled);
    }

    #[test]
    fn round_trips_through_ron() {
        let s = Script::new(AssetRef::new(AssetUid(42)));
        let text = ron::to_string(&s).unwrap();
        let back: Script = ron::from_str(&text).unwrap();
        assert_eq!(s, back);
    }

    #[test]
    fn the_source_field_is_a_script_asset_ref() {
        // The inspector reads this spelling to offer a `scripts/` asset picker.
        let s = Script::default();
        let field = s
            .fields()
            .iter()
            .find(|f| f.name == "source")
            .expect("source field is reflected");
        assert_eq!(
            oxide_engine::asset::asset_ref_target(field.type_name),
            Some("ScriptAsset")
        );
    }
}
