pmat 3.11.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
impl CodeIntelligence {
    /// Performs comprehensive code analysis with caching and parallel execution.
    ///
    /// This is the main entry point for code analysis. It checks the cache first,
    /// then runs the requested analysis types in parallel where possible.
    ///
    /// # Performance Contract
    ///
    /// - Cache lookup: O(1) amortized
    /// - Analysis time: O(n) where n = project size in lines of code
    /// - Memory: Bounded by cache size + working set
    /// - Parallelization: Automatic for independent analysis types
    ///
    /// # Error Handling
    ///
    /// Implements graceful degradation:
    /// 1. Cache misses → full analysis
    /// 2. Parse errors → partial results where possible
    /// 3. I/O errors → cached results when available
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pmat::services::code_intelligence::{
    ///     CodeIntelligence, AnalysisRequest, AnalysisType
    /// };
    /// use tempfile::tempdir;
    /// use std::fs;
    ///
    /// # tokio_test::block_on(async {
    /// // Create a temporary Rust project
    /// let dir = tempdir().expect("tempdir");
    /// let main_rs = dir.path().join("main.rs");
    /// fs::write(&main_rs, "fn main() { println!(\"Hello, world!\"); }").expect("write");
    ///
    /// let intelligence = CodeIntelligence::new();
    /// let request = AnalysisRequest {
    ///     project_path: dir.path().to_string_lossy().to_string(),
    ///     analysis_types: vec![AnalysisType::DependencyGraph],
    ///     include_patterns: vec!["*.rs".to_string()],
    ///     exclude_patterns: vec![],
    ///     max_depth: Some(10),
    ///     parallel: true,
    /// };
    ///
    /// let result = intelligence.analyze_comprehensive(request.clone()).await;
    /// assert!(result.is_ok());
    ///
    /// let report = result.expect("analysis succeeded");
    /// assert!(report.dependency_graph.is_some());
    ///
    /// // Second call should hit cache (much faster)
    /// let start = std::time::Instant::now();
    /// let cached_result = intelligence.analyze_comprehensive(request).await;
    /// let cache_time = start.elapsed();
    ///
    /// assert!(cached_result.is_ok());
    /// assert!(cache_time.as_millis() < 10); // Cache should be very fast
    /// # });
    /// ```
    pub async fn analyze_comprehensive(
        &self,
        req: AnalysisRequest,
    ) -> anyhow::Result<AnalysisReport> {
        let cache_key = req.cache_key();

        // Check cache first
        if let Some(cached) = self.cache.get(&cache_key).await {
            return Ok(cached);
        }

        let mut report = AnalysisReport {
            duplicates: None,
            dead_code: None,
            complexity_metrics: None,
            dependency_graph: None,
            defect_predictions: None,
            graph_metrics: None,
            timestamp: Utc::now(),
        };

        // First, analyze the project and build the AST DAG
        self.analyze_project(&req.project_path).await?;

        // Handle dependency graph analysis directly
        if req.analysis_types.contains(&AnalysisType::DependencyGraph) {
            // Create project context for dependency graph generation
            if let Ok(project_context) =
                analyze_project(std::path::Path::new(&req.project_path), "rust").await
            {
                // Build dependency graph using DagBuilder with limit
                let dependency_graph =
                    DagBuilder::build_from_project_with_limit(&project_context, 50);

                // Generate Mermaid diagram
                let mermaid_options = MermaidOptions {
                    max_depth: req.max_depth,
                    filter_external: false,
                    group_by_module: true,
                    show_complexity: true,
                };
                let mermaid_generator = MermaidGenerator::new(mermaid_options);
                let mermaid_diagram = mermaid_generator.generate(&dependency_graph);

                // Store results in report
                report.dependency_graph = Some(DependencyGraphReport {
                    nodes: dependency_graph.nodes.len(),
                    edges: dependency_graph.edges.len(),
                    circular_dependencies: Vec::new(), // TRACKED: Implement cycle detection
                    mermaid_diagram,
                });
            }
        }

        // Run other requested analyses in parallel
        let futures = self.build_analysis_futures(&req, &mut report);

        // Wait for all analyses to complete
        futures::future::join_all(futures).await;

        // Cache the results
        self.cache.put(cache_key, report.clone()).await;

        Ok(report)
    }

    /// Analyze project and build AST DAG
    async fn analyze_project(&self, project_path: &str) -> anyhow::Result<()> {
        use crate::models::unified_ast::{AstKind, Language, NodeMetadata, UnifiedAstNode};
        use crate::services::context::analyze_project as analyze_project_context;
        use std::path::Path;

        // Analyze the project using existing context analysis
        let project_context = analyze_project_context(
            Path::new(project_path),
            "rust", // Default to Rust for now, could be auto-detected
        )
        .await?;

        let mut dag = self.dag.write().await;

        // Convert project context to unified AST nodes
        for file in &project_context.files {
            for item in &file.items {
                use crate::services::context::AstItem;

                let node = match item {
                    AstItem::Function { name, line, .. } => {
                        let mut node = UnifiedAstNode::new(
                            AstKind::Function(crate::models::unified_ast::FunctionKind::Regular),
                            Language::Rust,
                        );
                        node.parent = crate::models::unified_ast::INVALID_NODE_KEY;
                        node.first_child = crate::models::unified_ast::INVALID_NODE_KEY;
                        node.next_sibling = crate::models::unified_ast::INVALID_NODE_KEY;
                        node.source_range = (*line as u32)..(*line as u32 + 1);
                        node.semantic_hash = name.as_bytes().iter().fold(0u64, |acc, &b| {
                            acc.wrapping_mul(31).wrapping_add(u64::from(b))
                        });
                        node.structural_hash = 0x12345678;
                        node
                    }
                    AstItem::Struct {
                        name,
                        line,
                        fields_count,
                        ..
                    } => {
                        let mut node = UnifiedAstNode::new(
                            AstKind::Class(crate::models::unified_ast::ClassKind::Struct),
                            Language::Rust,
                        );
                        node.parent = crate::models::unified_ast::INVALID_NODE_KEY;
                        node.first_child = crate::models::unified_ast::INVALID_NODE_KEY;
                        node.next_sibling = crate::models::unified_ast::INVALID_NODE_KEY;
                        node.source_range = (*line as u32)..(*line as u32 + 1);
                        node.semantic_hash = name.as_bytes().iter().fold(0u64, |acc, &b| {
                            acc.wrapping_mul(31).wrapping_add(u64::from(b))
                        });
                        node.structural_hash = 0x87654321;
                        node.name_vector = *fields_count as u64;
                        node.metadata = NodeMetadata {
                            raw: *fields_count as u64,
                        };
                        node
                    }
                    AstItem::Trait { name, line, .. } => {
                        let mut node = UnifiedAstNode::new(
                            AstKind::Class(crate::models::unified_ast::ClassKind::Trait),
                            Language::Rust,
                        );
                        node.parent = crate::models::unified_ast::INVALID_NODE_KEY;
                        node.first_child = crate::models::unified_ast::INVALID_NODE_KEY;
                        node.next_sibling = crate::models::unified_ast::INVALID_NODE_KEY;
                        node.source_range = (*line as u32)..(*line as u32 + 1);
                        node.semantic_hash = name.as_bytes().iter().fold(0u64, |acc, &b| {
                            acc.wrapping_mul(31).wrapping_add(u64::from(b))
                        });
                        node.structural_hash = 0x13579BDF;
                        node
                    }
                    AstItem::Module { name, line, .. } => {
                        let mut node = UnifiedAstNode::new(
                            AstKind::Module(crate::models::unified_ast::ModuleKind::File),
                            Language::Rust,
                        );
                        node.parent = crate::models::unified_ast::INVALID_NODE_KEY;
                        node.first_child = crate::models::unified_ast::INVALID_NODE_KEY;
                        node.next_sibling = crate::models::unified_ast::INVALID_NODE_KEY;
                        node.source_range = (*line as u32)..(*line as u32 + 1);
                        node.semantic_hash = name.as_bytes().iter().fold(0u64, |acc, &b| {
                            acc.wrapping_mul(31).wrapping_add(u64::from(b))
                        });
                        node.structural_hash = 0x24681ACE;
                        node
                    }
                    AstItem::Enum {
                        name,
                        line,
                        variants_count,
                        ..
                    } => {
                        let mut node = UnifiedAstNode::new(
                            AstKind::Class(crate::models::unified_ast::ClassKind::Enum),
                            Language::Rust,
                        );
                        node.parent = crate::models::unified_ast::INVALID_NODE_KEY;
                        node.first_child = crate::models::unified_ast::INVALID_NODE_KEY;
                        node.next_sibling = crate::models::unified_ast::INVALID_NODE_KEY;
                        node.source_range = (*line as u32)..(*line as u32 + 1);
                        node.semantic_hash = name.as_bytes().iter().fold(0u64, |acc, &b| {
                            acc.wrapping_mul(31).wrapping_add(u64::from(b))
                        });
                        node.structural_hash = 0x97531BDF;
                        node.name_vector = *variants_count as u64;
                        node.metadata = NodeMetadata {
                            raw: *variants_count as u64,
                        };
                        node
                    }
                    _ => continue, // Skip other types for now
                };

                dag.nodes.push(node);
            }
        }

        // Increment generation counter
        dag.generation
            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);

        Ok(())
    }

    /// Build analysis futures based on request
    fn build_analysis_futures<'a>(
        &'a self,
        req: &'a AnalysisRequest,
        _report: &'a mut AnalysisReport,
    ) -> Vec<std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 'a>>> {
        let mut futures = Vec::new();

        for analysis_type in &req.analysis_types {
            match analysis_type {
                AnalysisType::DuplicateDetection => {
                    // Duplicate detection is handled in the deep context analysis
                    // No additional processing needed here
                }

                AnalysisType::DeadCodeAnalysis => {
                    let deadcode = self.deadcode.clone();
                    let dag = self.dag.clone();
                    futures.push(Box::pin(async move {
                        let dag_guard = dag.read().await;
                        let mut analyzer = deadcode.write().await;
                        let _dead_report = analyzer.analyze(&dag_guard);
                        // TRACKED: Store in report
                    })
                        as std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>);
                }

                AnalysisType::DependencyGraph => {
                    // Dependency graph is handled synchronously in analyze_comprehensive
                    // No future needed here
                }

                // TRACKED: Implement other analysis types
                _ => {}
            }
        }

        futures
    }
}