//! Language detection for source files.

use std::fmt;
use std::path::Path;
use std::str::FromStr;

use serde::{Deserialize, Serialize};

/// Detected language of a file.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Language {
    /// Rust source files.
    Rust,
    /// Go source files.
    Go,
    /// Python source files.
    Python,
    /// TypeScript source files.
    TypeScript,
    /// JavaScript source files.
    JavaScript,
    /// C++ source files (including headers).
    Cpp,
    /// Java source files.
    Java,
    /// PHP source files.
    Php,
    /// Ruby source files (including ERB templates).
    Ruby,
    /// C# source files.
    CSharp,
    /// YAML configuration files.
    Yaml,
    /// TOML configuration files.
    Toml,
    /// JSON configuration files.
    Json,
    /// INI configuration files.
    Ini,
    /// Properties files (Java-style key=value).
    Properties,
    /// Dotenv files.
    Dotenv,
    /// Docker files.
    Docker,
    /// Terraform files.
    Terraform,
    /// Cargo manifest.
    CargoManifest,
    /// Go module file.
    GoMod,
    /// NPM manifest.
    NpmManifest,
    /// Python manifest.
    PythonManifest,
    /// Unknown language.
    Unknown,
}

/// Implement `Display` for formatting Language as a string.
impl fmt::Display for Language {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            Language::Rust => "rust",
            Language::Go => "go",
            Language::Python => "python",
            Language::TypeScript => "typescript",
            Language::JavaScript => "javascript",
            Language::Cpp => "cpp",
            Language::Java => "java",
            Language::Php => "php",
            Language::Ruby => "ruby",
            Language::CSharp => "csharp",
            Language::Yaml => "yaml",
            Language::Toml => "toml",
            Language::Json => "json",
            Language::Ini => "ini",
            Language::Properties => "properties",
            Language::Dotenv => "dotenv",
            Language::Docker => "docker",
            Language::Terraform => "terraform",
            Language::CargoManifest => "cargo",
            Language::GoMod => "gomod",
            Language::NpmManifest => "npm",
            Language::PythonManifest => "pip",
            Language::Unknown => "unknown",
        };
        write!(f, "{}", s)
    }
}

/// Implement `FromStr` to enable `.parse::<Language>()` syntax.
impl FromStr for Language {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "rust" => Ok(Language::Rust),
            "go" => Ok(Language::Go),
            "python" | "py" => Ok(Language::Python),
            "typescript" | "ts" => Ok(Language::TypeScript),
            "javascript" | "js" => Ok(Language::JavaScript),
            "cpp" | "c++" => Ok(Language::Cpp),
            "java" => Ok(Language::Java),
            "php" => Ok(Language::Php),
            "ruby" | "rb" => Ok(Language::Ruby),
            "csharp" | "c#" | "cs" => Ok(Language::CSharp),
            "yaml" | "yml" => Ok(Language::Yaml),
            "toml" => Ok(Language::Toml),
            "json" => Ok(Language::Json),
            "ini" => Ok(Language::Ini),
            "properties" => Ok(Language::Properties),
            "dotenv" | "env" => Ok(Language::Dotenv),
            "docker" | "dockerfile" => Ok(Language::Docker),
            "terraform" | "tf" => Ok(Language::Terraform),
            "cargo" | "cargo.toml" => Ok(Language::CargoManifest),
            "gomod" | "go.mod" => Ok(Language::GoMod),
            "npm" | "package.json" => Ok(Language::NpmManifest),
            "pip" | "requirements.txt" | "pyproject.toml" => Ok(Language::PythonManifest),
            _ => Err(s.to_string()),
        }
    }
}

impl Language {
    /// Parse a language name from a string.
    ///
    /// This is a convenience method that delegates to the `FromStr` implementation.
    /// Prefer using `.parse::<Language>()` for new code.
    ///
    /// # Errors
    ///
    /// Returns the unknown string if it doesn't match any known language.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// // Language is an internal type; use via config's declarative extractors
    /// use aphoria::types::Language;
    ///
    /// assert!(Language::from_str("rust").is_ok());
    /// assert!(Language::from_str("Rust").is_ok());
    /// assert!(Language::from_str("cobol").is_err());
    /// ```
    #[allow(clippy::should_implement_trait)] // We do implement FromStr, this is a convenience method
    pub fn from_str(s: &str) -> Result<Self, String> {
        s.parse()
    }

    /// Detect language from file extension.
    pub fn from_path(path: &Path) -> Self {
        let file_name = path.file_name().and_then(|s| s.to_str()).unwrap_or("");
        let extension = path.extension().and_then(|s| s.to_str()).unwrap_or("");

        // Check specific filenames first
        match file_name {
            "Cargo.toml" => return Language::CargoManifest,
            "go.mod" => return Language::GoMod,
            "package.json" => return Language::NpmManifest,
            "requirements.txt" | "pyproject.toml" => return Language::PythonManifest,
            _ if file_name.starts_with("Dockerfile") => return Language::Docker,
            _ if file_name.starts_with("docker-compose") => return Language::Docker,
            _ if file_name.starts_with(".env") => return Language::Dotenv,
            _ => {}
        }

        // Check extensions
        match extension {
            "rs" => Language::Rust,
            "go" => Language::Go,
            "py" => Language::Python,
            "ts" | "tsx" => Language::TypeScript,
            "js" | "jsx" => Language::JavaScript,
            "cpp" | "cxx" | "cc" | "h" | "hpp" => Language::Cpp,
            "java" => Language::Java,
            "php" => Language::Php,
            "rb" | "erb" => Language::Ruby,
            "cs" => Language::CSharp,
            "yaml" | "yml" => Language::Yaml,
            "toml" => Language::Toml,
            "json" => Language::Json,
            "ini" => Language::Ini,
            "properties" => Language::Properties,
            "tf" => Language::Terraform,
            _ => Language::Unknown,
        }
    }
}

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

    #[test]
    fn test_language_detection() {
        assert_eq!(Language::from_path(Path::new("src/main.rs")), Language::Rust);
        assert_eq!(Language::from_path(Path::new("main.go")), Language::Go);
        assert_eq!(Language::from_path(Path::new("app.py")), Language::Python);
        assert_eq!(Language::from_path(Path::new("game.cpp")), Language::Cpp);
        assert_eq!(Language::from_path(Path::new("header.hpp")), Language::Cpp);
        assert_eq!(Language::from_path(Path::new("config.ini")), Language::Ini);
        assert_eq!(Language::from_path(Path::new("Cargo.toml")), Language::CargoManifest);
        assert_eq!(Language::from_path(Path::new("go.mod")), Language::GoMod);
        assert_eq!(Language::from_path(Path::new(".env.production")), Language::Dotenv);
        assert_eq!(Language::from_path(Path::new("Dockerfile")), Language::Docker);
        assert_eq!(Language::from_path(Path::new("main.tf")), Language::Terraform);
        assert_eq!(Language::from_path(Path::new("variables.tf")), Language::Terraform);
        // New language extensions
        assert_eq!(Language::from_path(Path::new("Main.java")), Language::Java);
        assert_eq!(Language::from_path(Path::new("index.php")), Language::Php);
        assert_eq!(Language::from_path(Path::new("app.rb")), Language::Ruby);
        assert_eq!(Language::from_path(Path::new("view.erb")), Language::Ruby);
        assert_eq!(Language::from_path(Path::new("Controller.cs")), Language::CSharp);
        assert_eq!(Language::from_path(Path::new("application.properties")), Language::Properties);
    }

    #[test]
    fn test_from_str_valid_languages() {
        assert_eq!(Language::from_str("rust").unwrap(), Language::Rust);
        assert_eq!(Language::from_str("Rust").unwrap(), Language::Rust);
        assert_eq!(Language::from_str("RUST").unwrap(), Language::Rust);
        assert_eq!(Language::from_str("go").unwrap(), Language::Go);
        assert_eq!(Language::from_str("python").unwrap(), Language::Python);
        assert_eq!(Language::from_str("py").unwrap(), Language::Python);
        assert_eq!(Language::from_str("typescript").unwrap(), Language::TypeScript);
        assert_eq!(Language::from_str("ts").unwrap(), Language::TypeScript);
        assert_eq!(Language::from_str("javascript").unwrap(), Language::JavaScript);
        assert_eq!(Language::from_str("js").unwrap(), Language::JavaScript);
        assert_eq!(Language::from_str("cpp").unwrap(), Language::Cpp);
        assert_eq!(Language::from_str("c++").unwrap(), Language::Cpp);
        assert_eq!(Language::from_str("java").unwrap(), Language::Java);
        assert_eq!(Language::from_str("php").unwrap(), Language::Php);
        assert_eq!(Language::from_str("ruby").unwrap(), Language::Ruby);
        assert_eq!(Language::from_str("rb").unwrap(), Language::Ruby);
        assert_eq!(Language::from_str("csharp").unwrap(), Language::CSharp);
        assert_eq!(Language::from_str("c#").unwrap(), Language::CSharp);
        assert_eq!(Language::from_str("cs").unwrap(), Language::CSharp);
        assert_eq!(Language::from_str("yaml").unwrap(), Language::Yaml);
        assert_eq!(Language::from_str("yml").unwrap(), Language::Yaml);
        assert_eq!(Language::from_str("toml").unwrap(), Language::Toml);
        assert_eq!(Language::from_str("json").unwrap(), Language::Json);
        assert_eq!(Language::from_str("ini").unwrap(), Language::Ini);
        assert_eq!(Language::from_str("properties").unwrap(), Language::Properties);
        assert_eq!(Language::from_str("dotenv").unwrap(), Language::Dotenv);
        assert_eq!(Language::from_str("docker").unwrap(), Language::Docker);
        assert_eq!(Language::from_str("cargo").unwrap(), Language::CargoManifest);
        assert_eq!(Language::from_str("gomod").unwrap(), Language::GoMod);
        assert_eq!(Language::from_str("npm").unwrap(), Language::NpmManifest);
        assert_eq!(Language::from_str("pip").unwrap(), Language::PythonManifest);
        assert_eq!(Language::from_str("terraform").unwrap(), Language::Terraform);
        assert_eq!(Language::from_str("tf").unwrap(), Language::Terraform);
    }

    #[test]
    fn test_from_str_unknown_language() {
        assert_eq!(Language::from_str("cobol").unwrap_err(), "cobol");
        assert_eq!(Language::from_str("fortran").unwrap_err(), "fortran");
        assert_eq!(Language::from_str("").unwrap_err(), "");
    }

    #[test]
    fn test_parse_trait() {
        // Test that FromStr trait works with .parse()
        assert_eq!("rust".parse::<Language>().unwrap(), Language::Rust);
        assert_eq!("Python".parse::<Language>().unwrap(), Language::Python);
        assert!("cobol".parse::<Language>().is_err());
    }
}
