//! # Plugin System (Optional)
//!
//! Provides optional plugin architecture for extending rust-tui with custom components.
//! Plugins can register custom widgets, themes, event handlers, and features.
//!
//! ## Design
//!
//! The plugin system is completely optional and designed to:
//! - Not impact performance of non-plugin applications
//! - Enable dynamic component registration
//! - Manage plugin lifecycle (load → enable → disable → unload)
//! - Provide plugin isolation through clear API boundaries
//!
//! ## Example
//!
//! ```ignore
//! use tools_tui_shared::plugin::{Plugin, PluginRegistry, PluginContext};
//!
//! struct MyPlugin;
//!
//! impl Plugin for MyPlugin {
//!     fn name(&self) -> &'static str { "my-plugin" }
//!     fn version(&self) -> &'static str { "0.1.0" }
//!     fn on_load(&mut self, _ctx: &mut PluginContext) -> PluginResult<()> {
//!         println!("Plugin loaded!");
//!         Ok(())
//!     }
//! }
//!
//! let mut registry = PluginRegistry::new();
//! registry.register(Box::new(MyPlugin))?;
//! registry.load_all()?;
//! ```

use std::collections::HashMap;
use thiserror::Error;

/// Plugin system errors
#[derive(Error, Debug)]
pub enum PluginError {
    /// Plugin not found
    #[error("Plugin not found: {0}")]
    NotFound(String),

    /// Plugin already registered
    #[error("Plugin already registered: {0}")]
    AlreadyRegistered(String),

    /// Plugin failed to load
    #[error("Failed to load plugin {0}: {1}")]
    LoadFailed(String, String),

    /// Plugin failed to enable
    #[error("Failed to enable plugin {0}: {1}")]
    EnableFailed(String, String),

    /// Plugin failed to disable
    #[error("Failed to disable plugin {0}: {1}")]
    DisableFailed(String, String),

    /// Plugin failed to unload
    #[error("Failed to unload plugin {0}: {1}")]
    UnloadFailed(String, String),

    /// Custom plugin error
    #[error("Plugin error: {0}")]
    Custom(String),
}

/// Result type for plugin operations
pub type PluginResult<T> = Result<T, PluginError>;

/// Plugin context - API provided to plugins
///
/// Plugins use this to access TUI features and share state.
#[derive(Debug, Default)]
pub struct PluginContext {
    /// Plugin metadata
    pub metadata: HashMap<String, String>,

    /// Plugin state (key-value storage)
    pub state: HashMap<String, String>,

    /// Shared data across plugins
    pub shared_data: HashMap<String, Box<dyn std::any::Any>>,
}

impl PluginContext {
    /// Create new plugin context
    #[must_use]
    pub fn new() -> Self {
        Self {
            metadata: HashMap::new(),
            state: HashMap::new(),
            shared_data: HashMap::new(),
        }
    }

    /// Set metadata value
    pub fn set_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
        self.metadata.insert(key.into(), value.into());
    }

    /// Get metadata value
    #[must_use]
    pub fn get_metadata(&self, key: &str) -> Option<&str> {
        self.metadata.get(key).map(String::as_str)
    }

    /// Set plugin state
    pub fn set_state(&mut self, key: impl Into<String>, value: impl Into<String>) {
        self.state.insert(key.into(), value.into());
    }

    /// Get plugin state
    #[must_use]
    pub fn get_state(&self, key: &str) -> Option<&str> {
        self.state.get(key).map(String::as_str)
    }
}

/// Plugin trait - all plugins must implement this
///
/// Plugins follow a lifecycle:
/// 1. `on_load()` - Plugin is loaded and initialized
/// 2. `on_enable()` - Plugin is activated
/// 3. `on_event()` - Handle application events
/// 4. `on_disable()` - Plugin is deactivated
/// 5. `on_unload()` - Plugin is unloaded and cleaned up
pub trait Plugin: Send {
    /// Plugin name (should be unique)
    fn name(&self) -> &'static str;

    /// Plugin version (e.g., "0.1.0")
    fn version(&self) -> &'static str;

    /// Plugin description
    fn description(&self) -> &'static str {
        ""
    }

    /// Called when plugin is loaded
    /// Use this to initialize resources
    ///
    /// # Errors
    /// Returns an error if initialization fails.
    fn on_load(&mut self, ctx: &mut PluginContext) -> PluginResult<()> {
        let _ = ctx;
        Ok(())
    }

    /// Called when plugin is enabled
    /// Use this to start active functionality
    ///
    /// # Errors
    /// Returns an error if enabling fails.
    fn on_enable(&mut self, ctx: &mut PluginContext) -> PluginResult<()> {
        let _ = ctx;
        Ok(())
    }

    /// Called when plugin is disabled
    /// Use this to stop active functionality
    ///
    /// # Errors
    /// Returns an error if disabling fails.
    fn on_disable(&mut self, ctx: &mut PluginContext) -> PluginResult<()> {
        let _ = ctx;
        Ok(())
    }

    /// Called when plugin is unloaded
    /// Use this to clean up resources
    ///
    /// # Errors
    /// Returns an error if unloading fails.
    fn on_unload(&mut self, ctx: &mut PluginContext) -> PluginResult<()> {
        let _ = ctx;
        Ok(())
    }

    /// Handle custom events (if supported)
    ///
    /// # Errors
    /// Returns an error if event handling fails.
    fn on_event(&mut self, event: &str, ctx: &mut PluginContext) -> PluginResult<()> {
        let _ = (event, ctx);
        Ok(())
    }

    /// Check if plugin is enabled
    #[must_use]
    fn is_enabled(&self) -> bool {
        true
    }
}

/// Plugin metadata for registry
#[derive(Debug, Clone)]
struct PluginMetadata {
    #[allow(dead_code)]
    name: String,
    #[allow(dead_code)]
    version: String,
    loaded: bool,
    enabled: bool,
}

/// Plugin registry - manages all plugins
///
/// Central point for plugin management.
#[derive(Default)]
pub struct PluginRegistry {
    plugins: HashMap<String, Box<dyn Plugin>>,
    metadata: HashMap<String, PluginMetadata>,
    context: PluginContext,
}

impl PluginRegistry {
    /// Create new empty registry
    #[must_use]
    pub fn new() -> Self {
        Self {
            plugins: HashMap::new(),
            metadata: HashMap::new(),
            context: PluginContext::new(),
        }
    }

    /// Register a plugin
    ///
    /// # Errors
    /// Returns an error if the plugin is already registered or fails to load.
    pub fn register(&mut self, mut plugin: Box<dyn Plugin>) -> PluginResult<()> {
        let name = plugin.name().to_string();

        if self.plugins.contains_key(&name) {
            return Err(PluginError::AlreadyRegistered(name));
        }

        // Call on_load immediately
        plugin
            .on_load(&mut self.context)
            .map_err(|e| PluginError::LoadFailed(name.clone(), e.to_string()))?;

        self.plugins.insert(name.clone(), plugin);
        self.metadata.insert(
            name.clone(),
            PluginMetadata {
                name: name.clone(),
                version: self.plugins[&name].version().to_string(),
                loaded: true,
                enabled: false,
            },
        );

        Ok(())
    }

    /// Load all registered plugins
    ///
    /// # Errors
    /// Returns an error if any plugin fails to load.
    pub fn load_all(&mut self) -> PluginResult<()> {
        let names: Vec<_> = self.plugins.keys().cloned().collect();
        for name in names {
            self.load(&name)?;
        }
        Ok(())
    }

    /// Load a specific plugin
    ///
    /// # Errors
    /// Returns an error if the plugin fails to load.
    pub fn load(&mut self, name: &str) -> PluginResult<()> {
        if let Some(metadata) = self.metadata.get_mut(name) {
            metadata.loaded = true;
        }
        Ok(())
    }

    /// Enable a plugin
    ///
    /// # Errors
    /// Returns an error if the plugin is not found or fails to enable.
    pub fn enable(&mut self, name: &str) -> PluginResult<()> {
        if let Some(plugin) = self.plugins.get_mut(name) {
            plugin
                .on_enable(&mut self.context)
                .map_err(|e| PluginError::EnableFailed(name.to_string(), e.to_string()))?;

            if let Some(metadata) = self.metadata.get_mut(name) {
                metadata.enabled = true;
            }
            Ok(())
        } else {
            Err(PluginError::NotFound(name.to_string()))
        }
    }

    /// Enable all plugins
    ///
    /// # Errors
    /// Returns an error if any plugin fails to enable.
    pub fn enable_all(&mut self) -> PluginResult<()> {
        let names: Vec<_> = self.plugins.keys().cloned().collect();
        for name in names {
            self.enable(&name)?;
        }
        Ok(())
    }

    /// Disable a plugin
    ///
    /// # Errors
    /// Returns an error if the plugin is not found or fails to disable.
    pub fn disable(&mut self, name: &str) -> PluginResult<()> {
        if let Some(plugin) = self.plugins.get_mut(name) {
            plugin
                .on_disable(&mut self.context)
                .map_err(|e| PluginError::DisableFailed(name.to_string(), e.to_string()))?;

            if let Some(metadata) = self.metadata.get_mut(name) {
                metadata.enabled = false;
            }
            Ok(())
        } else {
            Err(PluginError::NotFound(name.to_string()))
        }
    }

    /// Disable all plugins
    ///
    /// # Errors
    /// Continues on errors and returns Ok if at least one plugin is disabled.
    pub fn disable_all(&mut self) -> PluginResult<()> {
        let names: Vec<_> = self.plugins.keys().cloned().collect();
        for name in names {
            let _ = self.disable(&name); // Continue on errors
        }
        Ok(())
    }

    /// Unload a plugin
    ///
    /// # Errors
    /// Returns an error if the plugin is not found or fails to unload.
    pub fn unload(&mut self, name: &str) -> PluginResult<()> {
        if let Some(mut plugin) = self.plugins.remove(name) {
            plugin
                .on_unload(&mut self.context)
                .map_err(|e| PluginError::UnloadFailed(name.to_string(), e.to_string()))?;
            self.metadata.remove(name);
            Ok(())
        } else {
            Err(PluginError::NotFound(name.to_string()))
        }
    }

    /// Unload all plugins
    ///
    /// # Errors
    /// Continues on errors and returns Ok if at least one plugin is unloaded.
    pub fn unload_all(&mut self) -> PluginResult<()> {
        let names: Vec<_> = self.plugins.keys().cloned().collect();
        for name in names {
            let _ = self.unload(&name); // Continue on errors
        }
        Ok(())
    }

    /// Check if plugin is loaded
    #[must_use]
    pub fn is_loaded(&self, name: &str) -> bool {
        self.metadata.get(name).is_some_and(|m| m.loaded)
    }

    /// Check if plugin is enabled
    #[must_use]
    pub fn is_enabled(&self, name: &str) -> bool {
        self.metadata.get(name).is_some_and(|m| m.enabled)
    }

    /// Get plugin count
    #[must_use]
    pub fn count(&self) -> usize {
        self.plugins.len()
    }

    /// List all plugins
    ///
    /// # Panics
    /// Panics if a plugin is registered but its metadata is missing (inconsistent state).
    #[must_use]
    pub fn list(&self) -> Vec<(&str, &str, bool, bool)> {
        self.plugins
            .iter()
            .map(|(name, plugin)| {
                let meta = self.metadata.get(name).unwrap();
                (name.as_str(), plugin.version(), meta.loaded, meta.enabled)
            })
            .collect()
    }

    /// Broadcast event to all plugins
    ///
    /// # Errors
    /// Returns an error if any plugin fails to handle the event.
    pub fn broadcast_event(&mut self, event: &str) -> PluginResult<()> {
        for plugin in self.plugins.values_mut() {
            plugin.on_event(event, &mut self.context)?;
        }
        Ok(())
    }

    /// Get mutable reference to plugin context
    #[must_use]
    pub const fn context_mut(&mut self) -> &mut PluginContext {
        &mut self.context
    }

    /// Get immutable reference to plugin context
    #[must_use]
    pub const fn context(&self) -> &PluginContext {
        &self.context
    }
}

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

    struct TestPlugin {
        loaded: bool,
        enabled: bool,
    }

    impl TestPlugin {
        fn new() -> Self {
            Self {
                loaded: false,
                enabled: false,
            }
        }
    }

    impl Plugin for TestPlugin {
        fn name(&self) -> &'static str {
            "test-plugin"
        }

        fn version(&self) -> &'static str {
            "0.1.0"
        }

        fn on_load(&mut self, _ctx: &mut PluginContext) -> PluginResult<()> {
            self.loaded = true;
            Ok(())
        }

        fn on_enable(&mut self, _ctx: &mut PluginContext) -> PluginResult<()> {
            self.enabled = true;
            Ok(())
        }

        fn on_disable(&mut self, _ctx: &mut PluginContext) -> PluginResult<()> {
            self.enabled = false;
            Ok(())
        }

        fn on_unload(&mut self, _ctx: &mut PluginContext) -> PluginResult<()> {
            self.loaded = false;
            Ok(())
        }
    }

    #[test]
    fn test_plugin_context_creation() {
        let ctx = PluginContext::new();
        assert!(ctx.metadata.is_empty());
        assert!(ctx.state.is_empty());
    }

    #[test]
    fn test_plugin_registry_creation() {
        let registry = PluginRegistry::new();
        assert_eq!(registry.count(), 0);
    }

    #[test]
    fn test_register_plugin() {
        let mut registry = PluginRegistry::new();
        let plugin = TestPlugin::new();
        assert!(registry.register(Box::new(plugin)).is_ok());
        assert_eq!(registry.count(), 1);
        assert!(registry.is_loaded("test-plugin"));
    }

    #[test]
    fn test_plugin_lifecycle() {
        let mut registry = PluginRegistry::new();
        let plugin = TestPlugin::new();
        registry.register(Box::new(plugin)).unwrap();

        assert!(registry.enable("test-plugin").is_ok());
        assert!(registry.is_enabled("test-plugin"));

        assert!(registry.disable("test-plugin").is_ok());
        assert!(!registry.is_enabled("test-plugin"));
    }
}
