/*!
 * Unified Box Factory Architecture
 *
 * Phase 9.78: 統合BoxFactoryアーキテクチャ
 * すべてのBox生成（ビルトイン、ユーザー定義、プラグイン）を統一的に扱う
 *
 * Design principles:
 * - "Everything is Box" 哲学の実装レベルでの体現
 * - birth/finiライフサイクルの明確な責務分離
 * - 保守性と拡張性の劇的向上
 */

use crate::box_trait::NyashBox;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};

/// Factory Priority Policy for Box creation (Phase 15.5 "Everything is Plugin")
///
/// Determines the order in which different Box factories are consulted
/// during Box creation to solve the StringBox/IntegerBox plugin priority issue.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FactoryPolicy {
    /// Strict Plugin Priority: plugins > user > builtin
    /// ⚡ SOLVES THE CORE PROBLEM: Plugins have highest priority
    /// Use when plugins should completely replace builtins (Phase 15.5)
    StrictPluginFirst,

    /// Compatible Plugin Priority: plugins > builtin > user
    /// 🔧 Compatibility mode: Plugins first, but builtins before user-defined
    /// Use for gradual migration scenarios
    CompatPluginFirst,

    /// Legacy Builtin Priority: builtin > user > plugin (CURRENT DEFAULT)
    /// ⚠️ PROBLEMATIC: Plugins can never override builtins
    /// Only use for compatibility with existing setups
    BuiltinFirst,
}

/// Factory type classification for policy-based ordering
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FactoryType {
    /// Built-in factory (StringBox, IntegerBox, etc.)
    Builtin,
    /// User-defined Box factory
    User,
    /// Plugin-provided Box factory
    Plugin,
}

/// Runtime error types for Box operations
#[derive(Debug, thiserror::Error)]
pub enum RuntimeError {
    #[error("invalid operation: {message}")]
    InvalidOperation { message: String },
    #[error("type error: {message}")]
    TypeError { message: String },
}

/// Shared state for interpreter context (legacy compatibility)
#[derive(Debug, Default, Clone)]
pub struct SharedState;

impl SharedState {
    pub fn new() -> Self {
        Self
    }
}

/// Unified interface for all Box creation
pub trait BoxFactory: Send + Sync {
    /// Create a new Box instance with given arguments
    fn create_box(
        &self,
        name: &str,
        args: &[Box<dyn NyashBox>],
    ) -> Result<Box<dyn NyashBox>, RuntimeError>;

    /// Check if this factory is currently available
    fn is_available(&self) -> bool {
        true
    }

    /// Get list of Box types this factory can create
    fn box_types(&self) -> Vec<&str>;

    /// Check if this factory supports birth/fini lifecycle
    fn supports_birth(&self) -> bool {
        true
    }

    /// Identify builtin factory to enforce reserved-name protections
    fn is_builtin_factory(&self) -> bool {
        false
    }

    /// Identify factory type for policy-based priority ordering
    fn factory_type(&self) -> FactoryType {
        if self.is_builtin_factory() {
            FactoryType::Builtin
        } else {
            FactoryType::Plugin // Default assumption for external factories
        }
    }
}

/// Registry that manages all BoxFactory implementations
pub struct UnifiedBoxRegistry {
    /// Ordered list of factories with policy-based priority
    pub factories: Vec<Arc<dyn BoxFactory>>,

    /// Quick lookup cache for performance
    type_cache: RwLock<HashMap<String, usize>>, // maps type name to factory index

    /// Factory priority policy (Phase 15.5: Everything is Plugin)
    policy: FactoryPolicy,
}

impl UnifiedBoxRegistry {
    /// Create a new empty registry with default policy
    /// Phase 86: Default changed to StrictPluginFirst via with_env_policy()
    pub fn new() -> Self {
        Self::with_env_policy()
    }

    /// Create a new empty registry with specified policy
    pub fn with_policy(policy: FactoryPolicy) -> Self {
        Self {
            factories: Vec::new(),
            type_cache: RwLock::new(HashMap::new()),
            policy,
        }
    }

    /// Create registry with policy from environment variable (Phase 15.5 setup)
    pub fn with_env_policy() -> Self {
        let policy = match crate::config::env::box_factory_policy().as_deref() {
            Some("compat_plugin_first") => FactoryPolicy::CompatPluginFirst,
            Some("builtin_first") => FactoryPolicy::BuiltinFirst,
            Some("strict_plugin_first") | _ => FactoryPolicy::StrictPluginFirst, // Phase 15.5: Plugin First DEFAULT!
        };

        eprintln!(
            "[UnifiedBoxRegistry] 🎯 Factory Policy: {:?} (Phase 15.5: Everything is Plugin!)",
            policy
        );
        Self::with_policy(policy)
    }

    /// Get current factory policy
    pub fn get_policy(&self) -> FactoryPolicy {
        self.policy
    }

    /// Set factory policy and rebuild cache to reflect new priorities
    pub fn set_policy(&mut self, policy: FactoryPolicy) {
        if self.policy != policy {
            self.policy = policy;
            self.rebuild_cache();
        }
    }

    /// Rebuild type cache based on current policy
    fn rebuild_cache(&mut self) {
        // Clear existing cache
        let mut cache = self.type_cache.write().unwrap();
        cache.clear();

        // Get factory priority order based on policy
        let factory_order = self.get_factory_order_by_policy();

        // Re-register types with policy-based priority
        for &factory_index in factory_order.iter() {
            if let Some(factory) = self.factories.get(factory_index) {
                let types = factory.box_types();

                // Phase 87: Reserved core types using CoreBoxId (型安全化)
                fn is_reserved_type(name: &str) -> bool {
                    use crate::runtime::CoreBoxId;

                    // Phase 15.5: 環境変数でプラグイン優先モード時は保護解除
                    if crate::config::env::use_plugin_builtins() {
                        if let Some(types) = crate::config::env::plugin_override_types() {
                            if types.iter().any(|t| t == name) {
                                return false; // 予約型として扱わない
                            }
                        }
                    }

                    // Phase 87: CoreBoxId による型安全な判定
                    // core_required (6個) + 特殊型の一部を予約型として保護
                    CoreBoxId::from_name(name)
                        .map(|id| {
                            id.is_core_required()
                                || matches!(id, CoreBoxId::Result | CoreBoxId::Method)
                        })
                        .unwrap_or(false)
                }

                for type_name in types {
                    // Enforce reserved names: only builtin factory may claim them
                    if is_reserved_type(type_name) && !factory.is_builtin_factory() {
                        eprintln!(
                            "[UnifiedBoxRegistry] ❌ Rejecting registration of reserved type '{}' by non-builtin factory #{}",
                            type_name, factory_index
                        );
                        continue;
                    }

                    // Policy-based priority: first in order wins
                    let entry = cache.entry(type_name.to_string());
                    use std::collections::hash_map::Entry;
                    match entry {
                        Entry::Occupied(existing) => {
                            // Collision: type already claimed by higher-priority factory
                            eprintln!("[UnifiedBoxRegistry] ⚠️ Policy '{}': type '{}' kept by higher priority factory #{}, ignoring factory #{}",
                                      format!("{:?}", self.policy), existing.key(), existing.get(), factory_index);
                        }
                        Entry::Vacant(v) => {
                            v.insert(factory_index);
                        }
                    }
                }
            }
        }
    }

    /// Get factory indices ordered by current policy priority
    fn get_factory_order_by_policy(&self) -> Vec<usize> {
        let mut factory_indices: Vec<usize> = (0..self.factories.len()).collect();

        // Sort by factory type according to policy
        factory_indices.sort_by_key(|&index| {
            if let Some(factory) = self.factories.get(index) {
                let factory_type = factory.factory_type();

                match self.policy {
                    FactoryPolicy::StrictPluginFirst => match factory_type {
                        FactoryType::Plugin => 0,  // Highest priority
                        FactoryType::User => 1,    // Medium priority
                        FactoryType::Builtin => 2, // Lowest priority
                    },
                    FactoryPolicy::CompatPluginFirst => match factory_type {
                        FactoryType::Plugin => 0,  // Highest priority
                        FactoryType::Builtin => 1, // Medium priority
                        FactoryType::User => 2,    // Lowest priority
                    },
                    FactoryPolicy::BuiltinFirst => match factory_type {
                        FactoryType::Builtin => 0, // Highest priority (current default)
                        FactoryType::User => 1,    // Medium priority
                        FactoryType::Plugin => 2,  // Lowest priority
                    },
                }
            } else {
                999 // Invalid factory index, put at end
            }
        });

        factory_indices
    }

    /// Register a new factory (policy-aware)
    pub fn register(&mut self, factory: Arc<dyn BoxFactory>) {
        // Simply add to the factory list
        self.factories.push(factory);

        // Rebuild cache to apply policy-based priority ordering
        // This ensures new factory is properly integrated with current policy
        self.rebuild_cache();
    }

    /// Create a Box using the unified interface
    pub fn create_box(
        &self,
        name: &str,
        args: &[Box<dyn NyashBox>],
    ) -> Result<Box<dyn NyashBox>, RuntimeError> {
        // Prefer plugin-builtins when enabled and provider is available in v2 registry
        // BUT: Skip if plugins are explicitly disabled
        let plugins_disabled = crate::config::env::disable_plugins();
        if !plugins_disabled && crate::config::env::use_plugin_builtins() {
            use crate::runtime::{get_global_registry, BoxProvider};
            // Allowlist types for override: env NYASH_PLUGIN_OVERRIDE_TYPES="ArrayBox,MapBox" (default: none)
            let allow: Vec<String> = crate::config::env::plugin_override_types()
                .unwrap_or_default();
            if allow.iter().any(|t| t == name) {
                let v2 = get_global_registry();
                if let Some(provider) = v2.get_provider(name) {
                    if let BoxProvider::Plugin(_lib) = provider {
                        return v2.create_box(name, args).map_err(|e| {
                            RuntimeError::InvalidOperation {
                                message: format!("Plugin Box creation failed: {}", e),
                            }
                        });
                    }
                }
            }
        }
        // Check cache first
        let cache = self.type_cache.read().unwrap();
        if let Some(&factory_index) = cache.get(name) {
            if let Some(factory) = self.factories.get(factory_index) {
                if factory.is_available() {
                    return factory.create_box(name, args);
                }
            }
        }
        drop(cache);

        // Linear search through all factories with fallback support
        let mut last_error: Option<RuntimeError> = None;
        let factory_order = self.get_factory_order_by_policy();

        for &factory_index in factory_order.iter() {
            if let Some(factory) = self.factories.get(factory_index) {
                if !factory.is_available() {
                    continue;
                }

                // For factories that advertise types, check if they support this type
                let box_types = factory.box_types();
                if !box_types.is_empty() && !box_types.contains(&name) {
                    continue;
                }

                // Try to create the box
                if crate::config::env::debug_plugin() {
                    eprintln!(
                        "[UnifiedBoxRegistry] try factory#{} {:?} for {}",
                        factory_index,
                        factory.factory_type(),
                        name
                    );
                }

                match factory.create_box(name, args) {
                    Ok(boxed) => return Ok(boxed),
                    Err(e) => {
                        // FileBox special case: handle fallback based on mode
                        if name == "FileBox" {
                            if let Some(fallback_result) = self.try_filebox_fallback(name, args, &e)
                            {
                                return fallback_result;
                            }
                        }

                        // For other boxes or if FileBox fallback not applicable, continue
                        last_error = Some(e);
                        continue;
                    }
                }
            }
        }

        // Final fallback: if v2 plugin registry has a provider for this name, try it once
        {
            let v2 = crate::runtime::get_global_registry();
            if let Some(_prov) = v2.get_provider(name) {
                if let Ok(b) = v2.create_box(name, args) {
                    return Ok(b);
                }
            }
        }

        // Return the last error if we have one, otherwise generic error
        if let Some(err) = last_error {
            Err(err)
        } else {
            Err(RuntimeError::InvalidOperation {
                message: format!("Unknown Box type: {}", name),
            })
        }
    }

    /// Try FileBox fallback based on NYASH_FILEBOX_MODE
    /// Returns Some(result) if fallback is applicable, None if should continue trying other factories
    fn try_filebox_fallback(
        &self,
        name: &str,
        args: &[Box<dyn NyashBox>],
        original_error: &RuntimeError,
    ) -> Option<Result<Box<dyn NyashBox>, RuntimeError>> {
        use crate::runner::modes::common_util::provider_registry;

        let mode = provider_registry::read_filebox_mode_from_env();

        match mode {
            provider_registry::FileBoxMode::PluginOnly => {
                // Fail-Fast: return the original error immediately
                eprintln!(
                    "[FileBox] Plugin creation failed in plugin-only mode: {}",
                    original_error
                );
                Some(Err(RuntimeError::InvalidOperation {
                    message: format!(
                        "FileBox plugin creation failed (plugin-only mode): {}",
                        original_error
                    ),
                }))
            }
            provider_registry::FileBoxMode::Auto => {
                // Auto mode: try fallback to builtin/core-ro
                eprintln!(
                    "[FileBox] Plugin creation failed, falling back to builtin/core-ro: {}",
                    original_error
                );

                // Try builtin factory if available
                for factory in &self.factories {
                    if factory.is_builtin_factory() && factory.box_types().contains(&name) {
                        match factory.create_box(name, args) {
                            Ok(boxed) => {
                                eprintln!("[FileBox] Successfully created with builtin factory");
                                return Some(Ok(boxed));
                            }
                            Err(e) => {
                                eprintln!("[FileBox] Builtin factory also failed: {}", e);
                            }
                        }
                    }
                }

                // If builtin failed, return None to continue with other factories
                None
            }
            provider_registry::FileBoxMode::CoreRo => {
                // Core-ro mode: try builtin factory
                eprintln!("[FileBox] Using core-ro mode, trying builtin factory");

                for factory in &self.factories {
                    if factory.is_builtin_factory() && factory.box_types().contains(&name) {
                        match factory.create_box(name, args) {
                            Ok(boxed) => return Some(Ok(boxed)),
                            Err(e) => {
                                return Some(Err(RuntimeError::InvalidOperation {
                                    message: format!("FileBox core-ro creation failed: {}", e),
                                }));
                            }
                        }
                    }
                }

                None
            }
        }
    }

    /// Check whether a type name is known to the registry
    pub fn has_type(&self, name: &str) -> bool {
        // Check cache first
        {
            let cache = self.type_cache.read().unwrap();
            if let Some(&idx) = cache.get(name) {
                if let Some(factory) = self.factories.get(idx) {
                    if factory.is_available() {
                        return true;
                    }
                }
            }
        }
        // Fallback: scan factories that can enumerate types
        for factory in &self.factories {
            if !factory.is_available() {
                continue;
            }
            let types = factory.box_types();
            if !types.is_empty() && types.contains(&name) {
                return true;
            }
        }
        false
    }

    /// Get all available Box types
    pub fn available_types(&self) -> Vec<String> {
        let mut types = Vec::new();
        for factory in &self.factories {
            if factory.is_available() {
                for type_name in factory.box_types() {
                    types.push(type_name.to_string());
                }
            }
        }
        types.sort();
        types.dedup();
        types
    }
}

pub mod builtin;
pub mod plugin;
/// Re-export submodules
#[cfg(feature = "interpreter-legacy")]
pub mod user_defined;

// Phase 15.5: Separated builtin implementations for easy deletion
pub mod builtin_impls;

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

    #[test]
    fn test_registry_creation() {
        let registry = UnifiedBoxRegistry::new();
        assert_eq!(registry.available_types().len(), 0);
    }

    // Phase 86: BoxFactory Priority Tests

    #[test]
    fn test_default_policy_is_strict_plugin_first() {
        let prev = crate::config::env::box_factory_policy();
        // Ensure NYASH_BOX_FACTORY_POLICY is not set
        crate::config::env::reset_box_factory_policy();

        let registry = UnifiedBoxRegistry::new();
        assert_eq!(
            registry.get_policy(),
            FactoryPolicy::StrictPluginFirst,
            "Default policy should be StrictPluginFirst"
        );

        if let Some(v) = prev {
            crate::config::env::set_box_factory_policy(&v);
        }
    }

    #[test]
    fn test_env_policy_override() {
        let prev = crate::config::env::box_factory_policy();

        // Test builtin_first override
        crate::config::env::set_box_factory_policy("builtin_first");
        let registry = UnifiedBoxRegistry::with_env_policy();
        assert_eq!(registry.get_policy(), FactoryPolicy::BuiltinFirst);

        // Test compat_plugin_first override
        crate::config::env::set_box_factory_policy("compat_plugin_first");
        let registry = UnifiedBoxRegistry::with_env_policy();
        assert_eq!(registry.get_policy(), FactoryPolicy::CompatPluginFirst);

        // Test strict_plugin_first explicit
        crate::config::env::set_box_factory_policy("strict_plugin_first");
        let registry = UnifiedBoxRegistry::with_env_policy();
        assert_eq!(registry.get_policy(), FactoryPolicy::StrictPluginFirst);

        // Cleanup
        if let Some(v) = prev {
            crate::config::env::set_box_factory_policy(&v);
        } else {
            crate::config::env::reset_box_factory_policy();
        }
    }

    #[test]
    fn test_reserved_type_protection() {
        // Ensure env vars are cleared
        // Note: can't clear env vars directly; tests rely on default behavior

        // Create a mock non-builtin factory that claims a reserved type
        struct MockPluginFactory;

        impl BoxFactory for MockPluginFactory {
            fn create_box(
                &self,
                name: &str,
                _args: &[Box<dyn NyashBox>],
            ) -> Result<Box<dyn NyashBox>, RuntimeError> {
                // This should never be called for StringBox since it's rejected
                Err(RuntimeError::InvalidOperation {
                    message: format!("Mock factory attempted to create: {}", name),
                })
            }

            fn box_types(&self) -> Vec<&str> {
                vec!["StringBox", "CustomBox"] // Claims a reserved type
            }

            fn is_builtin_factory(&self) -> bool {
                false // Non-builtin
            }

            fn factory_type(&self) -> FactoryType {
                FactoryType::Plugin
            }
        }

        let mut registry = UnifiedBoxRegistry::new();
        registry.register(Arc::new(MockPluginFactory));

        // Test that create_box fails for StringBox (not registered in cache)
        let result = registry.create_box("StringBox", &[]);
        assert!(
            result.is_err(),
            "StringBox creation should fail when only non-builtin factory provides it"
        );

        // Verify the error message indicates it's unknown (not in cache)
        if let Err(e) = result {
            let err_msg = format!("{}", e);
            assert!(
                err_msg.contains("Unknown Box type") || err_msg.contains("Mock factory"),
                "Error message should indicate StringBox is not properly registered: {}",
                err_msg
            );
        }
    }

    #[test]
    fn test_plugin_override_with_env() {
        // This test verifies that NYASH_USE_PLUGIN_BUILTINS or
        // NYASH_PLUGIN_OVERRIDE_TYPES allows plugins to override reserved types

        // Create a mock plugin factory
        struct MockPluginFactory;

        impl BoxFactory for MockPluginFactory {
            fn create_box(
                &self,
                name: &str,
                _args: &[Box<dyn NyashBox>],
            ) -> Result<Box<dyn NyashBox>, RuntimeError> {
                if name == "StringBox" {
                    // Return a mock box for testing
                    Err(RuntimeError::InvalidOperation {
                        message: "Mock plugin StringBox".to_string(),
                    })
                } else {
                    Err(RuntimeError::InvalidOperation {
                        message: "Unknown".to_string(),
                    })
                }
            }

            fn box_types(&self) -> Vec<&str> {
                vec!["StringBox"]
            }

            fn is_builtin_factory(&self) -> bool {
                false
            }

            fn factory_type(&self) -> FactoryType {
                FactoryType::Plugin
            }
        }

        // Test with NYASH_PLUGIN_OVERRIDE_TYPES
        crate::config::env::set_box_factory_policy("strict_plugin_first"); // ensure plugin first
        std::env::set_var("NYASH_PLUGIN_OVERRIDE_TYPES", "StringBox");
        let mut registry = UnifiedBoxRegistry::new();
        registry.register(Arc::new(MockPluginFactory));

        // With override enabled, StringBox should not be rejected
        // (Note: has_type will be false because create_box fails, but registration shouldn't be rejected)
        std::env::remove_var("NYASH_PLUGIN_OVERRIDE_TYPES");
    }

    #[test]
    fn test_non_reserved_plugin_priority() {
        // Test that non-reserved types (like FileBox) can be overridden by plugins

        struct MockBuiltinFactory;
        impl BoxFactory for MockBuiltinFactory {
            fn create_box(
                &self,
                _name: &str,
                _args: &[Box<dyn NyashBox>],
            ) -> Result<Box<dyn NyashBox>, RuntimeError> {
                Err(RuntimeError::InvalidOperation {
                    message: "Builtin FileBox".to_string(),
                })
            }

            fn box_types(&self) -> Vec<&str> {
                vec!["FileBox"]
            }

            fn is_builtin_factory(&self) -> bool {
                true
            }

            fn factory_type(&self) -> FactoryType {
                FactoryType::Builtin
            }
        }

        struct MockPluginFactory;
        impl BoxFactory for MockPluginFactory {
            fn create_box(
                &self,
                _name: &str,
                _args: &[Box<dyn NyashBox>],
            ) -> Result<Box<dyn NyashBox>, RuntimeError> {
                Err(RuntimeError::InvalidOperation {
                    message: "Plugin FileBox".to_string(),
                })
            }

            fn box_types(&self) -> Vec<&str> {
                vec!["FileBox"]
            }

            fn is_builtin_factory(&self) -> bool {
                false
            }

            fn factory_type(&self) -> FactoryType {
                FactoryType::Plugin
            }
        }

        let mut registry = UnifiedBoxRegistry::new();

        // Register builtin first, then plugin
        registry.register(Arc::new(MockBuiltinFactory));
        registry.register(Arc::new(MockPluginFactory));

        // With StrictPluginFirst policy, plugin should have priority
        // Both fail, but the error message tells us which was tried first
        let result = registry.create_box("FileBox", &[]);
        assert!(result.is_err());

        // The error should be from plugin (tried first) or builtin (fallback)
        // This test just verifies the mechanism works
    }
}
