//! Rhai scripting integration for HeroScript.
//!
//! Provides a builder pattern API for creating and manipulating PlayBooks from Rhai scripts.

use super::{Action, FindArgs, PlayBook, PlayBookNewArgs};
use rhai::{Array, Dynamic, Engine, EvalAltResult, Map};
use std::collections::HashMap;

/// Builder for creating a PlayBook with fluent API.
#[derive(Debug, Clone)]
pub struct PlayBookBuilder {
    path: String,
    text: String,
    process_includes: bool,
    max_include_depth: usize,
    replacements: HashMap<String, String>,
}

impl Default for PlayBookBuilder {
    fn default() -> Self {
        Self {
            path: String::new(),
            text: String::new(),
            process_includes: true,
            max_include_depth: 10,
            replacements: HashMap::new(),
        }
    }
}

impl PlayBookBuilder {
    /// Create a new PlayBookBuilder.
    pub fn new() -> Self {
        Self::default()
    }

    /// Set the path to load heroscript from (file or directory).
    pub fn path(mut self, path: &str) -> Self {
        self.path = path.to_string();
        self
    }

    /// Set the heroscript text directly.
    pub fn text(mut self, text: &str) -> Self {
        self.text = text.to_string();
        self
    }

    /// Set whether to process includes (default: true).
    pub fn process_includes(mut self, enabled: bool) -> Self {
        self.process_includes = enabled;
        self
    }

    /// Set maximum include depth (default: 10).
    pub fn max_include_depth(mut self, depth: i64) -> Self {
        self.max_include_depth = depth as usize;
        self
    }

    /// Add a text replacement (replaces {KEY} and KEY with value).
    pub fn replace(mut self, key: &str, value: &str) -> Self {
        self.replacements.insert(key.to_string(), value.to_string());
        self
    }

    /// Build the PlayBook.
    pub fn build(self) -> Result<PlayBookWrapper, Box<EvalAltResult>> {
        let args = PlayBookNewArgs {
            path: self.path,
            text: self.text,
            process_includes: Some(self.process_includes),
            max_include_depth: Some(self.max_include_depth),
            replace: self.replacements,
            ..Default::default()
        };

        let playbook = PlayBook::new(args).map_err(|e| {
            Box::new(EvalAltResult::ErrorRuntime(
                format!("Failed to create PlayBook: {}", e).into(),
                rhai::Position::NONE,
            ))
        })?;

        Ok(PlayBookWrapper { playbook })
    }
}

/// Wrapper around PlayBook for Rhai integration.
#[derive(Debug, Clone)]
pub struct PlayBookWrapper {
    playbook: PlayBook,
}

impl PlayBookWrapper {
    /// Get the number of actions.
    pub fn len(&mut self) -> i64 {
        self.playbook.len() as i64
    }

    /// Check if playbook is empty.
    pub fn is_empty(&mut self) -> bool {
        self.playbook.is_empty()
    }

    /// Get all unique action names.
    pub fn names(&mut self) -> Array {
        self.playbook
            .names()
            .into_iter()
            .map(|s| Dynamic::from(s))
            .collect()
    }

    /// Check if an action exists.
    pub fn exists(&mut self, filter: &str) -> bool {
        self.playbook.exists(filter)
    }

    /// Check if exactly one action exists.
    pub fn exists_once(&mut self, filter: &str) -> bool {
        self.playbook.exists_once(filter)
    }

    /// Find actions matching a filter.
    pub fn find(&mut self, filter: &str) -> Array {
        match self.playbook.find(&FindArgs {
            filter: filter.to_string(),
            include_done: false,
        }) {
            Ok(actions) => actions
                .into_iter()
                .map(|a| Dynamic::from(ActionWrapper::from_action(a.clone())))
                .collect(),
            Err(_) => Array::new(),
        }
    }

    /// Find actions including done ones.
    pub fn find_all(&mut self, filter: &str) -> Array {
        match self.playbook.find(&FindArgs {
            filter: filter.to_string(),
            include_done: true,
        }) {
            Ok(actions) => actions
                .into_iter()
                .map(|a| Dynamic::from(ActionWrapper::from_action(a.clone())))
                .collect(),
            Err(_) => Array::new(),
        }
    }

    /// Get a single action by filter.
    pub fn get(&mut self, filter: &str) -> Result<ActionWrapper, Box<EvalAltResult>> {
        self.playbook
            .get(filter)
            .map(|a| ActionWrapper::from_action(a.clone()))
            .map_err(|e| {
                Box::new(EvalAltResult::ErrorRuntime(
                    format!("Action not found: {}", e).into(),
                    rhai::Position::NONE,
                ))
            })
    }

    /// Generate heroscript from the playbook.
    pub fn heroscript(&mut self) -> String {
        self.playbook.heroscript(true)
    }

    /// Generate heroscript excluding done actions.
    pub fn heroscript_pending(&mut self) -> String {
        self.playbook.heroscript(false)
    }

    /// Get all actions as an array.
    pub fn actions(&mut self) -> Array {
        self.playbook
            .actions
            .iter()
            .map(|a| Dynamic::from(ActionWrapper::from_action(a.clone())))
            .collect()
    }

    /// Mark actions as done.
    pub fn mark_done(&mut self, filter: &str) -> i64 {
        self.playbook.mark_done(filter).unwrap_or(0) as i64
    }

    /// Get hash of the playbook.
    pub fn hashkey(&mut self) -> String {
        self.playbook.hashkey()
    }
}

/// Wrapper around Action for Rhai integration.
#[derive(Debug, Clone)]
pub struct ActionWrapper {
    action: Action,
}

impl ActionWrapper {
    fn from_action(action: Action) -> Self {
        Self { action }
    }

    /// Get the actor name.
    pub fn actor(&mut self) -> String {
        self.action.actor.clone()
    }

    /// Get the action name.
    pub fn name(&mut self) -> String {
        self.action.name.clone()
    }

    /// Get the full name (actor.name).
    pub fn full_name(&mut self) -> String {
        self.action.full_name()
    }

    /// Check if action is done.
    pub fn is_done(&mut self) -> bool {
        self.action.done
    }

    /// Get the action ID.
    pub fn id(&mut self) -> i64 {
        self.action.id as i64
    }

    /// Get the action priority.
    pub fn priority(&mut self) -> i64 {
        self.action.priority as i64
    }

    /// Get action comments.
    pub fn comments(&mut self) -> String {
        self.action.comments.clone()
    }

    /// Get a parameter value as string.
    pub fn get(&mut self, key: &str) -> Result<String, Box<EvalAltResult>> {
        self.action
            .params
            .get(key)
            .map(|s| s.to_string())
            .map_err(|e| {
                Box::new(EvalAltResult::ErrorRuntime(
                    format!("Parameter '{}' not found: {}", key, e).into(),
                    rhai::Position::NONE,
                ))
            })
    }

    /// Get a parameter with default value.
    pub fn get_default(&mut self, key: &str, default: &str) -> String {
        self.action
            .params
            .get(key)
            .map(|s| s.to_string())
            .unwrap_or_else(|_| default.to_string())
    }

    /// Get parameter as integer.
    pub fn get_int(&mut self, key: &str) -> Result<i64, Box<EvalAltResult>> {
        self.action
            .params
            .get_int(key)
            .map(|i| i as i64)
            .map_err(|e| {
                Box::new(EvalAltResult::ErrorRuntime(
                    format!("Parameter '{}' not an integer: {}", key, e).into(),
                    rhai::Position::NONE,
                ))
            })
    }

    /// Get parameter as integer with default.
    pub fn get_int_default(&mut self, key: &str, default: i64) -> i64 {
        self.action
            .params
            .get_int(key)
            .map(|i| i as i64)
            .unwrap_or(default)
    }

    /// Get parameter as float.
    pub fn get_float(&mut self, key: &str) -> Result<f64, Box<EvalAltResult>> {
        self.action.params.get_float(key).map_err(|e| {
            Box::new(EvalAltResult::ErrorRuntime(
                format!("Parameter '{}' not a float: {}", key, e).into(),
                rhai::Position::NONE,
            ))
        })
    }

    /// Get parameter as boolean (default true).
    pub fn get_bool(&mut self, key: &str) -> bool {
        self.action.params.get_default_true(key)
    }

    /// Get parameter as boolean (default false).
    pub fn get_bool_false(&mut self, key: &str) -> bool {
        self.action.params.get_default_false(key)
    }

    /// Get parameter as list.
    pub fn get_list(&mut self, key: &str) -> Array {
        self.action
            .params
            .get_list(key)
            .unwrap_or_default()
            .into_iter()
            .map(|s| Dynamic::from(s))
            .collect()
    }

    /// Check if parameter exists.
    pub fn has(&mut self, key: &str) -> bool {
        self.action.params.exists(key)
    }

    /// Get all parameter names.
    pub fn param_names(&mut self) -> Array {
        self.action
            .params
            .params
            .iter()
            .map(|p| Dynamic::from(p.key.clone()))
            .collect()
    }

    /// Get all parameters as a map.
    pub fn params(&mut self) -> Map {
        let mut map = Map::new();
        for param in &self.action.params.params {
            map.insert(param.key.clone().into(), Dynamic::from(param.value.clone()));
        }
        map
    }

    /// Generate heroscript for this action.
    pub fn heroscript(&mut self) -> String {
        self.action.heroscript()
    }

    /// Generate one-line heroscript for this action.
    pub fn oneline(&mut self) -> String {
        self.action.to_oneline()
    }
}

/// Register the heroscript module with the Rhai engine.
pub fn register_heroscript_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
    // Register PlayBookBuilder
    engine.register_type_with_name::<PlayBookBuilder>("PlayBookBuilder");
    engine.register_fn("playbook_builder", PlayBookBuilder::new);
    engine.register_fn("path", PlayBookBuilder::path);
    engine.register_fn("text", PlayBookBuilder::text);
    engine.register_fn("process_includes", PlayBookBuilder::process_includes);
    engine.register_fn("max_include_depth", PlayBookBuilder::max_include_depth);
    engine.register_fn("replace", PlayBookBuilder::replace);
    engine.register_fn("build", PlayBookBuilder::build);

    // Register PlayBookWrapper
    engine.register_type_with_name::<PlayBookWrapper>("PlayBook");
    engine.register_fn("len", PlayBookWrapper::len);
    engine.register_fn("is_empty", PlayBookWrapper::is_empty);
    engine.register_fn("names", PlayBookWrapper::names);
    engine.register_fn("exists", PlayBookWrapper::exists);
    engine.register_fn("exists_once", PlayBookWrapper::exists_once);
    engine.register_fn("find", PlayBookWrapper::find);
    engine.register_fn("find_all", PlayBookWrapper::find_all);
    engine.register_fn("get", PlayBookWrapper::get);
    engine.register_fn("heroscript", PlayBookWrapper::heroscript);
    engine.register_fn("heroscript_pending", PlayBookWrapper::heroscript_pending);
    engine.register_fn("actions", PlayBookWrapper::actions);
    engine.register_fn("mark_done", PlayBookWrapper::mark_done);
    engine.register_fn("hashkey", PlayBookWrapper::hashkey);

    // Register ActionWrapper
    engine.register_type_with_name::<ActionWrapper>("Action");
    engine.register_fn("actor", ActionWrapper::actor);
    engine.register_fn("name", ActionWrapper::name);
    engine.register_fn("full_name", ActionWrapper::full_name);
    engine.register_fn("is_done", ActionWrapper::is_done);
    engine.register_fn("id", ActionWrapper::id);
    engine.register_fn("priority", ActionWrapper::priority);
    engine.register_fn("comments", ActionWrapper::comments);
    engine.register_fn("get", ActionWrapper::get);
    engine.register_fn("get_default", ActionWrapper::get_default);
    engine.register_fn("get_int", ActionWrapper::get_int);
    engine.register_fn("get_int_default", ActionWrapper::get_int_default);
    engine.register_fn("get_float", ActionWrapper::get_float);
    engine.register_fn("get_bool", ActionWrapper::get_bool);
    engine.register_fn("get_bool_false", ActionWrapper::get_bool_false);
    engine.register_fn("get_list", ActionWrapper::get_list);
    engine.register_fn("has", ActionWrapper::has);
    engine.register_fn("param_names", ActionWrapper::param_names);
    engine.register_fn("params", ActionWrapper::params);
    engine.register_fn("heroscript", ActionWrapper::heroscript);
    engine.register_fn("oneline", ActionWrapper::oneline);

    // Convenience function to create playbook directly from text
    engine.register_fn(
        "playbook_from_text",
        |text: &str| -> Result<PlayBookWrapper, Box<EvalAltResult>> {
            PlayBookBuilder::new().text(text).build()
        },
    );

    // Convenience function to create playbook from path
    engine.register_fn(
        "playbook_from_path",
        |path: &str| -> Result<PlayBookWrapper, Box<EvalAltResult>> {
            PlayBookBuilder::new().path(path).build()
        },
    );

    Ok(())
}

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

    #[test]
    fn test_playbook_builder() {
        let pb = PlayBookBuilder::new()
            .text("!!server.define name:'test' port:8080")
            .build()
            .unwrap();

        assert_eq!(pb.playbook.len(), 1);
    }

    #[test]
    fn test_action_wrapper() {
        let pb = PlayBookBuilder::new()
            .text("!!server.define name:'test' port:8080 enabled:true")
            .build()
            .unwrap();

        let action = pb.playbook.get("server.define").unwrap();
        let mut wrapper = ActionWrapper::from_action(action.clone());

        assert_eq!(wrapper.actor(), "server");
        assert_eq!(wrapper.name(), "define");
        assert_eq!(wrapper.get("name").unwrap(), "test");
        assert_eq!(wrapper.get_int("port").unwrap(), 8080);
        assert!(wrapper.get_bool("enabled"));
    }
}
