use std::path::Path;

// Re-export tree-sitter crates for use by fresh-editor
pub use tree_sitter;
pub use tree_sitter_highlight;
pub use tree_sitter_highlight::HighlightConfiguration;

// Re-export language crates (gated by features)
#[cfg(feature = "tree-sitter-bash")]
pub use tree_sitter_bash;
#[cfg(feature = "tree-sitter-c")]
pub use tree_sitter_c;
#[cfg(feature = "tree-sitter-c-sharp")]
pub use tree_sitter_c_sharp;
#[cfg(feature = "tree-sitter-cpp")]
pub use tree_sitter_cpp;
#[cfg(feature = "tree-sitter-css")]
pub use tree_sitter_css;
#[cfg(feature = "tree-sitter-go")]
pub use tree_sitter_go;
#[cfg(feature = "tree-sitter-html")]
pub use tree_sitter_html;
#[cfg(feature = "tree-sitter-java")]
pub use tree_sitter_java;
#[cfg(feature = "tree-sitter-javascript")]
pub use tree_sitter_javascript;
#[cfg(feature = "tree-sitter-json")]
pub use tree_sitter_json;
#[cfg(feature = "tree-sitter-lua")]
pub use tree_sitter_lua;
#[cfg(feature = "tree-sitter-odin")]
pub use tree_sitter_odin;
#[cfg(feature = "tree-sitter-pascal")]
pub use tree_sitter_pascal;
#[cfg(feature = "tree-sitter-php")]
pub use tree_sitter_php;
#[cfg(feature = "tree-sitter-python")]
pub use tree_sitter_python;
#[cfg(feature = "tree-sitter-ruby")]
pub use tree_sitter_ruby;
#[cfg(feature = "tree-sitter-rust")]
pub use tree_sitter_rust;
#[cfg(feature = "tree-sitter-typescript")]
pub use tree_sitter_typescript;

/// Highlight category names used for default languages.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HighlightCategory {
    Attribute,
    Comment,
    Constant,
    Function,
    Keyword,
    Number,
    Operator,
    PunctuationBracket,
    PunctuationDelimiter,
    Property,
    String,
    Type,
    Variable,
}

impl HighlightCategory {
    /// Map a default language highlight index to a category
    pub fn from_default_index(index: usize) -> Option<Self> {
        match index {
            0 => Some(Self::Attribute),
            1 => Some(Self::Comment),
            2 => Some(Self::Constant),
            3 => Some(Self::Function),
            4 => Some(Self::Keyword),
            5 => Some(Self::Number),
            6 => Some(Self::Operator),
            7 => Some(Self::PunctuationBracket),
            8 => Some(Self::PunctuationDelimiter),
            9 => Some(Self::Property),
            10 => Some(Self::String),
            11 => Some(Self::Type),
            12 => Some(Self::Variable),
            _ => None,
        }
    }

    /// Map a TypeScript highlight index to a category.
    pub fn from_typescript_index(index: usize) -> Option<Self> {
        match index {
            0 => Some(Self::Attribute),             // attribute
            1 => Some(Self::Comment),               // comment
            2 => Some(Self::Constant),              // constant
            3 => Some(Self::Constant),              // constant.builtin
            4 => Some(Self::Type),                  // constructor
            5 => Some(Self::String),                // embedded (template substitutions)
            6 => Some(Self::Function),              // function
            7 => Some(Self::Function),              // function.builtin
            8 => Some(Self::Function),              // function.method
            9 => Some(Self::Keyword),               // keyword
            10 => Some(Self::Number),               // number
            11 => Some(Self::Operator),             // operator
            12 => Some(Self::Property),             // property
            13 => Some(Self::PunctuationBracket),   // punctuation.bracket
            14 => Some(Self::PunctuationDelimiter), // punctuation.delimiter
            15 => Some(Self::Constant),             // punctuation.special (template ${})
            16 => Some(Self::String),               // string
            17 => Some(Self::String),               // string.special (regex)
            18 => Some(Self::Type),                 // type
            19 => Some(Self::Type),                 // type.builtin
            20 => Some(Self::Variable),             // variable
            21 => Some(Self::Constant),             // variable.builtin (this, super, arguments)
            22 => Some(Self::Variable),             // variable.parameter
            _ => None,
        }
    }

    /// Get the theme key path for this category (e.g., "syntax.keyword").
    pub fn theme_key(&self) -> &'static str {
        match self {
            Self::Keyword => "syntax.keyword",
            Self::String => "syntax.string",
            Self::Comment => "syntax.comment",
            Self::Function => "syntax.function",
            Self::Type => "syntax.type",
            Self::Variable | Self::Property => "syntax.variable",
            Self::Constant | Self::Number | Self::Attribute => "syntax.constant",
            Self::Operator => "syntax.operator",
            Self::PunctuationBracket => "syntax.punctuation_bracket",
            Self::PunctuationDelimiter => "syntax.punctuation_delimiter",
        }
    }

    /// Get a human-readable display name for this category.
    pub fn display_name(&self) -> &'static str {
        match self {
            Self::Attribute => "Attribute",
            Self::Comment => "Comment",
            Self::Constant => "Constant",
            Self::Function => "Function",
            Self::Keyword => "Keyword",
            Self::Number => "Number",
            Self::Operator => "Operator",
            Self::PunctuationBracket => "Punctuation Bracket",
            Self::PunctuationDelimiter => "Punctuation Delimiter",
            Self::Property => "Property",
            Self::String => "String",
            Self::Type => "Type",
            Self::Variable => "Variable",
        }
    }
}

/// Language configuration for syntax highlighting
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Language {
    Rust,
    Python,
    JavaScript,
    TypeScript,
    HTML,
    CSS,
    C,
    Cpp,
    Go,
    Json,
    Jsonc,
    Java,
    CSharp,
    Php,
    Ruby,
    Bash,
    Lua,
    Pascal,
    Odin,
}

impl Language {
    /// Detect language from file extension.
    ///
    /// Derived from `extensions()` — see `Self::all` / `Self::extensions` for
    /// the authoritative table. A linear scan over ~18 languages is cheap
    /// enough that the nicer invariant (no duplicate tables) beats a match.
    pub fn from_path(path: &Path) -> Option<Self> {
        let ext = path.extension()?.to_str()?;
        Self::all()
            .iter()
            .find(|lang| lang.extensions().contains(&ext))
            .copied()
    }

    /// Get tree-sitter highlight configuration for this language
    pub fn highlight_config(&self) -> Result<HighlightConfiguration, String> {
        match self {
            Self::Rust => {
                #[cfg(feature = "tree-sitter-rust")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_rust::LANGUAGE.into(),
                        "rust",
                        tree_sitter_rust::HIGHLIGHTS_QUERY,
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create Rust highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-rust"))]
                Err("Rust language support not enabled".to_string())
            }
            Self::Python => {
                #[cfg(feature = "tree-sitter-python")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_python::LANGUAGE.into(),
                        "python",
                        tree_sitter_python::HIGHLIGHTS_QUERY,
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create Python highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-python"))]
                Err("Python language support not enabled".to_string())
            }
            Self::JavaScript => {
                #[cfg(feature = "tree-sitter-javascript")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_javascript::LANGUAGE.into(),
                        "javascript",
                        tree_sitter_javascript::HIGHLIGHT_QUERY,
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create JavaScript highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-javascript"))]
                Err("JavaScript language support not enabled".to_string())
            }
            Self::TypeScript => {
                #[cfg(all(feature = "tree-sitter-typescript", feature = "tree-sitter-javascript"))]
                {
                    let combined_highlights = format!(
                        "{}\n{}",
                        tree_sitter_typescript::HIGHLIGHTS_QUERY,
                        tree_sitter_javascript::HIGHLIGHT_QUERY
                    );
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
                        "typescript",
                        &combined_highlights,
                        "",
                        tree_sitter_typescript::LOCALS_QUERY,
                    )
                    .map_err(|e| format!("Failed to create TypeScript highlight config: {e}"))?;
                    config.configure(TYPESCRIPT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(all(
                    feature = "tree-sitter-typescript",
                    feature = "tree-sitter-javascript"
                )))]
                Err("TypeScript language support not enabled".to_string())
            }
            Self::HTML => {
                #[cfg(feature = "tree-sitter-html")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_html::LANGUAGE.into(),
                        "html",
                        tree_sitter_html::HIGHLIGHTS_QUERY,
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create HTML highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-html"))]
                Err("HTML language support not enabled".to_string())
            }
            Self::CSS => {
                #[cfg(feature = "tree-sitter-css")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_css::LANGUAGE.into(),
                        "css",
                        tree_sitter_css::HIGHLIGHTS_QUERY,
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create CSS highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-css"))]
                Err("CSS language support not enabled".to_string())
            }
            Self::C => {
                #[cfg(feature = "tree-sitter-c")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_c::LANGUAGE.into(),
                        "c",
                        tree_sitter_c::HIGHLIGHT_QUERY,
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create C highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-c"))]
                Err("C language support not enabled".to_string())
            }
            Self::Cpp => {
                #[cfg(feature = "tree-sitter-cpp")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_cpp::LANGUAGE.into(),
                        "cpp",
                        tree_sitter_cpp::HIGHLIGHT_QUERY,
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create C++ highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-cpp"))]
                Err("C++ language support not enabled".to_string())
            }
            Self::Go => {
                #[cfg(feature = "tree-sitter-go")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_go::LANGUAGE.into(),
                        "go",
                        tree_sitter_go::HIGHLIGHTS_QUERY,
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create Go highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-go"))]
                Err("Go language support not enabled".to_string())
            }
            Self::Json => {
                #[cfg(feature = "tree-sitter-json")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_json::LANGUAGE.into(),
                        "json",
                        tree_sitter_json::HIGHLIGHTS_QUERY,
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create JSON highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-json"))]
                Err("JSON language support not enabled".to_string())
            }
            Self::Jsonc => {
                // JSONC (JSON with Comments) reuses the tree-sitter-json parser.
                // A dedicated JSONC grammar isn't published as a Rust crate; the
                // JSON parser recovers past comments and trailing commas well
                // enough for highlighting, which is the only consumer here.
                #[cfg(feature = "tree-sitter-json")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_json::LANGUAGE.into(),
                        "jsonc",
                        tree_sitter_json::HIGHLIGHTS_QUERY,
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create JSONC highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-json"))]
                Err("JSONC language support not enabled".to_string())
            }
            Self::Java => {
                #[cfg(feature = "tree-sitter-java")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_java::LANGUAGE.into(),
                        "java",
                        tree_sitter_java::HIGHLIGHTS_QUERY,
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create Java highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-java"))]
                Err("Java language support not enabled".to_string())
            }
            Self::CSharp => {
                #[cfg(feature = "tree-sitter-c-sharp")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_c_sharp::LANGUAGE.into(),
                        "c_sharp",
                        "",
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create C# highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-c-sharp"))]
                Err("C# language support not enabled".to_string())
            }
            Self::Php => {
                #[cfg(feature = "tree-sitter-php")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_php::LANGUAGE_PHP.into(),
                        "php",
                        tree_sitter_php::HIGHLIGHTS_QUERY,
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create PHP highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-php"))]
                Err("PHP language support not enabled".to_string())
            }
            Self::Ruby => {
                #[cfg(feature = "tree-sitter-ruby")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_ruby::LANGUAGE.into(),
                        "ruby",
                        tree_sitter_ruby::HIGHLIGHTS_QUERY,
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create Ruby highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-ruby"))]
                Err("Ruby language support not enabled".to_string())
            }
            Self::Bash => {
                #[cfg(feature = "tree-sitter-bash")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_bash::LANGUAGE.into(),
                        "bash",
                        tree_sitter_bash::HIGHLIGHT_QUERY,
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create Bash highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-bash"))]
                Err("Bash language support not enabled".to_string())
            }
            Self::Lua => {
                #[cfg(feature = "tree-sitter-lua")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_lua::LANGUAGE.into(),
                        "lua",
                        tree_sitter_lua::HIGHLIGHTS_QUERY,
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create Lua highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-lua"))]
                Err("Lua language support not enabled".to_string())
            }
            Self::Pascal => {
                #[cfg(feature = "tree-sitter-pascal")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_pascal::LANGUAGE.into(),
                        "pascal",
                        "",
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create Pascal highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-pascal"))]
                Err("Pascal language support not enabled".to_string())
            }
            Self::Odin => {
                #[cfg(feature = "tree-sitter-odin")]
                {
                    let mut config = HighlightConfiguration::new(
                        tree_sitter_odin::LANGUAGE.into(),
                        "odin",
                        "",
                        "",
                        "",
                    )
                    .map_err(|e| format!("Failed to create Odin highlight config: {e}"))?;
                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
                    Ok(config)
                }
                #[cfg(not(feature = "tree-sitter-odin"))]
                Err("Odin language support not enabled".to_string())
            }
        }
    }

    /// Map tree-sitter highlight index to a highlight category
    pub fn highlight_category(&self, index: usize) -> Option<HighlightCategory> {
        match self {
            Self::TypeScript => HighlightCategory::from_typescript_index(index),
            _ => HighlightCategory::from_default_index(index),
        }
    }
}

impl Language {
    /// Returns all available language variants
    pub fn all() -> &'static [Language] {
        &[
            Language::Rust,
            Language::Python,
            Language::JavaScript,
            Language::TypeScript,
            Language::HTML,
            Language::CSS,
            Language::C,
            Language::Cpp,
            Language::Go,
            Language::Json,
            Language::Jsonc,
            Language::Java,
            Language::CSharp,
            Language::Php,
            Language::Ruby,
            Language::Bash,
            Language::Lua,
            Language::Pascal,
            Language::Odin,
        ]
    }

    /// Returns the language ID (lowercase identifier used in config/internal)
    pub fn id(&self) -> &'static str {
        match self {
            Self::Rust => "rust",
            Self::Python => "python",
            Self::JavaScript => "javascript",
            Self::TypeScript => "typescript",
            Self::HTML => "html",
            Self::CSS => "css",
            Self::C => "c",
            Self::Cpp => "cpp",
            Self::Go => "go",
            Self::Json => "json",
            Self::Jsonc => "jsonc",
            Self::Java => "java",
            Self::CSharp => "csharp",
            Self::Php => "php",
            Self::Ruby => "ruby",
            Self::Bash => "bash",
            Self::Lua => "lua",
            Self::Pascal => "pascal",
            Self::Odin => "odin",
        }
    }

    /// Returns the LSP languageId for use in textDocument/didOpen.
    ///
    /// This considers the file extension to return the correct LSP-spec language ID.
    /// For example, `.tsx` files return `"typescriptreact"` instead of `"typescript"`,
    /// and `.jsx` files return `"javascriptreact"` instead of `"javascript"`.
    ///
    /// See: <https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem>
    pub fn lsp_language_id(&self, path: &Path) -> &'static str {
        let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
        match (self, ext) {
            (Self::TypeScript, "tsx") => "typescriptreact",
            (Self::JavaScript, "jsx") => "javascriptreact",
            _ => self.id(),
        }
    }

    /// File extensions associated with this language.
    ///
    /// Keep in sync with `from_path`. Used by the grammar catalog so that
    /// tree-sitter-only languages (like TypeScript) still advertise the
    /// extensions they can highlight.
    pub fn extensions(&self) -> &'static [&'static str] {
        match self {
            Self::Rust => &["rs"],
            Self::Python => &["py"],
            Self::JavaScript => &["js", "jsx", "mjs", "cjs"],
            Self::TypeScript => &["ts", "tsx", "mts", "cts"],
            Self::HTML => &["html"],
            Self::CSS => &["css"],
            Self::C => &["c", "h"],
            Self::Cpp => &["cpp", "hpp", "cc", "hh", "cxx", "hxx", "cppm", "ixx"],
            Self::Go => &["go"],
            Self::Json => &["json"],
            Self::Jsonc => &["jsonc"],
            Self::Java => &["java"],
            Self::CSharp => &["cs"],
            Self::Php => &["php"],
            Self::Ruby => &["rb"],
            Self::Bash => &["sh", "bash"],
            Self::Lua => &["lua"],
            Self::Pascal => &["pas", "p"],
            Self::Odin => &["odin"],
        }
    }

    /// Returns the human-readable display name
    pub fn display_name(&self) -> &'static str {
        match self {
            Self::Rust => "Rust",
            Self::Python => "Python",
            Self::JavaScript => "JavaScript",
            Self::TypeScript => "TypeScript",
            Self::HTML => "HTML",
            Self::CSS => "CSS",
            Self::C => "C",
            Self::Cpp => "C++",
            Self::Go => "Go",
            Self::Json => "JSON",
            Self::Jsonc => "JSON with Comments",
            Self::Java => "Java",
            Self::CSharp => "C#",
            Self::Php => "PHP",
            Self::Ruby => "Ruby",
            Self::Bash => "Bash",
            Self::Lua => "Lua",
            Self::Pascal => "Pascal",
            Self::Odin => "Odin",
        }
    }

    /// Parse a language from its ID or display name
    pub fn from_id(id: &str) -> Option<Self> {
        let id_lower = id.to_lowercase();
        match id_lower.as_str() {
            "rust" => Some(Self::Rust),
            "python" => Some(Self::Python),
            "javascript" => Some(Self::JavaScript),
            "typescript" => Some(Self::TypeScript),
            "html" => Some(Self::HTML),
            "css" => Some(Self::CSS),
            "c" => Some(Self::C),
            "cpp" | "c++" => Some(Self::Cpp),
            "go" => Some(Self::Go),
            "json" => Some(Self::Json),
            "jsonc" => Some(Self::Jsonc),
            "java" => Some(Self::Java),
            "c_sharp" | "c#" | "csharp" => Some(Self::CSharp),
            "php" => Some(Self::Php),
            "ruby" => Some(Self::Ruby),
            "bash" => Some(Self::Bash),
            "lua" => Some(Self::Lua),
            "pascal" => Some(Self::Pascal),
            "odin" => Some(Self::Odin),
            _ => None,
        }
    }

    /// Try to map a syntect syntax name to a tree-sitter Language.
    ///
    /// This is used to get tree-sitter features (indentation, semantic highlighting)
    /// when using a syntect grammar for syntax highlighting. This is best-effort since
    /// tree-sitter only supports ~18 languages while syntect supports 100+.
    ///
    /// Syntect uses names like "Rust", "Python", "JavaScript", "JSON", "C++", "C#",
    /// "Bourne Again Shell (bash)", etc.
    pub fn from_name(name: &str) -> Option<Self> {
        // First try exact display name match
        for lang in Self::all() {
            if lang.display_name() == name {
                return Some(*lang);
            }
        }

        // Then try case-insensitive matching and common aliases
        let name_lower = name.to_lowercase();
        match name_lower.as_str() {
            "rust" => Some(Self::Rust),
            "python" => Some(Self::Python),
            "javascript" | "javascript (babel)" => Some(Self::JavaScript),
            "typescript" | "typescriptreact" => Some(Self::TypeScript),
            "html" => Some(Self::HTML),
            "css" => Some(Self::CSS),
            "c" => Some(Self::C),
            "c++" => Some(Self::Cpp),
            "go" | "golang" => Some(Self::Go),
            "json" => Some(Self::Json),
            "jsonc" | "json with comments" => Some(Self::Jsonc),
            "java" => Some(Self::Java),
            "c#" => Some(Self::CSharp),
            "php" => Some(Self::Php),
            "ruby" => Some(Self::Ruby),
            "lua" => Some(Self::Lua),
            "pascal" => Some(Self::Pascal),
            "odin" => Some(Self::Odin),
            _ => {
                // Try matching shell variants
                if name_lower.contains("bash") || name_lower.contains("shell") {
                    return Some(Self::Bash);
                }
                None
            }
        }
    }
}

impl std::fmt::Display for Language {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.id())
    }
}

const DEFAULT_HIGHLIGHT_CAPTURES: &[&str] = &[
    "attribute",
    "comment",
    "constant",
    "function",
    "keyword",
    "number",
    "operator",
    "punctuation.bracket",
    "punctuation.delimiter",
    "property",
    "string",
    "type",
    "variable",
];

const TYPESCRIPT_HIGHLIGHT_CAPTURES: &[&str] = &[
    "attribute",
    "comment",
    "constant",
    "constant.builtin",
    "constructor",
    "embedded",
    "function",
    "function.builtin",
    "function.method",
    "keyword",
    "number",
    "operator",
    "property",
    "punctuation.bracket",
    "punctuation.delimiter",
    "punctuation.special",
    "string",
    "string.special",
    "type",
    "type.builtin",
    "variable",
    "variable.builtin",
    "variable.parameter",
];

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

    #[test]
    fn test_lsp_language_id_tsx() {
        let lang = Language::TypeScript;
        assert_eq!(
            lang.lsp_language_id(Path::new("app.tsx")),
            "typescriptreact"
        );
    }

    #[test]
    fn test_lsp_language_id_ts() {
        let lang = Language::TypeScript;
        assert_eq!(lang.lsp_language_id(Path::new("app.ts")), "typescript");
    }

    #[test]
    fn test_lsp_language_id_jsx() {
        let lang = Language::JavaScript;
        assert_eq!(
            lang.lsp_language_id(Path::new("component.jsx")),
            "javascriptreact"
        );
    }

    #[test]
    fn test_lsp_language_id_js() {
        let lang = Language::JavaScript;
        assert_eq!(lang.lsp_language_id(Path::new("app.js")), "javascript");
    }

    #[test]
    fn test_lsp_language_id_csharp() {
        let lang = Language::CSharp;
        assert_eq!(lang.lsp_language_id(Path::new("main.cs")), "csharp");
    }

    #[test]
    fn test_lsp_language_id_other_languages() {
        assert_eq!(Language::Rust.lsp_language_id(Path::new("main.rs")), "rust");
        assert_eq!(
            Language::Python.lsp_language_id(Path::new("script.py")),
            "python"
        );
        assert_eq!(Language::Go.lsp_language_id(Path::new("main.go")), "go");
    }

    #[test]
    fn test_csharp_id_matches_config_key() {
        // Language::id() must return "csharp" to match the config key
        // used for LSP server lookup and language detection.
        assert_eq!(Language::CSharp.id(), "csharp");
    }

    /// Guard: `from_path` and `extensions()` must stay in sync — they used to
    /// be two hand-maintained tables with a "keep in sync" comment, which
    /// silently drifted when either was edited in isolation.
    #[test]
    fn test_from_path_matches_extensions() {
        for lang in Language::all() {
            for ext in lang.extensions() {
                let path = std::path::PathBuf::from(format!("x.{}", ext));
                let detected = Language::from_path(&path).unwrap_or_else(|| {
                    panic!(
                        "extension .{} listed by {:?} but from_path returned None",
                        ext, lang
                    )
                });
                assert_eq!(
                    detected, *lang,
                    "extension .{} listed by {:?} but from_path returned {:?}",
                    ext, lang, detected
                );
            }
        }
    }
}
