//! HeroScript - A Defensive Configuration Language
//!
//! HeroScript is a defensive configuration and scripting language designed for
//! safe, predictable system orchestration. It provides a structured way to define
//! actions, configurations, and workflows without the risks associated with
//! general-purpose scripting languages.
//!
//! # Key Features
//!
//! - **Defensive by Design**: No arbitrary code execution, shell injection, or dynamic evaluation
//! - **Declarative Actions**: Define *what* should happen through structured actions
//! - **Type-Safe Parameters**: All parameters are parsed and validated before execution
//! - **Audit Trail**: Every action has a defined actor, name, and parameters
//!
//! # Syntax
//!
//! Actions can be written in compact (single line) or expanded (multiline) form:
//!
//! ```heroscript
//! // Compact form
//! !!actor.action_name param1:'value1' param2:'value2'
//!
//! // Expanded form
//! !!actor.action_name
//!     param1: 'value1'
//!     param2: 'value with spaces'
//! ```
//!
//! # Example
//!
//! ```
//! use herosal_heroscript::{PlayBook, PlayBookNewArgs, FindArgs};
//!
//! let script = r#"
//!     !!person.define name:'John Doe' email:'john@example.com' active:true
//!
//!     !!person.define
//!         name: 'Jane Doe'
//!         email: 'jane@example.com'
//! "#;
//!
//! let playbook = PlayBook::new(PlayBookNewArgs {
//!     text: script.to_string(),
//!     ..Default::default()
//! }).unwrap();
//!
//! let actions = playbook.find(&FindArgs {
//!     filter: "person.define".to_string(),
//!     ..Default::default()
//! }).unwrap();
//!
//! for action in actions {
//!     let name = action.params.get("name").unwrap();
//!     let email = action.params.get("email").unwrap();
//!     println!("Person: {} <{}>", name, email);
//! }
//! ```

pub mod action;
pub mod errors;
pub mod include;
pub mod params;
pub mod playbook;
pub mod rhai;
pub mod scanner;

// Re-export main types at crate root
pub use action::{Action, ActionState, MatchFilter};
pub use errors::{HeroScriptError, ParamsError, ParamsResult, Result};
pub use include::{expand_includes, process_include, IncludeArgs};
pub use params::{ExportArgs, Param, Params};
pub use playbook::{FindArgs, PlayBook, PlayBookNewArgs};
pub use scanner::scan;

/// Parse a parameter string into a Params struct.
///
/// This is a convenience function that calls `params::parse`.
///
/// # Example
///
/// ```
/// use herosal_heroscript::parse_params;
///
/// let params = parse_params("name:'John' age:30 debug").unwrap();
/// assert_eq!(params.get("name").unwrap(), "John");
/// assert_eq!(params.get_int("age").unwrap(), 30);
/// assert!(params.exists_arg("debug"));
/// ```
pub fn parse_params(text: &str) -> Result<Params> {
    params::parse(text).map_err(HeroScriptError::from)
}

/// Parse HeroScript text into a list of actions.
///
/// This is a convenience function that calls `scanner::scan`.
///
/// # Example
///
/// ```
/// use herosal_heroscript::parse_actions;
///
/// let actions = parse_actions("!!person.define name:'John'").unwrap();
/// assert_eq!(actions.len(), 1);
/// assert_eq!(actions[0].actor, "person");
/// ```
pub fn parse_actions(text: &str) -> Result<Vec<Action>> {
    scanner::scan(text)
}

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

    #[test]
    fn test_basic_workflow() {
        let script = r#"
            !!payment.configure provider:'stripe' sandbox:true

            !!payment.add
                name: 'Credit Card'
                enabled: true
                min_amount: 1.00

            !!payment.add name:'Bank Transfer' enabled:true
        "#;

        let mut playbook = PlayBook::new(PlayBookNewArgs {
            text: script.to_string(),
            ..Default::default()
        })
        .unwrap();

        // Check we have 3 actions
        assert_eq!(playbook.len(), 3);

        // Process configure
        if playbook.exists_once("payment.configure") {
            let action = playbook.get_mut("payment.configure").unwrap();
            assert_eq!(action.params.get("provider").unwrap(), "stripe");
            assert!(action.params.get_default_true("sandbox"));
            action.mark_done();
        }

        // Process all add actions
        let add_actions = playbook
            .find(&FindArgs {
                filter: "payment.add".to_string(),
                ..Default::default()
            })
            .unwrap();

        assert_eq!(add_actions.len(), 2);

        for action in add_actions {
            let name = action.params.get("name").unwrap();
            assert!(name == "Credit Card" || name == "Bank Transfer");
        }
    }

    #[test]
    fn test_params_type_safety() {
        let params = parse_params("port:8080 ratio:3.14 enabled:true items:a,b,c").unwrap();

        // Integer
        assert_eq!(params.get_int("port").unwrap(), 8080);

        // Float
        assert!((params.get_float("ratio").unwrap() - 3.14).abs() < 0.001);

        // Boolean
        assert!(params.get_default_true("enabled"));
        assert!(!params.get_default_false("missing"));

        // List
        assert_eq!(params.get_list("items").unwrap(), vec!["a", "b", "c"]);
    }

    #[test]
    fn test_defensive_no_code_execution() {
        // This should just be stored as a string, never executed
        let script = "!!server.configure command:'rm -rf /'";
        let playbook = PlayBook::new(PlayBookNewArgs {
            text: script.to_string(),
            ..Default::default()
        })
        .unwrap();

        let action = playbook.get("server.configure").unwrap();
        // The value is just data, not code
        assert_eq!(action.params.get("command").unwrap(), "rm -rf /");
    }

    #[test]
    fn test_glob_filtering() {
        let script = r#"
            !!payment.add name:'Card'
            !!payment.configure provider:'stripe'
            !!notification.send to:'user'
        "#;

        let playbook = PlayBook::new(PlayBookNewArgs {
            text: script.to_string(),
            ..Default::default()
        })
        .unwrap();

        // Find all payment actions
        let payment = playbook
            .find(&FindArgs {
                filter: "payment.*".to_string(),
                ..Default::default()
            })
            .unwrap();
        assert_eq!(payment.len(), 2);

        // Find all configure actions
        let configure = playbook
            .find(&FindArgs {
                filter: "*.configure".to_string(),
                ..Default::default()
            })
            .unwrap();
        assert_eq!(configure.len(), 1);

        // Find with multiple filters
        let multi = playbook
            .find(&FindArgs {
                filter: "payment.add,notification.send".to_string(),
                ..Default::default()
            })
            .unwrap();
        assert_eq!(multi.len(), 2);
    }
}
