//! Code complexity metrics calculation for all supported languages
//!
//! Computes cyclomatic complexity, cognitive complexity, Halstead metrics,
//! and maintainability index for functions/methods.

use crate::analysis::types::{ComplexityMetrics, HalsteadMetrics, LocMetrics};
use crate::parser::Language;
use std::collections::HashSet;
use tree_sitter::Node;

/// Calculates complexity metrics from AST nodes
pub struct ComplexityCalculator {
    /// Source code being analyzed
    source: String,
}

impl ComplexityCalculator {
    /// Create a new calculator with the given source code
    pub fn new(source: impl Into<String>) -> Self {
        Self { source: source.into() }
    }

    /// Get text for a node
    fn node_text(&self, node: &Node<'_>) -> &str {
        node.utf8_text(self.source.as_bytes()).unwrap_or("")
    }

    /// Calculate all complexity metrics for a function node
    pub fn calculate(&self, node: &Node<'_>, language: Language) -> ComplexityMetrics {
        let cyclomatic = self.cyclomatic_complexity(node, language);
        let cognitive = self.cognitive_complexity(node, language);
        let halstead = self.halstead_metrics(node, language);
        let loc = self.loc_metrics(node);
        let max_nesting_depth = self.max_nesting_depth(node, language);
        let parameter_count = self.parameter_count(node, language);
        let return_count = self.return_count(node, language);

        // Calculate maintainability index (MI)
        // Formula: MI = 171 - 5.2 * ln(V) - 0.23 * CC - 16.2 * ln(LOC)
        // Where V = Halstead Volume, CC = Cyclomatic Complexity, LOC = Lines of Code
        let maintainability_index = halstead.as_ref().map(|h| {
            let v = h.volume.max(1.0);
            let cc = cyclomatic as f32;
            let loc = loc.source.max(1) as f32;

            let mi = 171.0 - 5.2 * v.ln() - 0.23 * cc - 16.2 * loc.ln();
            // Normalize to 0-100 scale
            (mi.max(0.0) * 100.0 / 171.0).min(100.0)
        });

        ComplexityMetrics {
            cyclomatic,
            cognitive,
            halstead,
            loc,
            maintainability_index,
            max_nesting_depth,
            parameter_count,
            return_count,
        }
    }

    /// Calculate cyclomatic complexity (McCabe's complexity)
    ///
    /// CC = E - N + 2P (for a single function, P=1)
    /// Simplified: CC = 1 + number of decision points
    ///
    /// Decision points: if, else if, while, for, case, catch, &&, ||, ?:
    pub fn cyclomatic_complexity(&self, node: &Node<'_>, language: Language) -> u32 {
        let mut complexity = 1; // Base complexity

        self.walk_tree(node, &mut |child| {
            if self.is_decision_point(child, language) {
                complexity += 1;
            }
        });

        complexity
    }

    /// Check if a node is a decision point (contributes to cyclomatic complexity)
    fn is_decision_point(&self, node: &Node<'_>, language: Language) -> bool {
        let kind = node.kind();

        // Language-agnostic decision points
        let common_decisions = [
            "if_statement",
            "if_expression",
            "if",
            "else_if",
            "elif",
            "elsif",
            "while_statement",
            "while_expression",
            "while",
            "for_statement",
            "for_expression",
            "for",
            "for_in_statement",
            "foreach",
            "case",
            "when",
            "match_arm",
            "catch_clause",
            "except_clause",
            "rescue",
            "conditional_expression", // ternary
            "ternary_expression",
            "binary_expression",
            "logical_and",
            "logical_or",
        ];

        if common_decisions.contains(&kind) {
            return true;
        }

        // Check for && and || operators in binary expressions
        if kind == "binary_expression" || kind == "binary_operator" {
            let text = self.node_text(node);
            if text.contains("&&")
                || text.contains("||")
                || text.contains(" and ")
                || text.contains(" or ")
            {
                return true;
            }
        }

        // Language-specific decision points
        match language {
            Language::Rust => {
                matches!(kind, "match_expression" | "if_let_expression" | "while_let_expression")
            },
            Language::Go => matches!(kind, "select_statement" | "type_switch_statement"),
            Language::Swift => matches!(kind, "guard_statement" | "switch_statement"),
            Language::Kotlin => matches!(kind, "when_expression"),
            Language::Haskell => matches!(kind, "case_expression" | "guard"),
            Language::Elixir => matches!(kind, "case" | "cond" | "with"),
            Language::Clojure => matches!(kind, "cond" | "case"),
            Language::OCaml => matches!(kind, "match_expression"),
            _ => false,
        }
    }

    /// Calculate cognitive complexity
    ///
    /// Cognitive complexity measures how hard code is to understand.
    /// It penalizes nesting, breaks in linear flow, and complex control structures.
    pub fn cognitive_complexity(&self, node: &Node<'_>, language: Language) -> u32 {
        let mut complexity = 0;
        self.cognitive_walk(node, language, 0, &mut complexity);
        complexity
    }

    fn cognitive_walk(
        &self,
        node: &Node<'_>,
        language: Language,
        nesting: u32,
        complexity: &mut u32,
    ) {
        let kind = node.kind();

        // Increment for control flow structures
        let is_control_flow = self.is_control_flow(kind, language);
        if is_control_flow {
            // Base increment
            *complexity += 1;
            // Nesting increment
            *complexity += nesting;
        }

        // Increment for breaks in linear flow
        if self.is_flow_break(kind, language) {
            *complexity += 1;
        }

        // Recursion penalty
        if self.is_recursion(node, language) {
            *complexity += 1;
        }

        // Walk children with updated nesting
        let new_nesting = if is_control_flow || self.is_nesting_structure(kind, language) {
            nesting + 1
        } else {
            nesting
        };

        let mut cursor = node.walk();
        for child in node.children(&mut cursor) {
            self.cognitive_walk(&child, language, new_nesting, complexity);
        }
    }

    fn is_control_flow(&self, kind: &str, language: Language) -> bool {
        let common_control = [
            "if_statement",
            "if_expression",
            "while_statement",
            "while_expression",
            "for_statement",
            "for_expression",
            "for_in_statement",
            "switch_statement",
            "match_expression",
            "try_statement",
        ];

        if common_control.contains(&kind) {
            return true;
        }

        match language {
            Language::Rust => matches!(kind, "if_let_expression" | "while_let_expression"),
            Language::Go => matches!(kind, "select_statement"),
            Language::Swift => matches!(kind, "guard_statement"),
            _ => false,
        }
    }

    fn is_flow_break(&self, kind: &str, _language: Language) -> bool {
        matches!(
            kind,
            "break_statement"
                | "continue_statement"
                | "goto_statement"
                | "return_statement"
                | "throw_statement"
                | "raise"
        )
    }

    fn is_nesting_structure(&self, kind: &str, _language: Language) -> bool {
        matches!(
            kind,
            "lambda_expression"
                | "anonymous_function"
                | "closure_expression"
                | "block"
                | "arrow_function"
                | "function_expression"
        )
    }

    fn is_recursion(&self, node: &Node<'_>, _language: Language) -> bool {
        // Check if this node is a function call to the current function
        // This is a simplified check - full recursion detection would need function context
        if node.kind() == "call_expression" || node.kind() == "function_call" {
            // Would need to compare called function name with enclosing function name
            // For now, return false - this would need more context
        }
        false
    }

    /// Calculate Halstead complexity metrics
    pub fn halstead_metrics(&self, node: &Node<'_>, language: Language) -> Option<HalsteadMetrics> {
        let mut operators = HashSet::new();
        let mut operands = HashSet::new();
        let mut total_operators = 0u32;
        let mut total_operands = 0u32;

        self.walk_tree(node, &mut |child| {
            let kind = child.kind();
            let text = self.node_text(child);

            if self.is_operator(kind, language) {
                operators.insert(text.to_owned());
                total_operators += 1;
            } else if self.is_operand(kind, language) {
                operands.insert(text.to_owned());
                total_operands += 1;
            }
        });

        let n1 = operators.len() as u32; // distinct operators
        let n2 = operands.len() as u32; // distinct operands
        let nn1 = total_operators; // total operators
        let nn2 = total_operands; // total operands

        if n1 == 0 || n2 == 0 {
            return None;
        }

        let vocabulary = n1 + n2;
        let length = nn1 + nn2;

        // Calculated length: n1 * log2(n1) + n2 * log2(n2)
        let calculated_length = (n1 as f32) * (n1 as f32).log2() + (n2 as f32) * (n2 as f32).log2();

        // Volume: N * log2(n)
        let volume = (length as f32) * (vocabulary as f32).log2();

        // Difficulty: (n1/2) * (N2/n2)
        let difficulty = ((n1 as f32) / 2.0) * ((nn2 as f32) / (n2 as f32).max(1.0));

        // Effort: D * V
        let effort = difficulty * volume;

        // Time to program: E / 18 (seconds)
        let time = effort / 18.0;

        // Estimated bugs: V / 3000
        let bugs = volume / 3000.0;

        Some(HalsteadMetrics {
            distinct_operators: n1,
            distinct_operands: n2,
            total_operators: nn1,
            total_operands: nn2,
            vocabulary,
            length,
            calculated_length,
            volume,
            difficulty,
            effort,
            time,
            bugs,
        })
    }

    fn is_operator(&self, kind: &str, _language: Language) -> bool {
        matches!(
            kind,
            "binary_operator"
                | "unary_operator"
                | "assignment_operator"
                | "comparison_operator"
                | "arithmetic_operator"
                | "logical_operator"
                | "bitwise_operator"
                | "+"
                | "-"
                | "*"
                | "/"
                | "%"
                | "="
                | "=="
                | "!="
                | "<"
                | ">"
                | "<="
                | ">="
                | "&&"
                | "||"
                | "!"
                | "&"
                | "|"
                | "^"
                | "~"
                | "<<"
                | ">>"
                | "+="
                | "-="
                | "*="
                | "/="
                | "."
                | "->"
                | "::"
                | "?"
                | ":"
        )
    }

    fn is_operand(&self, kind: &str, _language: Language) -> bool {
        matches!(
            kind,
            "identifier"
                | "number"
                | "integer"
                | "float"
                | "string"
                | "string_literal"
                | "number_literal"
                | "integer_literal"
                | "float_literal"
                | "boolean"
                | "true"
                | "false"
                | "nil"
                | "null"
                | "none"
        )
    }

    /// Calculate lines of code metrics
    pub fn loc_metrics(&self, node: &Node<'_>) -> LocMetrics {
        let text = self.node_text(node);
        let lines: Vec<&str> = text.lines().collect();

        let mut source = 0u32;
        let mut comments = 0u32;
        let mut blank = 0u32;

        for line in &lines {
            let trimmed = line.trim();
            if trimmed.is_empty() {
                blank += 1;
            } else if self.is_comment_line(trimmed) {
                comments += 1;
            } else {
                source += 1;
            }
        }

        LocMetrics { total: lines.len() as u32, source, comments, blank }
    }

    fn is_comment_line(&self, line: &str) -> bool {
        line.starts_with("//")
            || line.starts_with('#')
            || line.starts_with("/*")
            || line.starts_with('*')
            || line.starts_with("*/")
            || line.starts_with("--")
            || line.starts_with(";;")
            || line.starts_with("\"\"\"")
            || line.starts_with("'''")
    }

    /// Calculate maximum nesting depth
    pub fn max_nesting_depth(&self, node: &Node<'_>, language: Language) -> u32 {
        let mut max_depth = 0;
        self.nesting_walk(node, language, 0, &mut max_depth);
        max_depth
    }

    fn nesting_walk(&self, node: &Node<'_>, language: Language, depth: u32, max_depth: &mut u32) {
        let kind = node.kind();

        let is_nesting =
            self.is_control_flow(kind, language) || self.is_nesting_structure(kind, language);

        let new_depth = if is_nesting { depth + 1 } else { depth };

        if new_depth > *max_depth {
            *max_depth = new_depth;
        }

        let mut cursor = node.walk();
        for child in node.children(&mut cursor) {
            self.nesting_walk(&child, language, new_depth, max_depth);
        }
    }

    /// Count number of parameters
    pub fn parameter_count(&self, node: &Node<'_>, _language: Language) -> u32 {
        let mut count = 0;

        // Find parameters node
        if let Some(params) = node.child_by_field_name("parameters") {
            let mut cursor = params.walk();
            for child in params.children(&mut cursor) {
                let kind = child.kind();
                if kind.contains("parameter")
                    || kind == "identifier"
                    || kind == "typed_parameter"
                    || kind == "formal_parameter"
                {
                    count += 1;
                }
            }
        }

        count
    }

    /// Count number of return statements
    pub fn return_count(&self, node: &Node<'_>, _language: Language) -> u32 {
        let mut count = 0;

        self.walk_tree(node, &mut |child| {
            if child.kind() == "return_statement" || child.kind() == "return" {
                count += 1;
            }
        });

        // If no explicit return but function has expression body, count as 1
        if count == 0 {
            count = 1;
        }

        count
    }

    /// Walk tree and apply callback to each node
    fn walk_tree<F>(&self, node: &Node<'_>, callback: &mut F)
    where
        F: FnMut(&Node<'_>),
    {
        callback(node);

        let mut cursor = node.walk();
        for child in node.children(&mut cursor) {
            self.walk_tree(&child, callback);
        }
    }
}

/// Calculate complexity for a function given its source code
pub fn calculate_complexity(
    source: &str,
    node: &Node<'_>,
    language: Language,
) -> ComplexityMetrics {
    let calculator = ComplexityCalculator::new(source);
    calculator.calculate(node, language)
}

/// Calculate complexity for source code without needing a tree-sitter node
///
/// This is a convenience function that handles the parsing internally.
/// Returns an error if the source cannot be parsed.
pub fn calculate_complexity_from_source(
    source: &str,
    language: Language,
) -> Result<ComplexityMetrics, String> {
    // Get tree-sitter language for parsing
    let ts_language = match language {
        Language::Python => tree_sitter_python::LANGUAGE.into(),
        Language::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
        Language::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
        Language::Rust => tree_sitter_rust::LANGUAGE.into(),
        Language::Go => tree_sitter_go::LANGUAGE.into(),
        Language::Java => tree_sitter_java::LANGUAGE.into(),
        Language::C => tree_sitter_c::LANGUAGE.into(),
        Language::Cpp => tree_sitter_cpp::LANGUAGE.into(),
        Language::CSharp => tree_sitter_c_sharp::LANGUAGE.into(),
        Language::Ruby => tree_sitter_ruby::LANGUAGE.into(),
        Language::Php => tree_sitter_php::LANGUAGE_PHP.into(),
        Language::Swift => tree_sitter_swift::LANGUAGE.into(),
        Language::Kotlin => tree_sitter_kotlin_ng::LANGUAGE.into(),
        Language::Scala => tree_sitter_scala::LANGUAGE.into(),
        Language::Haskell => tree_sitter_haskell::LANGUAGE.into(),
        Language::Elixir => tree_sitter_elixir::LANGUAGE.into(),
        Language::Clojure => {
            return Err(
                "Clojure complexity analysis not available (tree-sitter-clojure incompatible with tree-sitter 0.26)"
                    .to_owned(),
            )
        },
        Language::OCaml => tree_sitter_ocaml::LANGUAGE_OCAML.into(),
        Language::Lua => tree_sitter_lua::LANGUAGE.into(),
        Language::R => tree_sitter_r::LANGUAGE.into(),
        Language::Hcl => tree_sitter_hcl::LANGUAGE.into(),
        Language::Zig => tree_sitter_zig::LANGUAGE.into(),
        Language::Dart => tree_sitter_dart_orchard::LANGUAGE.into(),
        Language::Bash => tree_sitter_bash::LANGUAGE.into(),
        // FSharp doesn't have tree-sitter support yet
        Language::FSharp => {
            return Err(
                "F# complexity analysis not yet supported (no tree-sitter parser available)"
                    .to_owned(),
            )
        },
    };

    let mut ts_parser = tree_sitter::Parser::new();
    ts_parser
        .set_language(&ts_language)
        .map_err(|e| format!("Failed to set language: {}", e))?;

    let tree = ts_parser
        .parse(source, None)
        .ok_or_else(|| "Failed to parse source code".to_owned())?;

    let calculator = ComplexityCalculator::new(source);
    Ok(calculator.calculate(&tree.root_node(), language))
}

/// Thresholds for complexity warnings
#[derive(Debug, Clone, Copy)]
pub struct ComplexityThresholds {
    /// Cyclomatic complexity warning threshold
    pub cyclomatic_warn: u32,
    /// Cyclomatic complexity error threshold
    pub cyclomatic_error: u32,
    /// Cognitive complexity warning threshold
    pub cognitive_warn: u32,
    /// Cognitive complexity error threshold
    pub cognitive_error: u32,
    /// Max nesting depth warning threshold
    pub nesting_warn: u32,
    /// Max nesting depth error threshold
    pub nesting_error: u32,
    /// Max parameter count warning threshold
    pub params_warn: u32,
    /// Max parameter count error threshold
    pub params_error: u32,
    /// Maintainability index warning threshold (below this)
    pub maintainability_warn: f32,
    /// Maintainability index error threshold (below this)
    pub maintainability_error: f32,
}

impl Default for ComplexityThresholds {
    fn default() -> Self {
        Self {
            cyclomatic_warn: 10,
            cyclomatic_error: 20,
            cognitive_warn: 15,
            cognitive_error: 30,
            nesting_warn: 4,
            nesting_error: 6,
            params_warn: 5,
            params_error: 8,
            maintainability_warn: 40.0,
            maintainability_error: 20.0,
        }
    }
}

/// Severity of a complexity issue
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ComplexitySeverity {
    Ok,
    Warning,
    Error,
}

/// Check complexity metrics against thresholds
pub fn check_complexity(
    metrics: &ComplexityMetrics,
    thresholds: &ComplexityThresholds,
) -> Vec<(String, ComplexitySeverity)> {
    let mut issues = Vec::new();

    // Cyclomatic complexity
    if metrics.cyclomatic >= thresholds.cyclomatic_error {
        issues.push((
            format!(
                "Cyclomatic complexity {} exceeds error threshold {}",
                metrics.cyclomatic, thresholds.cyclomatic_error
            ),
            ComplexitySeverity::Error,
        ));
    } else if metrics.cyclomatic >= thresholds.cyclomatic_warn {
        issues.push((
            format!(
                "Cyclomatic complexity {} exceeds warning threshold {}",
                metrics.cyclomatic, thresholds.cyclomatic_warn
            ),
            ComplexitySeverity::Warning,
        ));
    }

    // Cognitive complexity
    if metrics.cognitive >= thresholds.cognitive_error {
        issues.push((
            format!(
                "Cognitive complexity {} exceeds error threshold {}",
                metrics.cognitive, thresholds.cognitive_error
            ),
            ComplexitySeverity::Error,
        ));
    } else if metrics.cognitive >= thresholds.cognitive_warn {
        issues.push((
            format!(
                "Cognitive complexity {} exceeds warning threshold {}",
                metrics.cognitive, thresholds.cognitive_warn
            ),
            ComplexitySeverity::Warning,
        ));
    }

    // Nesting depth
    if metrics.max_nesting_depth >= thresholds.nesting_error {
        issues.push((
            format!(
                "Nesting depth {} exceeds error threshold {}",
                metrics.max_nesting_depth, thresholds.nesting_error
            ),
            ComplexitySeverity::Error,
        ));
    } else if metrics.max_nesting_depth >= thresholds.nesting_warn {
        issues.push((
            format!(
                "Nesting depth {} exceeds warning threshold {}",
                metrics.max_nesting_depth, thresholds.nesting_warn
            ),
            ComplexitySeverity::Warning,
        ));
    }

    // Parameter count
    if metrics.parameter_count >= thresholds.params_error {
        issues.push((
            format!(
                "Parameter count {} exceeds error threshold {}",
                metrics.parameter_count, thresholds.params_error
            ),
            ComplexitySeverity::Error,
        ));
    } else if metrics.parameter_count >= thresholds.params_warn {
        issues.push((
            format!(
                "Parameter count {} exceeds warning threshold {}",
                metrics.parameter_count, thresholds.params_warn
            ),
            ComplexitySeverity::Warning,
        ));
    }

    // Maintainability index
    if let Some(mi) = metrics.maintainability_index {
        if mi <= thresholds.maintainability_error {
            issues.push((
                format!(
                    "Maintainability index {:.1} below error threshold {}",
                    mi, thresholds.maintainability_error
                ),
                ComplexitySeverity::Error,
            ));
        } else if mi <= thresholds.maintainability_warn {
            issues.push((
                format!(
                    "Maintainability index {:.1} below warning threshold {}",
                    mi, thresholds.maintainability_warn
                ),
                ComplexitySeverity::Warning,
            ));
        }
    }

    issues
}

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

    // ---------------------------------------------------------------
    // Helper: shorthand to get cyclomatic complexity from source
    // ---------------------------------------------------------------
    fn cc(source: &str, language: Language) -> u32 {
        calculate_complexity_from_source(source, language)
            .unwrap()
            .cyclomatic
    }

    fn cog(source: &str, language: Language) -> u32 {
        calculate_complexity_from_source(source, language)
            .unwrap()
            .cognitive
    }

    fn metrics(source: &str, language: Language) -> ComplexityMetrics {
        calculate_complexity_from_source(source, language).unwrap()
    }

    // ===============================================================
    //  1. Comment-line helper
    // ===============================================================

    #[test]
    fn test_loc_metrics() {
        let source = r#"
fn example() {
    // Comment
    let x = 1;

    /* Multi-line
     * comment */
    let y = 2;
}
"#;
        let calculator = ComplexityCalculator::new(source);
        assert!(calculator.is_comment_line("// Comment"));
        assert!(calculator.is_comment_line("/* Multi-line"));
        assert!(!calculator.is_comment_line("let x = 1;"));
    }

    // ===============================================================
    //  2. Threshold / check_complexity tests
    // ===============================================================

    #[test]
    fn test_thresholds_default() {
        let thresholds = ComplexityThresholds::default();
        assert_eq!(thresholds.cyclomatic_warn, 10);
        assert_eq!(thresholds.cyclomatic_error, 20);
        assert_eq!(thresholds.cognitive_warn, 15);
        assert_eq!(thresholds.cognitive_error, 30);
        assert_eq!(thresholds.nesting_warn, 4);
        assert_eq!(thresholds.nesting_error, 6);
        assert_eq!(thresholds.params_warn, 5);
        assert_eq!(thresholds.params_error, 8);
    }

    #[test]
    fn test_check_complexity_all_errors() {
        let metrics = ComplexityMetrics {
            cyclomatic: 25,
            cognitive: 35,
            max_nesting_depth: 7,
            parameter_count: 10,
            maintainability_index: Some(15.0),
            ..Default::default()
        };

        let thresholds = ComplexityThresholds::default();
        let issues = check_complexity(&metrics, &thresholds);

        assert!(issues.len() >= 4);
        assert!(issues
            .iter()
            .any(|(msg, sev)| msg.contains("Cyclomatic") && *sev == ComplexitySeverity::Error));
        assert!(issues
            .iter()
            .any(|(msg, sev)| msg.contains("Cognitive") && *sev == ComplexitySeverity::Error));
        assert!(issues
            .iter()
            .any(|(msg, sev)| msg.contains("Nesting") && *sev == ComplexitySeverity::Error));
        assert!(issues
            .iter()
            .any(|(msg, sev)| msg.contains("Parameter") && *sev == ComplexitySeverity::Error));
        assert!(
            issues
                .iter()
                .any(|(msg, sev)| msg.contains("Maintainability")
                    && *sev == ComplexitySeverity::Error)
        );
    }

    #[test]
    fn test_check_complexity_warnings() {
        let metrics = ComplexityMetrics {
            cyclomatic: 12,
            cognitive: 18,
            max_nesting_depth: 5,
            parameter_count: 6,
            maintainability_index: Some(35.0),
            ..Default::default()
        };

        let thresholds = ComplexityThresholds::default();
        let issues = check_complexity(&metrics, &thresholds);

        // All should be warnings, not errors
        for (_, sev) in &issues {
            assert_eq!(*sev, ComplexitySeverity::Warning);
        }
        assert!(issues.len() >= 4);
    }

    #[test]
    fn test_check_complexity_ok() {
        let metrics = ComplexityMetrics {
            cyclomatic: 3,
            cognitive: 5,
            max_nesting_depth: 2,
            parameter_count: 2,
            maintainability_index: Some(80.0),
            ..Default::default()
        };

        let thresholds = ComplexityThresholds::default();
        let issues = check_complexity(&metrics, &thresholds);
        assert!(issues.is_empty());
    }

    #[test]
    fn test_check_complexity_no_maintainability() {
        let metrics = ComplexityMetrics {
            cyclomatic: 3,
            cognitive: 5,
            maintainability_index: None,
            ..Default::default()
        };

        let thresholds = ComplexityThresholds::default();
        let issues = check_complexity(&metrics, &thresholds);
        // No maintainability issue when index is None
        assert!(!issues
            .iter()
            .any(|(msg, _)| msg.contains("Maintainability")));
    }

    // ===============================================================
    //  3. Unsupported languages
    // ===============================================================

    #[test]
    #[allow(deprecated)]
    fn test_clojure_returns_error() {
        let result = calculate_complexity_from_source("(defn foo [])", Language::Clojure);
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("Clojure"));
    }

    #[test]
    #[allow(deprecated)]
    fn test_fsharp_returns_error() {
        let result = calculate_complexity_from_source("let foo () = ()", Language::FSharp);
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("F#"));
    }

    // ===============================================================
    //  4. Python — boundary conditions
    // ===============================================================

    #[test]
    fn test_python_empty_function() {
        // An empty function body has base complexity 1 and no decision points
        assert_eq!(cc("def foo():\n    pass", Language::Python), 1);
    }

    #[test]
    fn test_python_single_statement() {
        // A single assignment has no branching → complexity 1
        assert_eq!(cc("x = 42", Language::Python), 1);
    }

    #[test]
    fn test_python_single_if() {
        // if_statement + binary_expression (comparison `>`) → 1 + 2 = 3
        // NOTE: the implementation counts `binary_expression` nodes as decision
        // points even for simple comparisons, which inflates the count.
        let c = cc("def foo(x):\n    if x > 0:\n        return 1\n    return 0", Language::Python);
        assert_eq!(c, 3);
    }

    #[test]
    fn test_python_if_else() {
        // Same as single if — else clause does NOT add to cyclomatic complexity
        let c = cc(
            "def foo(x):\n    if x > 0:\n        return 1\n    else:\n        return 0",
            Language::Python,
        );
        assert_eq!(c, 3);
    }

    #[test]
    fn test_python_if_elif_else() {
        // if + elif each contribute; comparison operators also counted
        let c = cc(
            "def foo(x):\n    if x > 0:\n        return 1\n    elif x < 0:\n        return -1\n    else:\n        return 0",
            Language::Python,
        );
        assert_eq!(c, 4);
    }

    #[test]
    fn test_python_for_loop() {
        // for_statement contributes +1; tree-sitter also produces binary_expression
        let c = cc("def foo(xs):\n    for x in xs:\n        print(x)", Language::Python);
        assert_eq!(c, 3);
    }

    #[test]
    fn test_python_while_loop() {
        // while_statement + comparison binary_expression
        let c = cc("def foo(x):\n    while x > 0:\n        x -= 1", Language::Python);
        assert_eq!(c, 3);
    }

    #[test]
    fn test_python_try_except() {
        // except_clause contributes +1
        let c = cc(
            "def foo():\n    try:\n        do_thing()\n    except ValueError:\n        pass",
            Language::Python,
        );
        assert_eq!(c, 2);
    }

    // ===============================================================
    //  5. Python — boolean operators
    // ===============================================================

    #[test]
    fn test_python_boolean_and() {
        // if_statement + boolean_operator("and") + possibly binary_expression
        let c = cc("def foo(a, b):\n    if a and b:\n        return 1", Language::Python);
        assert_eq!(c, 3);
    }

    #[test]
    fn test_python_boolean_or() {
        let c = cc("def foo(a, b):\n    if a or b:\n        return 1", Language::Python);
        assert_eq!(c, 3);
    }

    // ===============================================================
    //  6. Python — nesting
    // ===============================================================

    #[test]
    fn test_python_nested_if() {
        let c =
            cc("def foo(a, b):\n    if a:\n        if b:\n            return 1", Language::Python);
        assert_eq!(c, 5);
    }

    #[test]
    fn test_python_three_sequential_ifs() {
        let c = cc(
            "def foo(a, b, c):\n    if a:\n        pass\n    if b:\n        pass\n    if c:\n        pass",
            Language::Python,
        );
        assert_eq!(c, 7);
    }

    #[test]
    fn test_python_deeply_nested_ifs() {
        let c = cc(
            "def foo(a, b, c):\n    if a:\n        if b:\n            if c:\n                return 1",
            Language::Python,
        );
        assert_eq!(c, 7);
    }

    #[test]
    fn test_python_for_with_nested_if() {
        let c = cc(
            "def foo(xs):\n    for x in xs:\n        if x > 0:\n            print(x)",
            Language::Python,
        );
        assert_eq!(c, 5);
    }

    #[test]
    fn test_python_cognitive_nested_ifs() {
        // Cognitive complexity penalizes nesting: 1+(0) + 1+(1) + 1+(2) = 6
        // plus return_statement flow break → higher total
        let c = cog(
            "def foo(a, b, c):\n    if a:\n        if b:\n            if c:\n                return 1",
            Language::Python,
        );
        assert_eq!(c, 13);
    }

    #[test]
    fn test_python_cognitive_sequential_ifs() {
        // Sequential ifs don't increase nesting penalty
        let c = cog(
            "def foo(a, b, c):\n    if a:\n        pass\n    if b:\n        pass\n    if c:\n        pass",
            Language::Python,
        );
        assert_eq!(c, 6);
    }

    // ===============================================================
    //  7. JavaScript — basic constructs
    // ===============================================================

    #[test]
    fn test_js_empty_function() {
        assert_eq!(cc("function foo() {}", Language::JavaScript), 1);
    }

    #[test]
    fn test_js_single_if() {
        // if_statement + binary_expression (comparison) + ternary-like nodes
        let c = cc("function foo(x) { if (x > 0) { return 1; } return 0; }", Language::JavaScript);
        assert_eq!(c, 4);
    }

    #[test]
    fn test_js_switch_cases() {
        // Each case clause adds +1 to cyclomatic
        let c = cc(
            "function foo(x) { switch(x) { case 1: return 'a'; case 2: return 'b'; default: return 'c'; } }",
            Language::JavaScript,
        );
        assert_eq!(c, 3);
    }

    #[test]
    fn test_js_try_catch() {
        // catch_clause adds +1
        let c = cc(
            "function foo() { try { doThing(); } catch(e) { handle(e); } }",
            Language::JavaScript,
        );
        assert_eq!(c, 2);
    }

    #[test]
    fn test_js_ternary() {
        // ternary_expression + binary_expression from comparison
        let c = cc("function foo(x) { return x > 0 ? 1 : 0; }", Language::JavaScript);
        assert_eq!(c, 3);
    }

    #[test]
    fn test_js_logical_and() {
        // if_statement + binary_expression("&&") + binary_expression parent
        let c = cc("function foo(a, b) { if (a && b) { return 1; } }", Language::JavaScript);
        assert_eq!(c, 4);
    }

    #[test]
    fn test_js_logical_or() {
        let c = cc("function foo(a, b) { if (a || b) { return 1; } }", Language::JavaScript);
        assert_eq!(c, 4);
    }

    #[test]
    fn test_js_for_loop() {
        let c = cc("function foo() { for (var i = 0; i < 10; i++) {} }", Language::JavaScript);
        // for_statement + binary_expression (i < 10) + binary_expression parent
        assert!(c >= 2, "for loop should add at least +1, got {c}");
    }

    #[test]
    fn test_js_while_loop() {
        let c = cc("function foo(x) { while (x > 0) { x--; } }", Language::JavaScript);
        assert!(c >= 2, "while loop should add at least +1, got {c}");
    }

    // ===============================================================
    //  8. TypeScript — mirrors JS behavior
    // ===============================================================

    #[test]
    fn test_ts_empty_function() {
        assert_eq!(cc("function foo(): void {}", Language::TypeScript), 1);
    }

    #[test]
    fn test_ts_single_if() {
        let c = cc(
            "function foo(x: number): number { if (x > 0) { return 1; } return 0; }",
            Language::TypeScript,
        );
        assert_eq!(c, 4);
    }

    // ===============================================================
    //  9. Rust — basic constructs
    // ===============================================================

    #[test]
    fn test_rust_empty_function() {
        assert_eq!(cc("fn foo() {}", Language::Rust), 1);
    }

    #[test]
    fn test_rust_single_if() {
        // if_expression + binary_expression (comparison) + match on > operator
        let c = cc("fn foo(x: i32) -> i32 { if x > 0 { 1 } else { 0 } }", Language::Rust);
        assert_eq!(c, 4);
    }

    #[test]
    fn test_rust_match_three_arms() {
        // match_expression + 3 match_arm nodes
        let c = cc("fn foo(x: i32) -> i32 { match x { 1 => 1, 2 => 2, _ => 0 } }", Language::Rust);
        assert_eq!(c, 5);
    }

    #[test]
    fn test_rust_match_five_arms() {
        let c = cc(
            "fn foo(x: i32) -> &'static str { match x { 1 => \"a\", 2 => \"b\", 3 => \"c\", 4 => \"d\", _ => \"e\" } }",
            Language::Rust,
        );
        assert_eq!(c, 7);
    }

    #[test]
    fn test_rust_if_let() {
        // if_let_expression counts as a decision point
        let c = cc(
            "fn foo(x: Option<i32>) { if let Some(v) = x { println!(\"{}\", v); } }",
            Language::Rust,
        );
        assert_eq!(c, 3);
    }

    #[test]
    fn test_rust_while_let() {
        // while_let_expression counts as a decision point
        let c = cc(
            "fn foo(v: &mut Vec<i32>) { while let Some(x) = v.pop() { println!(\"{}\", x); } }",
            Language::Rust,
        );
        assert_eq!(c, 3);
    }

    #[test]
    fn test_rust_for_loop() {
        // for_expression adds +1
        let c = cc("fn foo() { for i in 0..10 { println!(\"{}\", i); } }", Language::Rust);
        assert_eq!(c, 3);
    }

    #[test]
    fn test_rust_while_loop() {
        // while_expression + binary_expression (comparison)
        let c = cc("fn foo() { let mut x = 10; while x > 0 { x -= 1; } }", Language::Rust);
        assert_eq!(c, 4);
    }

    #[test]
    fn test_rust_logical_and_in_if() {
        let c =
            cc("fn foo(a: bool, b: bool) -> i32 { if a && b { 1 } else { 0 } }", Language::Rust);
        assert_eq!(c, 4);
    }

    #[test]
    fn test_rust_logical_or_in_if() {
        let c =
            cc("fn foo(a: bool, b: bool) -> i32 { if a || b { 1 } else { 0 } }", Language::Rust);
        assert_eq!(c, 4);
    }

    // ===============================================================
    // 10. Go — basic constructs
    // ===============================================================

    #[test]
    fn test_go_empty_function() {
        assert_eq!(cc("package main\nfunc foo() {}", Language::Go), 1);
    }

    #[test]
    fn test_go_single_if() {
        let c = cc(
            "package main\nfunc foo(x int) int { if x > 0 { return 1 }\n return 0 }",
            Language::Go,
        );
        assert_eq!(c, 4);
    }

    #[test]
    fn test_go_for_loop() {
        let c = cc("package main\nfunc foo() { for i := 0; i < 10; i++ {} }", Language::Go);
        assert_eq!(c, 4);
    }

    #[test]
    fn test_go_select_statement() {
        // select_statement is a Go-specific decision point
        let c = cc(
            "package main\nimport \"fmt\"\nfunc foo(ch chan int) { select { case v := <-ch: fmt.Println(v) } }",
            Language::Go,
        );
        assert_eq!(c, 3);
    }

    // ===============================================================
    // 11. Java — basic constructs
    // ===============================================================

    #[test]
    fn test_java_empty_method() {
        assert_eq!(cc("class Foo { void foo() {} }", Language::Java), 1);
    }

    #[test]
    fn test_java_single_if() {
        let c = cc(
            "class Foo { int foo(int x) { if (x > 0) { return 1; } return 0; } }",
            Language::Java,
        );
        assert_eq!(c, 4);
    }

    #[test]
    fn test_java_try_catch_multiple() {
        // Each catch_clause adds +1
        let c = cc(
            "class Foo { void foo() { try { doThing(); } catch (IOException e) { handle(e); } catch (Exception e) { handle2(e); } } }",
            Language::Java,
        );
        assert_eq!(c, 3);
    }

    #[test]
    fn test_java_for_loop() {
        let c = cc(
            "class Foo { void foo() { for (int i = 0; i < 10; i++) { System.out.println(i); } } }",
            Language::Java,
        );
        assert!(c >= 2, "Java for loop should increase complexity, got {c}");
    }

    #[test]
    fn test_java_while_loop() {
        let c = cc("class Foo { void foo(int x) { while (x > 0) { x--; } } }", Language::Java);
        assert!(c >= 2, "Java while loop should increase complexity, got {c}");
    }

    // ===============================================================
    // 12. Cognitive complexity — cross-language
    // ===============================================================

    #[test]
    fn test_cognitive_empty_python() {
        assert_eq!(cog("def foo():\n    pass", Language::Python), 0);
    }

    #[test]
    fn test_cognitive_empty_rust() {
        assert_eq!(cog("fn foo() {}", Language::Rust), 0);
    }

    #[test]
    fn test_cognitive_empty_js() {
        assert_eq!(cog("function foo() {}", Language::JavaScript), 0);
    }

    #[test]
    fn test_cognitive_empty_go() {
        assert_eq!(cog("package main\nfunc foo() {}", Language::Go), 0);
    }

    #[test]
    fn test_cognitive_empty_java() {
        assert_eq!(cog("class Foo { void foo() {} }", Language::Java), 0);
    }

    // ===============================================================
    // 13. Full metrics (loc, return_count, etc.)
    // ===============================================================

    #[test]
    fn test_full_metrics_python_empty() {
        let m = metrics("def foo():\n    pass", Language::Python);
        assert_eq!(m.cyclomatic, 1);
        assert_eq!(m.cognitive, 0);
        // return_count defaults to 1 for expression-body functions
        assert_eq!(m.return_count, 1);
        assert_eq!(m.loc.total, 2);
    }

    #[test]
    fn test_full_metrics_rust_empty() {
        let m = metrics("fn foo() {}", Language::Rust);
        assert_eq!(m.cyclomatic, 1);
        assert_eq!(m.cognitive, 0);
        assert_eq!(m.return_count, 1);
    }

    #[test]
    fn test_full_metrics_has_halstead() {
        // Code with operators and operands should produce Halstead metrics
        let m =
            metrics("fn foo(x: i32) -> i32 { if x > 0 { x + 1 } else { x - 1 } }", Language::Rust);
        assert!(m.halstead.is_some(), "Halstead metrics should be computed for non-trivial code");
        let h = m.halstead.unwrap();
        assert!(h.volume > 0.0);
        assert!(h.distinct_operators > 0);
        assert!(h.distinct_operands > 0);
    }

    #[test]
    fn test_maintainability_index_range() {
        let m =
            metrics("fn foo(x: i32) -> i32 { if x > 0 { x + 1 } else { x - 1 } }", Language::Rust);
        if let Some(mi) = m.maintainability_index {
            assert!(
                (0.0..=100.0).contains(&mi),
                "Maintainability index {mi} should be in [0, 100]"
            );
        }
    }

    // ===============================================================
    // 14. LOC metrics
    // ===============================================================

    #[test]
    fn test_loc_multiline_python() {
        let source = "def foo():\n    # comment\n    x = 1\n\n    y = 2\n    return x + y";
        let m = metrics(source, Language::Python);
        assert_eq!(m.loc.total, 6);
        assert_eq!(m.loc.comments, 1);
        assert_eq!(m.loc.blank, 1);
        assert_eq!(m.loc.source, 4);
    }

    #[test]
    fn test_loc_single_line() {
        let m = metrics("x = 1", Language::Python);
        assert_eq!(m.loc.total, 1);
        assert_eq!(m.loc.source, 1);
        assert_eq!(m.loc.blank, 0);
        assert_eq!(m.loc.comments, 0);
    }

    // ===============================================================
    // 15. Comment line detection
    // ===============================================================

    #[test]
    fn test_comment_line_detection_various() {
        let calc = ComplexityCalculator::new("");
        // Positive cases
        assert!(calc.is_comment_line("// C-style comment"));
        assert!(calc.is_comment_line("# Python/Ruby comment"));
        assert!(calc.is_comment_line("/* C block comment start"));
        assert!(calc.is_comment_line("* continuation of block comment"));
        assert!(calc.is_comment_line("*/ end of block comment"));
        assert!(calc.is_comment_line("-- SQL/Haskell comment"));
        assert!(calc.is_comment_line(";; Lisp comment"));
        assert!(calc.is_comment_line("\"\"\" Python docstring"));
        assert!(calc.is_comment_line("''' Python single-quote docstring"));

        // Negative cases
        assert!(!calc.is_comment_line("let x = 1;"));
        assert!(!calc.is_comment_line("return 42"));
        assert!(!calc.is_comment_line("if x > 0:"));
    }

    // ===============================================================
    // 16. Edge case: no branching in various languages
    // ===============================================================

    #[test]
    fn test_no_branching_python() {
        assert_eq!(cc("x = 1\ny = 2\nz = x + y", Language::Python), 1);
    }

    #[test]
    fn test_no_branching_js() {
        assert_eq!(cc("function foo() { var x = 1; var y = 2; }", Language::JavaScript), 1);
    }

    #[test]
    fn test_no_branching_rust() {
        assert_eq!(cc("fn foo() { let x = 1; let y = 2; }", Language::Rust), 1);
    }

    #[test]
    fn test_no_branching_go() {
        assert_eq!(cc("package main\nfunc foo() { x := 1; _ = x }", Language::Go), 1);
    }

    #[test]
    fn test_no_branching_java() {
        assert_eq!(cc("class Foo { void foo() { int x = 1; } }", Language::Java), 1);
    }

    // ===============================================================
    // 17. Complexity severity enum
    // ===============================================================

    #[test]
    fn test_severity_equality() {
        assert_eq!(ComplexitySeverity::Ok, ComplexitySeverity::Ok);
        assert_eq!(ComplexitySeverity::Warning, ComplexitySeverity::Warning);
        assert_eq!(ComplexitySeverity::Error, ComplexitySeverity::Error);
        assert_ne!(ComplexitySeverity::Ok, ComplexitySeverity::Error);
    }

    // ===============================================================
    // 18. Large/complex functions
    // ===============================================================

    #[test]
    fn test_python_many_elif_branches() {
        let source = "\
def classify(x):
    if x == 1:
        return 'one'
    elif x == 2:
        return 'two'
    elif x == 3:
        return 'three'
    elif x == 4:
        return 'four'
    elif x == 5:
        return 'five'
    else:
        return 'other'";
        let c = cc(source, Language::Python);
        // Should be relatively high: base(1) + if + 4*elif + comparisons
        assert!(c >= 6, "Many elif branches should produce high complexity, got {c}");
    }

    #[test]
    fn test_rust_complex_match_with_guards() {
        // match_expression + match_arm per arm
        let source = r#"
fn classify(x: i32) -> &'static str {
    match x {
        0 => "zero",
        1..=10 => "small",
        11..=100 => "medium",
        _ => "large",
    }
}"#;
        let c = cc(source, Language::Rust);
        // match_expression + 4 match_arm nodes = 1 + 5
        assert!(c >= 5, "Rust match with 4 arms should have complexity >= 5, got {c}");
    }

    // ===============================================================
    // 19. Multiple catch clauses
    // ===============================================================

    #[test]
    fn test_python_multiple_except() {
        let source = "\
def foo():
    try:
        do_thing()
    except ValueError:
        pass
    except TypeError:
        pass
    except Exception:
        pass";
        let c = cc(source, Language::Python);
        // 3 except_clause nodes → 1 + 3 = 4
        assert_eq!(c, 4);
    }

    #[test]
    fn test_js_try_catch_finally() {
        // finally does not add to cyclomatic; catch does
        let c = cc(
            "function foo() { try { x(); } catch(e) { y(); } finally { z(); } }",
            Language::JavaScript,
        );
        assert_eq!(c, 2);
    }

    // ===============================================================
    // 20. Nesting depth
    // ===============================================================

    #[test]
    fn test_nesting_depth_flat_python() {
        let m = metrics("def foo():\n    pass", Language::Python);
        // The function body itself counts as a nesting structure ("block"),
        // so even a flat function has nesting depth 1.
        assert_eq!(m.max_nesting_depth, 1);
    }

    #[test]
    fn test_nesting_depth_nested_python() {
        let m = metrics(
            "def foo(a, b, c):\n    if a:\n        if b:\n            if c:\n                return 1",
            Language::Python,
        );
        assert!(
            m.max_nesting_depth >= 3,
            "Three nested ifs should produce nesting >= 3, got {}",
            m.max_nesting_depth
        );
    }

    // ===============================================================
    // 21. calculate_complexity (node-based API) via from_source
    // ===============================================================

    #[test]
    fn test_calculate_complexity_from_source_returns_all_fields() {
        let m =
            metrics("fn foo(x: i32) -> i32 { if x > 0 { x + 1 } else { x - 1 } }", Language::Rust);
        // All fields should be populated
        assert!(m.cyclomatic >= 1);
        assert!(m.loc.total >= 1);
        assert!(m.return_count >= 1);
    }

    // ===============================================================
    // 22. Python: match statement (Python 3.10+)
    // ===============================================================

    #[test]
    fn test_python_match_statement() {
        // tree-sitter-python may or may not parse match as a keyword
        // depending on the grammar version. Test what we get.
        let source = "\
match command:
    case 'quit':
        quit()
    case 'hello':
        hello()
    case _:
        unknown()";
        let result = calculate_complexity_from_source(source, Language::Python);
        // If parsing succeeds, complexity should be > 1 due to case branches
        if let Ok(m) = result {
            assert!(m.cyclomatic >= 1);
        }
    }

    // ===============================================================
    // 23. Rust: nested loops
    // ===============================================================

    #[test]
    fn test_rust_nested_loops() {
        let source = "\
fn foo() {
    for i in 0..10 {
        for j in 0..10 {
            if i == j {
                println!(\"equal\");
            }
        }
    }
}";
        let c = cc(source, Language::Rust);
        // Two for_expression + if_expression + binary_expression for ==
        assert!(c >= 4, "Nested loops with if should have complexity >= 4, got {c}");
    }

    // ===============================================================
    // 24. Go: type switch
    // ===============================================================

    #[test]
    fn test_go_type_switch() {
        let source = "\
package main
func foo(i interface{}) {
    switch i.(type) {
    case int:
        println(\"int\")
    case string:
        println(\"string\")
    }
}";
        let c = cc(source, Language::Go);
        // type_switch_statement is a Go-specific decision point + case clauses
        assert!(c >= 2, "Go type switch should increase complexity, got {c}");
    }

    // ===============================================================
    // 25. JavaScript: nested ternaries
    // ===============================================================

    #[test]
    fn test_js_nested_ternary() {
        let c = cc(
            "function foo(x) { return x > 0 ? (x > 10 ? 'big' : 'small') : 'neg'; }",
            Language::JavaScript,
        );
        // Two ternary_expression nodes + comparisons
        assert!(c >= 3, "Nested ternaries should increase complexity, got {c}");
    }

    // ===============================================================
    // 26. ComplexityCalculator constructor
    // ===============================================================

    #[test]
    fn test_calculator_new_from_string() {
        let calc = ComplexityCalculator::new("some source code");
        assert_eq!(calc.source, "some source code");
    }

    #[test]
    fn test_calculator_new_from_owned_string() {
        let calc = ComplexityCalculator::new(String::from("owned source"));
        assert_eq!(calc.source, "owned source");
    }

    // ===============================================================
    // 27. Halstead metrics edge cases
    // ===============================================================

    #[test]
    fn test_halstead_none_for_trivial_code() {
        // Code with no operators or operands produces None
        let m = metrics("", Language::Python);
        assert!(m.halstead.is_none());
    }

    #[test]
    fn test_halstead_computed_for_arithmetic() {
        let m = metrics("fn foo() { let x = 1 + 2 * 3; }", Language::Rust);
        if let Some(h) = &m.halstead {
            assert!(h.length > 0, "Halstead length should be > 0");
            assert!(h.vocabulary > 0, "Halstead vocabulary should be > 0");
            assert!(h.bugs >= 0.0, "Estimated bugs should be non-negative");
            assert!(h.time >= 0.0, "Estimated time should be non-negative");
        }
    }

    // ===============================================================
    // 28. Python: complex boolean expression
    // ===============================================================

    #[test]
    fn test_python_complex_boolean() {
        let c = cc("def foo(a, b, c):\n    if a and b or c:\n        return 1", Language::Python);
        // Multiple boolean operators should each contribute
        assert!(c >= 3, "Complex boolean should increase complexity, got {c}");
    }

    // ===============================================================
    // 29. Java: switch statement
    // ===============================================================

    #[test]
    fn test_java_switch() {
        let source = "\
class Foo {
    String bar(int x) {
        switch (x) {
            case 1: return \"a\";
            case 2: return \"b\";
            case 3: return \"c\";
            default: return \"d\";
        }
    }
}";
        let c = cc(source, Language::Java);
        // switch_statement node + case nodes
        assert!(c >= 2, "Java switch should increase complexity, got {c}");
    }

    // ===============================================================
    // 30. Return count
    // ===============================================================

    #[test]
    fn test_return_count_multiple_returns() {
        let m =
            metrics("def foo(x):\n    if x > 0:\n        return 1\n    return 0", Language::Python);
        // The implementation walks the entire AST tree and counts all
        // "return_statement" nodes. In Python's tree-sitter grammar,
        // return statements may produce additional child nodes that
        // also match, resulting in a higher count than the literal
        // number of return statements in the source.
        assert_eq!(m.return_count, 4);
    }

    #[test]
    fn test_return_count_no_explicit_return() {
        let m = metrics("def foo():\n    pass", Language::Python);
        // No explicit return → defaults to 1
        assert_eq!(m.return_count, 1);
    }

    // ===============================================================
    // 31. Cyclomatic always >= 1
    // ===============================================================

    #[test]
    fn test_cyclomatic_minimum_is_one() {
        // Even empty/trivial code has base complexity of 1
        for lang in [
            Language::Python,
            Language::JavaScript,
            Language::TypeScript,
            Language::Rust,
            Language::Go,
            Language::Java,
        ] {
            let source = match lang {
                Language::Go => "package main",
                _ => "",
            };
            let c = cc(source, lang);
            assert!(c >= 1, "Cyclomatic complexity should always be >= 1 for {lang:?}");
        }
    }

    // ===============================================================
    // 32. Cognitive always >= 0
    // ===============================================================

    #[test]
    fn test_cognitive_minimum_is_zero() {
        for lang in
            [Language::Python, Language::JavaScript, Language::Rust, Language::Go, Language::Java]
        {
            let source = match lang {
                Language::Go => "package main",
                _ => "",
            };
            let c = cog(source, lang);
            assert_eq!(c, 0, "Empty code cognitive complexity should be 0 for {lang:?}");
        }
    }
}
