pub mod javascript;
pub mod python;
pub mod ruby;
pub mod rust_lang;

use std::path::Path;

use tree_sitter::Parser;

use crate::checks::{Confidence, Severity};

/// Languages we can parse with tree-sitter
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Lang {
    JavaScript,
    TypeScript,
    Python,
    Ruby,
    Rust,
}

/// A finding produced by AST analysis — higher confidence than regex
pub struct AstFinding {
    pub rule_id: String,
    pub severity: Severity,
    pub confidence: Confidence,
    pub message: String,
    pub line: usize, // 1-indexed
}

pub struct AstAnalyzer {
    js_parser: Parser,
    ts_parser: Parser,
    py_parser: Parser,
    rb_parser: Parser,
    rs_parser: Parser,
}

impl AstAnalyzer {
    pub fn new() -> Self {
        let mut js_parser = Parser::new();
        js_parser
            .set_language(&tree_sitter_javascript::LANGUAGE.into())
            .expect("failed to set JS language");

        let mut ts_parser = Parser::new();
        ts_parser
            .set_language(&tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into())
            .expect("failed to set TS language");

        let py_parser = python::new_parser();
        let rb_parser = ruby::new_parser();
        let rs_parser = rust_lang::new_parser();

        Self {
            js_parser,
            ts_parser,
            py_parser,
            rb_parser,
            rs_parser,
        }
    }

    /// Analyze a file for security patterns using AST.
    pub fn analyze(&mut self, path: &Path, source: &str) -> Vec<AstFinding> {
        match detect_language(path) {
            Some(Lang::JavaScript) => javascript::analyze(&mut self.js_parser, source),
            Some(Lang::TypeScript) => javascript::analyze(&mut self.ts_parser, source),
            Some(Lang::Python) => python::analyze(&mut self.py_parser, source),
            Some(Lang::Ruby) => ruby::analyze(&mut self.rb_parser, source),
            Some(Lang::Rust) => rust_lang::analyze(&mut self.rs_parser, source),
            None => vec![],
        }
    }
}

pub fn detect_language(path: &Path) -> Option<Lang> {
    let ext = path.extension().and_then(|e| e.to_str())?;
    match ext {
        "js" | "mjs" | "cjs" | "jsx" => Some(Lang::JavaScript),
        "ts" | "mts" | "cts" | "tsx" => Some(Lang::TypeScript),
        "py" | "pyw" => Some(Lang::Python),
        "rb" | "rake" | "gemspec" => Some(Lang::Ruby),
        "rs" => Some(Lang::Rust),
        _ => None,
    }
}

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

    #[test]
    fn test_detect_language_js() {
        assert_eq!(
            detect_language(Path::new("file.js")),
            Some(Lang::JavaScript)
        );
        assert_eq!(
            detect_language(Path::new("file.mjs")),
            Some(Lang::JavaScript)
        );
    }

    #[test]
    fn test_detect_language_ts() {
        assert_eq!(
            detect_language(Path::new("file.ts")),
            Some(Lang::TypeScript)
        );
        assert_eq!(
            detect_language(Path::new("file.tsx")),
            Some(Lang::TypeScript)
        );
    }

    #[test]
    fn test_detect_language_python() {
        assert_eq!(detect_language(Path::new("file.py")), Some(Lang::Python));
        assert_eq!(detect_language(Path::new("file.pyw")), Some(Lang::Python));
    }

    #[test]
    fn test_detect_language_ruby() {
        assert_eq!(detect_language(Path::new("file.rb")), Some(Lang::Ruby));
        assert_eq!(detect_language(Path::new("file.rake")), Some(Lang::Ruby));
        assert_eq!(detect_language(Path::new("file.gemspec")), Some(Lang::Ruby));
    }

    #[test]
    fn test_detect_language_rust() {
        assert_eq!(detect_language(Path::new("file.rs")), Some(Lang::Rust));
    }

    #[test]
    fn test_detect_language_unknown() {
        assert_eq!(detect_language(Path::new("file.go")), None);
        assert_eq!(detect_language(Path::new("file")), None);
    }

    #[test]
    fn test_ast_analyzer_new() {
        // Should not panic
        let _analyzer = AstAnalyzer::new();
    }
}
