//! Import graph extractor for Rust.
//!
//! Tracks `use` statements to detect architecture boundaries and dependency patterns.
//! Enables learning loop conventions like "core never imports tokio" or
//! "all message types import serde".

use regex::Regex;
use stemedb_core::types::ObjectValue;

use super::Extractor;
use crate::types::{ExtractedClaim, Language};

/// Extractor for Rust import patterns.
///
/// Detects `use` statements to track which modules import which crates.
/// This enables the learning loop to establish and enforce architecture boundaries.
pub struct ImportGraphExtractor {
    /// Matches: use crate_name::...;
    use_statement: Regex,
    /// Matches: use crate::{A, B, C};
    use_group: Regex,
}

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

impl ImportGraphExtractor {
    /// Create a new import graph extractor.
    ///
    /// # Panics
    /// Panics if any regex pattern is invalid (programmer error).
    #[allow(clippy::expect_used)]
    pub fn new() -> Self {
        Self {
            // Matches: use tokio::runtime::Runtime;
            // Captures the root crate name
            use_statement: Regex::new(r"^\s*(?:pub\s+)?use\s+([a-zA-Z_][a-zA-Z0-9_]*)")
                .expect("valid regex"),

            // For grouped imports: use tokio::{...};
            use_group: Regex::new(r"^\s*(?:pub\s+)?use\s+([a-zA-Z_][a-zA-Z0-9_]*)::\{")
                .expect("valid regex"),
        }
    }

    /// Extract the root crate name from a use statement.
    fn extract_crate_name(&self, line: &str) -> Option<String> {
        // Try regular use statement first
        if let Some(cap) = self.use_statement.captures(line) {
            let crate_name = cap.get(1)?.as_str();

            // Filter out relative imports and standard patterns
            if crate_name == "crate" || crate_name == "self" || crate_name == "super" {
                return None;
            }

            return Some(crate_name.to_string());
        }

        // Try grouped import
        if let Some(cap) = self.use_group.captures(line) {
            let crate_name = cap.get(1)?.as_str();

            if crate_name == "crate" || crate_name == "self" || crate_name == "super" {
                return None;
            }

            return Some(crate_name.to_string());
        }

        None
    }

    /// Determine confidence based on context.
    fn confidence_for_file(&self, file: &str) -> f32 {
        if file.contains("test") || file.contains("example") || file.contains("bench") {
            0.5 // Test/example imports don't reflect production architecture
        } else {
            1.0
        }
    }
}

impl Extractor for ImportGraphExtractor {
    fn name(&self) -> &str {
        "import_graph"
    }

    fn languages(&self) -> &[Language] {
        &[Language::Rust]
    }

    fn extract(
        &self,
        path_segments: &[String],
        content: &str,
        _language: Language,
        file: &str,
    ) -> Vec<ExtractedClaim> {
        let mut claims = Vec::new();
        let confidence = self.confidence_for_file(file);

        // Track unique imports to avoid duplicate claims
        let mut seen_imports = std::collections::HashSet::new();

        for (line_idx, line) in content.lines().enumerate() {
            let line_num = line_idx + 1;

            if let Some(crate_name) = self.extract_crate_name(line) {
                // Only create one claim per imported crate per file
                if !seen_imports.contains(&crate_name) {
                    seen_imports.insert(crate_name.clone());

                    let mut concept_path = path_segments.to_vec();
                    concept_path.push("imports".to_string());
                    concept_path.push(crate_name.clone());

                    claims.push(ExtractedClaim {
                        concept_path: format!("code://{}", concept_path.join("/")),
                        predicate: "imported".to_string(),
                        value: ObjectValue::Boolean(true),
                        file: file.to_string(),
                        line: line_num,
                        matched_text: line.trim().to_string(),
                        confidence,
                        description: format!("Module imports {}", crate_name),
                    });
                }
            }
        }

        claims
    }
}

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

    #[test]
    fn test_simple_use_statement() {
        let extractor = ImportGraphExtractor::new();
        let content = r#"
use tokio::runtime::Runtime;
use serde::{Serialize, Deserialize};
use std::sync::Arc;
        "#;

        let claims = extractor.extract(
            &["rust".to_string(), "maxwell".to_string(), "core".to_string()],
            content,
            Language::Rust,
            "src/lib.rs",
        );

        assert_eq!(claims.len(), 3);

        // Check that we captured the right crates
        let crate_names: Vec<_> =
            claims.iter().filter_map(|c| c.concept_path.split('/').last()).collect();

        assert!(crate_names.contains(&"tokio"));
        assert!(crate_names.contains(&"serde"));
        assert!(crate_names.contains(&"std"));
    }

    #[test]
    fn test_pub_use() {
        let extractor = ImportGraphExtractor::new();
        let content = r#"
pub use tokio::sync::Mutex;
        "#;

        let claims = extractor.extract(
            &["rust".to_string(), "myproject".to_string()],
            content,
            Language::Rust,
            "src/lib.rs",
        );

        assert_eq!(claims.len(), 1);
        assert!(claims[0].concept_path.contains("tokio"));
    }

    #[test]
    fn test_ignores_relative_imports() {
        let extractor = ImportGraphExtractor::new();
        let content = r#"
use crate::wallet::Wallet;
use super::common;
use self::internal;
        "#;

        let claims =
            extractor.extract(&["rust".to_string()], content, Language::Rust, "src/lib.rs");

        // Should not create claims for crate/super/self
        assert_eq!(claims.len(), 0);
    }

    #[test]
    fn test_deduplication() {
        let extractor = ImportGraphExtractor::new();
        let content = r#"
use tokio::runtime::Runtime;
use tokio::sync::Mutex;
use tokio::time::sleep;
        "#;

        let claims =
            extractor.extract(&["rust".to_string()], content, Language::Rust, "src/lib.rs");

        // Should only create one claim for "tokio" even though it's imported 3 times
        assert_eq!(claims.len(), 1);
        assert!(claims[0].concept_path.contains("tokio"));
    }

    #[test]
    fn test_confidence_in_test_file() {
        let extractor = ImportGraphExtractor::new();
        let content = r#"
use tokio::runtime::Runtime;
        "#;

        let claims =
            extractor.extract(&["rust".to_string()], content, Language::Rust, "src/wallet_test.rs");

        assert_eq!(claims.len(), 1);
        assert_eq!(claims[0].confidence, 0.5); // Test file gets reduced confidence
    }

    #[test]
    fn test_real_world_example() {
        let extractor = ImportGraphExtractor::new();
        let content = r#"
//! Wallet module for Maxwell.

use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
pub struct Wallet {
    balance: AtomicU64,
}
        "#;

        let claims = extractor.extract(
            &["rust".to_string(), "maxwell".to_string(), "wallet".to_string()],
            content,
            Language::Rust,
            "src/wallet.rs",
        );

        // Should capture std and serde, but deduplicate std
        assert_eq!(claims.len(), 2);
        assert!(claims.iter().any(|c| c.concept_path.contains("std")));
        assert!(claims.iter().any(|c| c.concept_path.contains("serde")));
    }

    #[test]
    fn test_concept_path_structure() {
        let extractor = ImportGraphExtractor::new();
        let content = r#"
use tokio::runtime::Runtime;
        "#;

        let claims = extractor.extract(
            &["rust".to_string(), "maxwell".to_string(), "core".to_string()],
            content,
            Language::Rust,
            "src/core/mod.rs",
        );

        assert_eq!(claims.len(), 1);
        // Should be: code://rust/maxwell/core/imports/tokio
        assert_eq!(claims[0].concept_path, "code://rust/maxwell/core/imports/tokio");
        assert_eq!(claims[0].predicate, "imported");
        assert_eq!(claims[0].value, ObjectValue::Boolean(true));
    }
}
