use std::path::Path;

/// Base trait and implementations for technology detection
/// Follows Nushell single-purpose principle: each detector does one thing
use crate::error::DetectionResult;
use crate::models::Detection;

/// Core trait for detecting a technology
/// Each implementation is pure and returns Ok(Some(Detection)) or Ok(None)
pub trait Detector: Send + Sync {
    /// Name of this detector (e.g., "nodejs", "postgres")
    fn name(&self) -> &str;

    /// Attempt to detect technology in given project path
    /// Returns:
    /// - Ok(Some(detection)) if technology detected
    /// - Ok(None) if technology not detected
    /// - Err if detection failed (e.g., I/O error)
    fn detect(&self, project_path: &Path) -> DetectionResult<Option<Detection>>;

    /// Detector priority (higher = run first)
    /// Used for ordering multi-detector runs
    fn priority(&self) -> u8 {
        50 // Default priority
    }
}

pub mod docker;
pub mod nodejs;
pub mod postgres;
pub mod python;
pub mod redis;
pub mod rust;

pub use docker::DockerDetector;
pub use nodejs::NodeJsDetector;
pub use postgres::PostgresDetector;
pub use python::PythonDetector;
pub use redis::RedisDetector;
pub use rust::RustDetector;

/// Helper functions for detectors
pub mod helpers {
    use std::path::Path;

    use crate::error::{DetectionError, DetectionResult};

    /// Check if file exists in project
    pub fn file_exists(project_path: &Path, filename: &str) -> DetectionResult<bool> {
        let path = project_path.join(filename);
        Ok(path.exists())
    }

    /// Read file content as string
    pub fn read_file(project_path: &Path, filename: &str) -> DetectionResult<String> {
        let path = project_path.join(filename);

        if !path.exists() {
            return Err(DetectionError::file_not_found(&path));
        }

        std::fs::read_to_string(&path).map_err(|_| DetectionError::file_not_found(&path))
    }

    /// Parse JSON file
    pub fn read_json<T: serde::de::DeserializeOwned>(
        project_path: &Path,
        filename: &str,
    ) -> DetectionResult<T> {
        let content = read_file(project_path, filename)?;
        serde_json::from_str(&content)
            .map_err(|e| DetectionError::invalid_json(project_path.join(filename), e.to_string()))
    }

    /// Parse TOML file
    pub fn read_toml<T: serde::de::DeserializeOwned>(
        project_path: &Path,
        filename: &str,
    ) -> DetectionResult<T> {
        let content = read_file(project_path, filename)?;
        toml::from_str(&content)
            .map_err(|e| DetectionError::invalid_toml(project_path.join(filename), e.to_string()))
    }

    /// Search for pattern in files (grep-like)
    pub fn grep_in_project(
        project_path: &Path,
        pattern: &regex::Regex,
        extensions: &[&str],
    ) -> DetectionResult<Vec<GrepResult>> {
        let mut results = Vec::new();

        for entry in walkdir::WalkDir::new(project_path)
            .into_iter()
            .filter_map(|e| e.ok())
            .filter(|e| e.path().is_file())
        {
            let path = entry.path();

            // Check extension
            if !extensions.is_empty() {
                let ext = match path.extension() {
                    Some(e) => e.to_str().unwrap_or(""),
                    None => continue,
                };
                if !extensions.contains(&ext) {
                    continue;
                }
            }

            // Read and search
            let Ok(content) = std::fs::read_to_string(path) else {
                continue;
            };
            for (line_num, line) in content.lines().enumerate() {
                if pattern.is_match(line) {
                    results.push(GrepResult {
                        path: path.to_path_buf(),
                        line_num: line_num + 1,
                        content: line.to_string(),
                    });
                }
            }
        }

        Ok(results)
    }

    #[derive(Debug, Clone)]
    pub struct GrepResult {
        pub path: std::path::PathBuf,
        pub line_num: usize,
        pub content: String,
    }
}
