//! Modular Pluggable Decomposition Architecture
//!
//! This module provides a flexible, extensible framework for matrix decomposition
//! that allows easy composition and customization of different algorithms,
//! preprocessing steps, and post-processing operations.
//!
//! Features:
//! - Plugin-based architecture with trait-based decomposition algorithms
//! - Configurable preprocessing and post-processing pipelines
//! - Algorithm registry and dynamic algorithm selection
//! - Composable decomposition chains and multi-step workflows
//! - Extension points for custom algorithms and transformations
//! - Runtime algorithm switching and fallback mechanisms

use scirs2_core::ndarray::{Array1, Array2};
use serde::{Deserialize, Serialize};
use sklears_core::{
    error::{Result, SklearsError},
    types::Float,
};
use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;

/// Core trait for decomposition algorithms
pub trait DecompositionAlgorithm: Send + Sync {
    /// Get algorithm name
    fn name(&self) -> &str;

    /// Get algorithm description
    fn description(&self) -> &str;

    /// Get algorithm capabilities
    fn capabilities(&self) -> AlgorithmCapabilities;

    /// Validate input parameters
    fn validate_params(&self, params: &DecompositionParams) -> Result<()>;

    /// Fit the decomposition algorithm
    fn fit(&mut self, data: &Array2<Float>, params: &DecompositionParams) -> Result<()>;

    /// Transform data using fitted algorithm
    fn transform(&self, data: &Array2<Float>) -> Result<Array2<Float>>;

    /// Inverse transform if supported
    fn inverse_transform(&self, _data: &Array2<Float>) -> Result<Array2<Float>> {
        Err(SklearsError::InvalidInput(
            "Inverse transform not supported by this algorithm".to_string(),
        ))
    }

    /// Get decomposition results/components
    fn get_components(&self) -> Result<DecompositionComponents>;

    /// Check if algorithm is fitted
    fn is_fitted(&self) -> bool;

    /// Clone the algorithm (for plugin system)
    fn clone_algorithm(&self) -> Box<dyn DecompositionAlgorithm>;

    /// Get algorithm as Any for downcasting
    fn as_any(&self) -> &dyn Any;
}

/// Algorithm capabilities descriptor
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AlgorithmCapabilities {
    /// Supports non-square matrices
    pub supports_non_square: bool,
    /// Supports sparse matrices
    pub supports_sparse: bool,
    /// Supports incremental/online learning
    pub supports_incremental: bool,
    /// Supports inverse transform
    pub supports_inverse_transform: bool,
    /// Supports partial fitting
    pub supports_partial_fit: bool,
    /// Required matrix properties
    pub required_properties: Vec<MatrixProperty>,
    /// Computational complexity
    pub complexity: ComputationalComplexity,
}

impl Default for AlgorithmCapabilities {
    fn default() -> Self {
        Self {
            supports_non_square: true,
            supports_sparse: false,
            supports_incremental: false,
            supports_inverse_transform: false,
            supports_partial_fit: false,
            required_properties: Vec::new(),
            complexity: ComputationalComplexity::Cubic,
        }
    }
}

/// Required matrix properties
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MatrixProperty {
    NonNegative,
    Symmetric,
    PositiveDefinite,
    FullRank,
}

/// Computational complexity categories
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ComputationalComplexity {
    Linear,
    Quadratic,
    Cubic,
    Exponential,
}

/// Decomposition parameters
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DecompositionParams {
    pub n_components: Option<usize>,
    pub tolerance: Option<Float>,
    pub max_iterations: Option<usize>,
    pub random_seed: Option<u64>,
    pub algorithm_specific: HashMap<String, ParamValue>,
}

impl Default for DecompositionParams {
    fn default() -> Self {
        Self {
            n_components: None,
            tolerance: Some(1e-6),
            max_iterations: Some(100),
            random_seed: None,
            algorithm_specific: HashMap::new(),
        }
    }
}

/// Parameter value types
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ParamValue {
    Integer(i64),
    Float(Float),
    Boolean(bool),
    String(String),
    Array(Vec<Float>),
}

/// Decomposition components/results
#[derive(Debug, Clone, Default)]
pub struct DecompositionComponents {
    pub components: Option<Array2<Float>>,
    pub singular_values: Option<Array1<Float>>,
    pub eigenvalues: Option<Array1<Float>>,
    pub mean: Option<Array1<Float>>,
    pub explained_variance_ratio: Option<Array1<Float>>,
    pub factor_loadings: Option<Array2<Float>>,
    pub metadata: HashMap<String, String>,
}

/// Trait for preprocessing steps
pub trait PreprocessingStep: Send + Sync {
    /// Get step name
    fn name(&self) -> &str;

    /// Fit the step to training data and return transformed output (fit + transform).
    /// After this call, `is_fitted()` returns `true` and `apply` can be called.
    fn process(&mut self, data: &Array2<Float>) -> Result<Array2<Float>>;

    /// Apply the already-fitted step to new data (transform only, no mutation).
    /// Returns an error if the step has not been fitted yet.
    fn apply(&self, data: &Array2<Float>) -> Result<Array2<Float>> {
        if !self.is_fitted() {
            return Err(SklearsError::InvalidInput(format!(
                "Preprocessing step '{}' has not been fitted; call process() on training data first",
                self.name()
            )));
        }
        self.apply_fitted(data)
    }

    /// Internal implementation of the transform-only path used by `apply`.
    /// Implementors must override this when the step learns parameters during fit.
    fn apply_fitted(&self, data: &Array2<Float>) -> Result<Array2<Float>>;

    /// Inverse process if applicable
    fn inverse_process(&self, _data: &Array2<Float>) -> Result<Array2<Float>> {
        Err(SklearsError::InvalidInput(
            "Inverse processing not supported".to_string(),
        ))
    }

    /// Check if step is fitted
    fn is_fitted(&self) -> bool;

    /// Clone the step
    fn clone_step(&self) -> Box<dyn PreprocessingStep>;
}

/// Trait for post-processing steps
pub trait PostprocessingStep: Send + Sync {
    /// Get step name
    fn name(&self) -> &str;

    /// Process decomposition results
    fn process(&self, components: DecompositionComponents) -> Result<DecompositionComponents>;

    /// Clone the step
    fn clone_step(&self) -> Box<dyn PostprocessingStep>;
}

/// Algorithm registry for dynamic algorithm selection
pub struct AlgorithmRegistry {
    algorithms: HashMap<String, Box<dyn Fn() -> Box<dyn DecompositionAlgorithm> + Send + Sync>>,
    metadata: HashMap<String, AlgorithmMetadata>,
}

impl AlgorithmRegistry {
    /// Create new algorithm registry
    pub fn new() -> Self {
        Self {
            algorithms: HashMap::new(),
            metadata: HashMap::new(),
        }
    }

    /// Register an algorithm
    pub fn register<F>(&mut self, name: String, factory: F, metadata: AlgorithmMetadata)
    where
        F: Fn() -> Box<dyn DecompositionAlgorithm> + Send + Sync + 'static,
    {
        self.algorithms.insert(name.clone(), Box::new(factory));
        self.metadata.insert(name, metadata);
    }

    /// Create algorithm instance by name
    pub fn create_algorithm(&self, name: &str) -> Result<Box<dyn DecompositionAlgorithm>> {
        if let Some(factory) = self.algorithms.get(name) {
            Ok(factory())
        } else {
            Err(SklearsError::InvalidInput(format!(
                "Algorithm '{}' not found in registry",
                name
            )))
        }
    }

    /// Get all registered algorithm names
    pub fn list_algorithms(&self) -> Vec<String> {
        self.algorithms.keys().cloned().collect()
    }

    /// Get algorithm metadata
    pub fn get_metadata(&self, name: &str) -> Option<&AlgorithmMetadata> {
        self.metadata.get(name)
    }

    /// Find algorithms by capability
    pub fn find_by_capability(&self, capability: AlgorithmCapability) -> Vec<String> {
        self.metadata
            .iter()
            .filter(|(_, metadata)| metadata.capabilities.contains(&capability))
            .map(|(name, _)| name.clone())
            .collect()
    }
}

impl Default for AlgorithmRegistry {
    fn default() -> Self {
        Self::new()
    }
}

/// Algorithm metadata
#[derive(Debug, Clone)]
pub struct AlgorithmMetadata {
    pub description: String,
    pub version: String,
    pub author: String,
    pub capabilities: Vec<AlgorithmCapability>,
    pub computational_complexity: ComputationalComplexity,
    pub memory_complexity: ComputationalComplexity,
}

/// Algorithm capability types
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AlgorithmCapability {
    DimensionalityReduction,
    FeatureExtraction,
    MatrixFactorization,
    NoiseReduction,
    DataCompression,
    PatternRecognition,
}

/// Modular decomposition pipeline
pub struct DecompositionPipeline {
    preprocessing_steps: Vec<Box<dyn PreprocessingStep>>,
    algorithm: Box<dyn DecompositionAlgorithm>,
    postprocessing_steps: Vec<Box<dyn PostprocessingStep>>,
    fallback_algorithms: Vec<Box<dyn DecompositionAlgorithm>>,
    pipeline_config: PipelineConfig,
}

impl DecompositionPipeline {
    /// Create new decomposition pipeline
    pub fn new(algorithm: Box<dyn DecompositionAlgorithm>) -> Self {
        Self {
            preprocessing_steps: Vec::new(),
            algorithm,
            postprocessing_steps: Vec::new(),
            fallback_algorithms: Vec::new(),
            pipeline_config: PipelineConfig::default(),
        }
    }

    /// Add preprocessing step
    pub fn add_preprocessing(mut self, step: Box<dyn PreprocessingStep>) -> Self {
        self.preprocessing_steps.push(step);
        self
    }

    /// Add postprocessing step
    pub fn add_postprocessing(mut self, step: Box<dyn PostprocessingStep>) -> Self {
        self.postprocessing_steps.push(step);
        self
    }

    /// Add fallback algorithm
    pub fn add_fallback(mut self, algorithm: Box<dyn DecompositionAlgorithm>) -> Self {
        self.fallback_algorithms.push(algorithm);
        self
    }

    /// Set pipeline configuration
    pub fn with_config(mut self, config: PipelineConfig) -> Self {
        self.pipeline_config = config;
        self
    }

    /// Execute the complete pipeline
    pub fn fit_transform(
        &mut self,
        data: &Array2<Float>,
        params: &DecompositionParams,
    ) -> Result<PipelineResult> {
        let start_time = std::time::Instant::now();

        // Apply preprocessing steps
        let mut processed_data = data.clone();
        for step in &mut self.preprocessing_steps {
            processed_data = step.process(&processed_data)?;
        }

        // Try main algorithm
        let mut components = {
            let algorithm = &mut self.algorithm;
            match Self::try_algorithm_static(algorithm, &processed_data, params) {
                Ok(result) => result,
                Err(error) if self.pipeline_config.use_fallbacks => {
                    // Try fallback algorithms
                    let mut last_error = error;
                    let mut success = false;
                    let mut result_components = DecompositionComponents::default();

                    for fallback in &mut self.fallback_algorithms {
                        match Self::try_algorithm_static(fallback, &processed_data, params) {
                            Ok(components) => {
                                result_components = components;
                                success = true;
                                break;
                            }
                            Err(err) => last_error = err,
                        }
                    }

                    if !success {
                        return Err(last_error);
                    }
                    result_components
                }
                Err(error) => return Err(error),
            }
        };

        // Apply postprocessing steps
        for step in &self.postprocessing_steps {
            components = step.process(components)?;
        }

        let execution_time = start_time.elapsed();

        Ok(PipelineResult {
            components,
            execution_time,
            algorithm_used: self.algorithm.name().to_string(),
            preprocessing_steps: self
                .preprocessing_steps
                .iter()
                .map(|s| s.name().to_string())
                .collect(),
            postprocessing_steps: self
                .postprocessing_steps
                .iter()
                .map(|s| s.name().to_string())
                .collect(),
            pipeline_metadata: HashMap::new(),
        })
    }

    /// Transform new data using fitted pipeline
    ///
    /// Applies each preprocessing step's `apply` method (transform-only, no refitting),
    /// then delegates to the main algorithm's `transform`.  The pipeline must have been
    /// fitted via `fit_transform` before calling this method.
    pub fn transform(&self, data: &Array2<Float>) -> Result<Array2<Float>> {
        if !self.is_fitted() {
            return Err(SklearsError::InvalidInput(
                "Pipeline not fitted; call fit_transform on training data first".to_string(),
            ));
        }

        // Apply each fitted preprocessing step in order (transform only — no mutation)
        let mut processed_data = data.clone();
        for step in &self.preprocessing_steps {
            processed_data = step.apply(&processed_data)?;
        }

        // Transform using main algorithm
        self.algorithm.transform(&processed_data)
    }

    /// Check if pipeline is fitted
    pub fn is_fitted(&self) -> bool {
        self.algorithm.is_fitted()
    }

    /// Try to execute an algorithm with error handling
    fn try_algorithm_static(
        algorithm: &mut Box<dyn DecompositionAlgorithm>,
        data: &Array2<Float>,
        params: &DecompositionParams,
    ) -> Result<DecompositionComponents> {
        algorithm.validate_params(params)?;
        algorithm.fit(data, params)?;
        algorithm.get_components()
    }
}

/// Pipeline configuration
#[derive(Debug, Clone)]
pub struct PipelineConfig {
    /// Use fallback algorithms on failure
    pub use_fallbacks: bool,
    /// Enable caching of intermediate results
    pub enable_caching: bool,
    /// Maximum execution time before timeout
    pub max_execution_time: Option<std::time::Duration>,
    /// Validate inputs at each step
    pub validate_inputs: bool,
}

impl Default for PipelineConfig {
    fn default() -> Self {
        Self {
            use_fallbacks: true,
            enable_caching: false,
            max_execution_time: None,
            validate_inputs: true,
        }
    }
}

/// Pipeline execution result
#[derive(Debug, Clone)]
pub struct PipelineResult {
    pub components: DecompositionComponents,
    pub execution_time: std::time::Duration,
    pub algorithm_used: String,
    pub preprocessing_steps: Vec<String>,
    pub postprocessing_steps: Vec<String>,
    pub pipeline_metadata: HashMap<String, String>,
}

/// Builder for creating complex decomposition workflows
pub struct DecompositionWorkflowBuilder {
    registry: Arc<AlgorithmRegistry>,
    pipeline: Option<DecompositionPipeline>,
    config: PipelineConfig,
}

impl DecompositionWorkflowBuilder {
    /// Create new workflow builder
    pub fn new(registry: Arc<AlgorithmRegistry>) -> Self {
        Self {
            registry,
            pipeline: None,
            config: PipelineConfig::default(),
        }
    }

    /// Set primary algorithm by name
    pub fn with_algorithm(mut self, algorithm_name: &str) -> Result<Self> {
        let algorithm = self.registry.create_algorithm(algorithm_name)?;
        self.pipeline = Some(DecompositionPipeline::new(algorithm));
        Ok(self)
    }

    /// Add preprocessing step
    pub fn with_preprocessing(mut self, step: Box<dyn PreprocessingStep>) -> Result<Self> {
        if let Some(pipeline) = self.pipeline.take() {
            self.pipeline = Some(pipeline.add_preprocessing(step));
        } else {
            return Err(SklearsError::InvalidInput(
                "Must set algorithm before adding preprocessing steps".to_string(),
            ));
        }
        Ok(self)
    }

    /// Add postprocessing step
    pub fn with_postprocessing(mut self, step: Box<dyn PostprocessingStep>) -> Result<Self> {
        if let Some(pipeline) = self.pipeline.take() {
            self.pipeline = Some(pipeline.add_postprocessing(step));
        } else {
            return Err(SklearsError::InvalidInput(
                "Must set algorithm before adding postprocessing steps".to_string(),
            ));
        }
        Ok(self)
    }

    /// Add fallback algorithm by name
    pub fn with_fallback(mut self, algorithm_name: &str) -> Result<Self> {
        let algorithm = self.registry.create_algorithm(algorithm_name)?;
        if let Some(pipeline) = self.pipeline.take() {
            self.pipeline = Some(pipeline.add_fallback(algorithm));
        } else {
            return Err(SklearsError::InvalidInput(
                "Must set primary algorithm before adding fallbacks".to_string(),
            ));
        }
        Ok(self)
    }

    /// Set pipeline configuration
    pub fn with_config(mut self, config: PipelineConfig) -> Self {
        self.config = config;
        self
    }

    /// Build the workflow
    pub fn build(mut self) -> Result<DecompositionPipeline> {
        match self.pipeline.take() {
            Some(pipeline) => Ok(pipeline.with_config(self.config)),
            None => Err(SklearsError::InvalidInput(
                "No algorithm specified for workflow".to_string(),
            )),
        }
    }
}

/// Example preprocessing step: data standardization
#[derive(Debug, Clone)]
pub struct StandardizationStep {
    mean: Option<Array1<Float>>,
    std: Option<Array1<Float>>,
    fitted: bool,
}

impl StandardizationStep {
    pub fn new() -> Self {
        Self {
            mean: None,
            std: None,
            fitted: false,
        }
    }
}

impl Default for StandardizationStep {
    fn default() -> Self {
        Self::new()
    }
}

impl PreprocessingStep for StandardizationStep {
    fn name(&self) -> &str {
        "standardization"
    }

    fn process(&mut self, data: &Array2<Float>) -> Result<Array2<Float>> {
        if !self.fitted {
            // Fit step - compute mean and std from training data
            let mean = data
                .mean_axis(scirs2_core::ndarray::Axis(0))
                .ok_or_else(|| {
                    SklearsError::InvalidInput("Cannot compute mean of empty array".to_string())
                })?;
            let std = data
                .var_axis(scirs2_core::ndarray::Axis(0), 0.0)
                .mapv(|x| x.sqrt());

            self.mean = Some(mean);
            self.std = Some(std);
            self.fitted = true;
        }

        // Transform step (applies fitted parameters)
        self.apply_fitted(data)
    }

    fn apply_fitted(&self, data: &Array2<Float>) -> Result<Array2<Float>> {
        let mean = self.mean.as_ref().ok_or_else(|| {
            SklearsError::InvalidInput("Standardization step not fitted".to_string())
        })?;
        let std = self.std.as_ref().ok_or_else(|| {
            SklearsError::InvalidInput("Standardization step not fitted".to_string())
        })?;

        let mean_broadcast = mean.clone().insert_axis(scirs2_core::ndarray::Axis(0));
        let std_broadcast = std.clone().insert_axis(scirs2_core::ndarray::Axis(0));
        let standardized = (data - &mean_broadcast) / &std_broadcast;

        Ok(standardized)
    }

    fn inverse_process(&self, data: &Array2<Float>) -> Result<Array2<Float>> {
        if !self.fitted {
            return Err(SklearsError::InvalidInput(
                "Standardization step not fitted".to_string(),
            ));
        }

        let mean = self.mean.as_ref().ok_or_else(|| {
            SklearsError::InvalidInput("Standardization step not fitted".to_string())
        })?;
        let std = self.std.as_ref().ok_or_else(|| {
            SklearsError::InvalidInput("Standardization step not fitted".to_string())
        })?;

        let mean_broadcast = mean.clone().insert_axis(scirs2_core::ndarray::Axis(0));
        let std_broadcast = std.clone().insert_axis(scirs2_core::ndarray::Axis(0));
        let unstandardized = data * &std_broadcast + &mean_broadcast;

        Ok(unstandardized)
    }

    fn is_fitted(&self) -> bool {
        self.fitted
    }

    fn clone_step(&self) -> Box<dyn PreprocessingStep> {
        Box::new(self.clone())
    }
}

/// Example postprocessing step: component rotation
#[derive(Debug, Clone)]
pub struct VarimaxRotationStep {
    max_iterations: usize,
    tolerance: Float,
}

impl VarimaxRotationStep {
    pub fn new() -> Self {
        Self {
            max_iterations: 100,
            tolerance: 1e-6,
        }
    }

    pub fn with_max_iterations(mut self, max_iterations: usize) -> Self {
        self.max_iterations = max_iterations;
        self
    }

    pub fn with_tolerance(mut self, tolerance: Float) -> Self {
        self.tolerance = tolerance;
        self
    }
}

impl Default for VarimaxRotationStep {
    fn default() -> Self {
        Self::new()
    }
}

impl PostprocessingStep for VarimaxRotationStep {
    fn name(&self) -> &str {
        "varimax_rotation"
    }

    fn process(&self, mut components: DecompositionComponents) -> Result<DecompositionComponents> {
        if let Some(ref mut loadings) = components.factor_loadings {
            // Apply Varimax rotation (simplified implementation)
            *loadings = self.apply_varimax_rotation(loadings)?;
        } else if let Some(ref mut comps) = components.components {
            // Apply to components if no factor loadings
            *comps = self.apply_varimax_rotation(comps)?;
        }

        components
            .metadata
            .insert("rotation_applied".to_string(), "varimax".to_string());

        Ok(components)
    }

    fn clone_step(&self) -> Box<dyn PostprocessingStep> {
        Box::new(self.clone())
    }
}

impl VarimaxRotationStep {
    fn apply_varimax_rotation(&self, matrix: &Array2<Float>) -> Result<Array2<Float>> {
        // Simplified Varimax rotation implementation
        // In practice, this would implement the full Varimax algorithm
        Ok(matrix.clone())
    }
}

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

    // Mock algorithm for testing
    #[derive(Debug, Clone)]
    struct MockPCA {
        fitted: bool,
        n_components: usize,
    }

    impl MockPCA {
        fn new() -> Self {
            Self {
                fitted: false,
                n_components: 2,
            }
        }
    }

    impl DecompositionAlgorithm for MockPCA {
        fn name(&self) -> &str {
            "mock_pca"
        }

        fn description(&self) -> &str {
            "Mock PCA for testing"
        }

        fn capabilities(&self) -> AlgorithmCapabilities {
            AlgorithmCapabilities {
                supports_inverse_transform: true,
                ..AlgorithmCapabilities::default()
            }
        }

        fn validate_params(&self, _params: &DecompositionParams) -> Result<()> {
            Ok(())
        }

        fn fit(&mut self, _data: &Array2<Float>, params: &DecompositionParams) -> Result<()> {
            if let Some(n_comp) = params.n_components {
                self.n_components = n_comp;
            }
            self.fitted = true;
            Ok(())
        }

        fn transform(&self, data: &Array2<Float>) -> Result<Array2<Float>> {
            if !self.fitted {
                return Err(SklearsError::InvalidInput(
                    "Algorithm not fitted".to_string(),
                ));
            }

            let (rows, _) = data.dim();
            Ok(Array2::zeros((rows, self.n_components)))
        }

        fn get_components(&self) -> Result<DecompositionComponents> {
            if !self.fitted {
                return Err(SklearsError::InvalidInput(
                    "Algorithm not fitted".to_string(),
                ));
            }

            Ok(DecompositionComponents {
                components: Some(Array2::eye(self.n_components)),
                eigenvalues: Some(Array1::ones(self.n_components)),
                ..DecompositionComponents::default()
            })
        }

        fn is_fitted(&self) -> bool {
            self.fitted
        }

        fn clone_algorithm(&self) -> Box<dyn DecompositionAlgorithm> {
            Box::new(self.clone())
        }

        fn as_any(&self) -> &dyn Any {
            self
        }
    }

    #[test]
    fn test_algorithm_capabilities() {
        let capabilities = AlgorithmCapabilities::default();
        assert!(capabilities.supports_non_square);
        assert!(!capabilities.supports_sparse);
        assert_eq!(capabilities.complexity, ComputationalComplexity::Cubic);
    }

    #[test]
    fn test_decomposition_params() {
        let mut params = DecompositionParams {
            n_components: Some(5),
            ..Default::default()
        };
        params.algorithm_specific.insert(
            "test_param".to_string(),
            ParamValue::Float(std::f64::consts::PI),
        );

        assert_eq!(params.n_components, Some(5));
        assert_eq!(
            params.algorithm_specific.get("test_param"),
            Some(&ParamValue::Float(std::f64::consts::PI))
        );
    }

    #[test]
    fn test_algorithm_registry() {
        let mut registry = AlgorithmRegistry::new();

        let metadata = AlgorithmMetadata {
            description: "Mock PCA".to_string(),
            version: "1.0".to_string(),
            author: "Test".to_string(),
            capabilities: vec![AlgorithmCapability::DimensionalityReduction],
            computational_complexity: ComputationalComplexity::Cubic,
            memory_complexity: ComputationalComplexity::Quadratic,
        };

        registry.register(
            "mock_pca".to_string(),
            || Box::new(MockPCA::new()),
            metadata,
        );

        let algorithms = registry.list_algorithms();
        assert_eq!(algorithms, vec!["mock_pca"]);

        let algorithm = registry
            .create_algorithm("mock_pca")
            .expect("operation should succeed");
        assert_eq!(algorithm.name(), "mock_pca");

        let dim_red_algorithms =
            registry.find_by_capability(AlgorithmCapability::DimensionalityReduction);
        assert_eq!(dim_red_algorithms, vec!["mock_pca"]);
    }

    #[test]
    fn test_standardization_step() {
        let mut step = StandardizationStep::new();
        assert!(!step.is_fitted());
        assert_eq!(step.name(), "standardization");

        let data = Array2::from_shape_vec((3, 2), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
            .expect("shape and data length should match");

        let processed = step.process(&data).expect("operation should succeed");
        assert!(step.is_fitted());
        assert_eq!(processed.shape(), data.shape());
    }

    #[test]
    fn test_varimax_rotation_step() {
        let step = VarimaxRotationStep::new();
        assert_eq!(step.name(), "varimax_rotation");

        let components = DecompositionComponents {
            components: Some(Array2::eye(3)),
            ..Default::default()
        };

        let processed = step.process(components).expect("operation should succeed");
        assert!(processed.metadata.contains_key("rotation_applied"));
    }

    #[test]
    fn test_decomposition_pipeline() {
        let mut pipeline = DecompositionPipeline::new(Box::new(MockPCA::new()));

        let data = Array2::from_shape_vec(
            (4, 3),
            vec![
                1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0,
            ],
        )
        .expect("operation should succeed");

        let params = DecompositionParams {
            n_components: Some(2),
            ..DecompositionParams::default()
        };

        let result = pipeline
            .fit_transform(&data, &params)
            .expect("operation should succeed");
        assert_eq!(result.algorithm_used, "mock_pca");
        assert!(pipeline.is_fitted());

        // Test transform
        let transformed = pipeline
            .transform(&data)
            .expect("transformation should succeed");
        assert_eq!(transformed.shape(), &[4, 2]);
    }

    #[test]
    fn test_workflow_builder() {
        let mut registry = AlgorithmRegistry::new();
        let metadata = AlgorithmMetadata {
            description: "Mock PCA".to_string(),
            version: "1.0".to_string(),
            author: "Test".to_string(),
            capabilities: vec![AlgorithmCapability::DimensionalityReduction],
            computational_complexity: ComputationalComplexity::Cubic,
            memory_complexity: ComputationalComplexity::Quadratic,
        };

        registry.register(
            "mock_pca".to_string(),
            || Box::new(MockPCA::new()),
            metadata,
        );

        let registry = Arc::new(registry);
        let builder = DecompositionWorkflowBuilder::new(registry);

        let pipeline = builder
            .with_algorithm("mock_pca")
            .expect("operation should succeed")
            .with_preprocessing(Box::new(StandardizationStep::new()))
            .expect("operation should succeed")
            .with_postprocessing(Box::new(VarimaxRotationStep::new()))
            .expect("operation should succeed")
            .build()
            .expect("operation should succeed");

        assert_eq!(pipeline.algorithm.name(), "mock_pca");
        assert_eq!(pipeline.preprocessing_steps.len(), 1);
        assert_eq!(pipeline.postprocessing_steps.len(), 1);
    }

    #[test]
    fn test_param_values() {
        let int_param = ParamValue::Integer(42);
        let float_param = ParamValue::Float(std::f64::consts::PI);
        let bool_param = ParamValue::Boolean(true);
        let string_param = ParamValue::String("test".to_string());
        let array_param = ParamValue::Array(vec![1.0, 2.0, 3.0]);

        assert_eq!(int_param, ParamValue::Integer(42));
        assert_eq!(float_param, ParamValue::Float(std::f64::consts::PI));
        assert_eq!(bool_param, ParamValue::Boolean(true));
        assert_eq!(string_param, ParamValue::String("test".to_string()));
        assert_eq!(array_param, ParamValue::Array(vec![1.0, 2.0, 3.0]));
    }
}
