//! Constant declarations extractor for Rust.
//!
//! Tracks `const` and `static` declarations with their values for provenance tracking.
//! Enables learning loop to preserve knowledge of magic constants like:
//! - `const RAPL_POWER_UNIT: u32 = 0x606` (Intel SDM register)
//! - `const MAX_RETRIES: u8 = 3` (retry policy)
//! - `const BUFFER_SIZE: usize = 4096` (buffer sizing)

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

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

/// Extractor for Rust constant declarations.
///
/// Detects `const` and `static` declarations to track magic constants
/// and preserve provenance information.
pub struct ConstDeclarationsExtractor {
    /// Matches: const NAME: Type = value;
    const_decl: Regex,
    /// Matches: static NAME: Type = value;
    static_decl: Regex,
}

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

impl ConstDeclarationsExtractor {
    /// Create a new constant declarations extractor.
    ///
    /// # Panics
    /// Panics if any regex pattern is invalid (programmer error).
    #[allow(clippy::expect_used)]
    pub fn new() -> Self {
        Self {
            // const RAPL_POWER_UNIT: u32 = 0x606;
            const_decl: Regex::new(
                r"^\s*(?:pub\s+)?const\s+([A-Z_][A-Z0-9_]*)\s*:\s*(\w+)\s*=\s*([^;]+);",
            )
            .expect("valid regex"),

            // static MAX_CONNECTIONS: usize = 100;
            static_decl: Regex::new(
                r"^\s*(?:pub\s+)?static\s+([A-Z_][A-Z0-9_]*)\s*:\s*(\w+)\s*=\s*([^;]+);",
            )
            .expect("valid regex"),
        }
    }

    /// Clean up the value string (remove comments, whitespace).
    fn clean_value(&self, value: &str) -> String {
        value.split("//").next().unwrap_or(value).trim().to_string()
    }

    /// 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
        } else {
            1.0
        }
    }
}

impl Extractor for ConstDeclarationsExtractor {
    fn name(&self) -> &str {
        "const_declarations"
    }

    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);

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

            // Check for const declarations
            if let Some(cap) = self.const_decl.captures(line) {
                let name = cap.get(1).map_or("", |m| m.as_str());
                let type_name = cap.get(2).map_or("", |m| m.as_str());
                let value = cap.get(3).map_or("", |m| m.as_str());

                let cleaned_value = self.clean_value(value);

                let mut concept_path = path_segments.to_vec();
                concept_path.push("const".to_string());
                concept_path.push(name.to_lowercase());

                claims.push(ExtractedClaim {
                    concept_path: format!("code://{}", concept_path.join("/")),
                    predicate: "value".to_string(),
                    value: ObjectValue::Text(cleaned_value.clone()),
                    file: file.to_string(),
                    line: line_num,
                    matched_text: line.trim().to_string(),
                    confidence,
                    description: format!("{}: {} = {}", name, type_name, cleaned_value),
                });
            }

            // Check for static declarations
            if let Some(cap) = self.static_decl.captures(line) {
                let name = cap.get(1).map_or("", |m| m.as_str());
                let type_name = cap.get(2).map_or("", |m| m.as_str());
                let value = cap.get(3).map_or("", |m| m.as_str());

                let cleaned_value = self.clean_value(value);

                let mut concept_path = path_segments.to_vec();
                concept_path.push("static".to_string());
                concept_path.push(name.to_lowercase());

                claims.push(ExtractedClaim {
                    concept_path: format!("code://{}", concept_path.join("/")),
                    predicate: "value".to_string(),
                    value: ObjectValue::Text(cleaned_value.clone()),
                    file: file.to_string(),
                    line: line_num,
                    matched_text: line.trim().to_string(),
                    confidence,
                    description: format!("static {}: {} = {}", name, type_name, cleaned_value),
                });
            }
        }

        claims
    }
}

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

    #[test]
    fn test_simple_const() {
        let extractor = ConstDeclarationsExtractor::new();
        let content = r#"
const MAX_RETRIES: u8 = 3;
const BUFFER_SIZE: usize = 4096;
        "#;

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

        assert_eq!(claims.len(), 2);
        assert!(claims.iter().any(|c| c.concept_path.contains("max_retries")));
        assert!(claims.iter().any(|c| c.concept_path.contains("buffer_size")));

        let retry_claim = claims.iter().find(|c| c.concept_path.contains("max_retries")).unwrap();
        assert_eq!(retry_claim.value, ObjectValue::Text("3".to_string()));
    }

    #[test]
    fn test_hex_constant() {
        let extractor = ConstDeclarationsExtractor::new();
        let content = r#"
const RAPL_POWER_UNIT: u32 = 0x606;
        "#;

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

        assert_eq!(claims.len(), 1);
        assert!(claims[0].concept_path.contains("thermal"));
        assert!(claims[0].concept_path.contains("rapl_power_unit"));
        assert_eq!(claims[0].value, ObjectValue::Text("0x606".to_string()));
    }

    #[test]
    fn test_pub_const() {
        let extractor = ConstDeclarationsExtractor::new();
        let content = r#"
pub const DEFAULT_TIMEOUT: u64 = 30;
        "#;

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

        assert_eq!(claims.len(), 1);
        assert_eq!(claims[0].value, ObjectValue::Text("30".to_string()));
    }

    #[test]
    fn test_static_declaration() {
        let extractor = ConstDeclarationsExtractor::new();
        let content = r#"
static MAX_CONNECTIONS: usize = 100;
        "#;

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

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

    #[test]
    fn test_value_with_comment() {
        let extractor = ConstDeclarationsExtractor::new();
        let content = r#"
const TIMEOUT_MS: u64 = 5000; // 5 seconds
        "#;

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

        assert_eq!(claims.len(), 1);
        // Comment should be stripped
        assert_eq!(claims[0].value, ObjectValue::Text("5000".to_string()));
    }

    #[test]
    fn test_confidence_in_test_file() {
        let extractor = ConstDeclarationsExtractor::new();
        let content = r#"
const TEST_VALUE: u32 = 42;
        "#;

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

        assert_eq!(claims.len(), 1);
        assert_eq!(claims[0].confidence, 0.5);
    }

    #[test]
    fn test_real_world_maxwell() {
        let extractor = ConstDeclarationsExtractor::new();
        let content = r#"
//! MSR register definitions

pub const RAPL_POWER_UNIT: u32 = 0x606;
pub const RAPL_PKG_POWER_LIMIT: u32 = 0x610;
pub const RAPL_PKG_ENERGY_STATUS: u32 = 0x611;

const MAX_TEMP_CELSIUS: u8 = 85;
        "#;

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

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

        // All thermal constants should be tracked
        assert!(claims.iter().any(|c| c.value == ObjectValue::Text("0x606".to_string())));
        assert!(claims.iter().any(|c| c.value == ObjectValue::Text("0x610".to_string())));
        assert!(claims.iter().any(|c| c.value == ObjectValue::Text("85".to_string())));
    }
}
