//! Execution logic for declarative extractors.

use stemedb_core::types::ObjectValue;

use super::parser::DeclarativeExtractor;
use super::types::DeclarativeValue;
use crate::extractors::Extractor;
use crate::types::{ExtractedClaim, Language};

impl Extractor for DeclarativeExtractor {
    fn name(&self) -> &str {
        self.name()
    }

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

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

        for (line_idx, line) in content.lines().enumerate() {
            if let Some(m) = self.pattern().find(line) {
                let matched_text = m.as_str().to_string();

                // Build concept path: code://{path_segments}/{subject}
                // path_segments already contains lang and project from the walker
                let base_path = path_segments.join("/");
                let concept_path = format!("code://{}/{}", base_path, self.def().claim.subject);

                // Determine value based on configuration
                let value = match &self.def().claim.value {
                    DeclarativeValue::MatchedText { .. } => {
                        // Use the regex match as the claim value
                        ObjectValue::Text(matched_text.clone())
                    }
                    DeclarativeValue::Boolean { value } => ObjectValue::Boolean(*value),
                    DeclarativeValue::Text { value } => ObjectValue::Text(value.clone()),
                };

                claims.push(ExtractedClaim {
                    concept_path,
                    predicate: self.def().claim.predicate.clone(),
                    value,
                    file: file.to_string(),
                    line: line_idx + 1,
                    matched_text,
                    confidence: self.def().confidence,
                    description: self.def().description.clone(),
                });
            }
        }

        claims
    }
}

#[cfg(test)]
mod tests {
    use super::super::types::{DeclarativeClaimDef, DeclarativeExtractorDef, DeclarativeValue};
    use super::*;

    fn make_def(name: &str, pattern: &str, languages: Vec<&str>) -> DeclarativeExtractorDef {
        DeclarativeExtractorDef {
            name: name.to_string(),
            description: "Test extractor".to_string(),
            languages: languages.into_iter().map(String::from).collect(),
            pattern: pattern.to_string(),
            claim: DeclarativeClaimDef {
                subject: "test/subject".to_string(),
                predicate: "test_predicate".to_string(),
                value: DeclarativeValue::Boolean { value: true },
            },
            confidence: 0.9,
            source: None,
        }
    }

    #[test]
    fn test_extract_with_boolean_value() {
        let def = make_def("unwrap_detector", r"\.unwrap\(\)", vec!["rust"]);
        let extractor = DeclarativeExtractor::try_new(def).expect("valid extractor");

        let content = r#"
            fn main() {
                let x = some_option.unwrap();
                let y = another.expect("msg");
            }
        "#;

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

        assert_eq!(claims.len(), 1);
        assert_eq!(claims[0].value, ObjectValue::Boolean(true));
        assert_eq!(claims[0].predicate, "test_predicate");
        assert_eq!(claims[0].confidence, 0.9);
        assert_eq!(claims[0].line, 3);
        assert!(claims[0].matched_text.contains("unwrap()"));
    }

    #[test]
    fn test_extract_with_text_value() {
        let mut def = make_def("api_v1", r"/api/v1/", vec!["rust", "go"]);
        def.claim.value = DeclarativeValue::Text { value: "v1".to_string() };
        def.claim.predicate = "api_version".to_string();

        let extractor = DeclarativeExtractor::try_new(def).expect("valid extractor");

        let content = r#"
            const ENDPOINT = "/api/v1/users";
        "#;

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

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

    #[test]
    fn test_extract_with_matched_text_value() {
        let mut def = make_def("legacy_algo", r"(?i)(blowfish|twofish|cast5)", vec!["rust"]);
        def.claim.value = DeclarativeValue::MatchedText { value_from_match: true };
        def.claim.predicate = "algorithm".to_string();
        def.claim.subject = "crypto/encryption/algorithm".to_string();

        let extractor = DeclarativeExtractor::try_new(def).expect("valid extractor");

        let content = r#"
            let cipher = Blowfish::new(key);
        "#;

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

        assert_eq!(claims.len(), 1);
        assert_eq!(claims[0].value, ObjectValue::Text("Blowfish".to_string()));
        assert!(claims[0].concept_path.contains("crypto/encryption/algorithm"));
    }

    #[test]
    fn test_multiple_matches_same_file() {
        let def = make_def("todo_finder", r"TODO:", vec!["rust"]);
        let extractor = DeclarativeExtractor::try_new(def).expect("valid extractor");

        let content = r#"
            // TODO: implement this
            fn foo() {}
            // TODO: add tests
            fn bar() {}
        "#;

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

        assert_eq!(claims.len(), 2);
        assert_eq!(claims[0].line, 2);
        assert_eq!(claims[1].line, 4);
    }

    #[test]
    fn test_no_matches() {
        let def = make_def("test", r"NONEXISTENT_PATTERN_XYZ", vec!["rust"]);
        let extractor = DeclarativeExtractor::try_new(def).expect("valid extractor");

        let claims =
            extractor.extract(&["rust".to_string()], "fn main() {}", Language::Rust, "src/main.rs");

        assert!(claims.is_empty());
    }

    #[test]
    fn test_concept_path_construction() {
        let mut def = make_def("test", r"pattern", vec!["rust"]);
        def.claim.subject = "security/tls/verify".to_string();

        let extractor = DeclarativeExtractor::try_new(def).expect("valid extractor");

        let claims = extractor.extract(
            &["rust".to_string(), "myproject".to_string()],
            "some pattern here",
            Language::Rust,
            "src/tls.rs",
        );

        assert_eq!(claims.len(), 1);
        assert_eq!(claims[0].concept_path, "code://rust/myproject/security/tls/verify");
    }
}
