"""
Configuration objects for language servers
"""

import logging
import os
import re
from collections.abc import Iterable
from dataclasses import dataclass, field
from enum import Enum
from functools import cache
from pathlib import Path
from typing import TYPE_CHECKING, Self

if TYPE_CHECKING:
    from solidlsp import SolidLanguageServer

log = logging.getLogger(__name__)


class FilenameMatcher:
    def __init__(self, *file_extensions: str, case_sensitive: bool = True) -> None:
        """
        :param file_extensions: file extensions, e.g., `.py, .yml`
        :param case_sensitive: whether the file extensions are case-sensitive.
        """
        self._file_extensions = list(set(file_extensions)) if case_sensitive else list(set(ext.lower() for ext in file_extensions))
        self._case_sensitive = case_sensitive
        # Snapshot of the initial configuration, used by ``reset``. Relevant for matchers that are
        # per-language singletons (``Language.get_source_fn_matcher`` is ``@cache``d): extensions added
        # via ``add_extensions`` for one project must not leak into the next, so the singleton is reset
        # to this snapshot at every language server initialisation.
        self._initial_file_extensions = list(self._file_extensions)

    def reset(self) -> None:
        """
        Restore the matcher to its initial set of extensions (as provided at construction).

        Undoes any extensions added via :meth:`add_extensions`. Intended for the per-language
        singleton matchers, which are reset at the start of every language server initialisation so
        that a previous project's reconfiguration does not leak into a newly activated one.
        """
        self._file_extensions = list(self._initial_file_extensions)

    def add_extensions(self, *file_extensions: str) -> None:
        """
        Add further file extensions to this matcher (idempotent).

        This is intended for matchers that are per-language singletons, i.e. those returned by
        :meth:`Language.get_source_fn_matcher` (which is ``@cache``d): extensions that a user
        configures for a language server (e.g. ``.cgi`` for Perl) can be added here so that every
        consumer of the matcher — symbol index traversal, ignore checks, language composition —
        treats the same set of files as sources, staying in sync with the language server.

        :param file_extensions: the additional file extensions, e.g. ``.cgi``
        """
        for ext in file_extensions:
            norm = ext if self._case_sensitive else ext.lower()
            if norm not in self._file_extensions:
                self._file_extensions.append(norm)

    def is_relevant_filename(self, fn: str) -> bool:
        if not self._case_sensitive:
            fn = fn.lower()
        for ext in self._file_extensions:
            if fn.endswith(ext):
                return True
        return False

    def string_contains_relevant_filename(self, string: str) -> bool:
        """:return: whether ``string`` contains an occurrence of any registered extension as
        a *complete* extension — i.e. the extension must either end the string or be followed
        by a non-extension-character (anything other than a letter, digit, or underscore).
        """
        if not self._case_sensitive:
            string = string.lower()
        for ext in self._file_extensions:
            if re.search(rf"{re.escape(ext)}(?:\W|$)", string):
                return True
        return False


class Language(str, Enum):
    """
    Enumeration of language servers supported by SolidLSP.
    """

    CSHARP = "csharp"
    PYTHON = "python"
    RUST = "rust"
    JAVA = "java"
    KOTLIN = "kotlin"
    TYPESCRIPT = "typescript"
    GO = "go"
    RUBY = "ruby"
    DART = "dart"
    CPP = "cpp"
    CPP_CCLS = "cpp_ccls"
    PHP = "php"
    R = "r"
    PERL = "perl"
    CLOJURE = "clojure"
    ELIXIR = "elixir"
    ELM = "elm"
    TERRAFORM = "terraform"
    SWIFT = "swift"
    BASH = "bash"
    CRYSTAL = "crystal"
    CUE = "cue"
    ZIG = "zig"
    LUA = "lua"
    LUAU = "luau"
    """Luau Language Server for Roblox's Luau language (typed Lua 5.1 superset).
    Uses luau-lsp by JohnnyMorganz. Automatically downloads the binary if not found.
    Supports .luau files. Configure via .luaurc in the project root.
    """
    NIX = "nix"
    ERLANG = "erlang"
    OCAML = "ocaml"
    AL = "al"
    FSHARP = "fsharp"
    REGO = "rego"
    SCALA = "scala"
    JULIA = "julia"
    FORTRAN = "fortran"
    HASKELL = "haskell"
    HAXE = "haxe"
    """Haxe language server using vshaxe/haxe-language-server.
    Requires Haxe compiler (3.4.0+) and Node.js.
    Discovered from system PATH or vshaxe VSCode extension, otherwise downloaded from Open VSX.
    """
    LEAN4 = "lean4"
    GROOVY = "groovy"
    VUE = "vue"
    SVELTE = "svelte"
    """Svelte language server using svelte-language-server.
    Supports .svelte Single File Components plus TypeScript and JavaScript
    files in Svelte projects. Requires Node.js v18+ and npm.
    """
    POWERSHELL = "powershell"
    PASCAL = "pascal"
    """Pascal Language Server (pasls) for Free Pascal and Lazarus projects.
    Automatically downloads pasls binary. Requires FPC for full functionality.
    Set PP and FPCDIR environment variables for source navigation.
    """
    MATLAB = "matlab"
    """MATLAB language server using the official MathWorks MATLAB Language Server.
    Requires MATLAB R2021b or later and Node.js.
    Set MATLAB_PATH environment variable or configure matlab_path in ls_specific_settings.
    """
    MSL = "msl"
    """mIRC Scripting Language (mSL) language server.
    Supports .mrc files used in mIRC and AdiIRC IRC clients.
    Uses a custom LSP server based on pygls. Automatically sets up
    a virtual environment with pygls dependencies on first use.
    """
    BSL = "bsl"
    """BSL Language Server for 1C:Enterprise and OneScript languages.
    Uses bsl-language-server by 1c-syntax. Automatically downloads the JAR.
    Supports .bsl and .os files. Requires Java 21+ on PATH.
    """
    ADA = "ada"
    """Ada / SPARK language server using AdaCore's Ada Language Server (ALS).
    Supports .ads (specs), .adb (bodies), and .ada files. Auto-downloads the
    ALS binary from AdaCore's GitHub releases. Works best with a .gpr GNAT
    project file at the repository root. SPARK files are handled transparently
    by the same server, since SPARK is distinguished by pragmas/aspects in
    source rather than by file extension.
    """
    GDSCRIPT = "gdscript"
    """GDScript language server for Godot Engine projects (Godot 3 and 4).
    Connects to the Godot editor's built-in LSP server over TCP (port 6008).
    The editor must already be running with its built-in LSP enabled (default).
    Supports .gd and .gdscript files.
    """
    QML = "qml"
    """QML language server using Qt's qmlls (or qmlls6).
    Supports .qml files. Requires Qt 6 installation providing qmlls on PATH.
    See https://doc.qt.io/qt-6/qtqml-tool-qmlls.html
    """
    # Experimental or deprecated Language Servers
    TYPESCRIPT_VTS = "typescript_vts"
    """Use the typescript language server through the natively bundled vscode extension via https://github.com/yioneko/vtsls"""
    PYTHON_JEDI = "python_jedi"
    """Jedi language server for Python (instead of pyright, which is the default)"""
    PYTHON_TY = "python_ty"
    """Ty language server for Python (instead of pyright, which is the default)."""
    PYTHON_PYREFLY = "python_pyrefly"
    """Pyrefly language server for Python (instead of pyright, which is the default)."""
    CSHARP_OMNISHARP = "csharp_omnisharp"
    """OmniSharp language server for C# (instead of the default csharp-ls by microsoft).
    Currently has problems with finding references, and generally seems less stable and performant.
    """
    RUBY_SOLARGRAPH = "ruby_solargraph"
    """Solargraph language server for Ruby (legacy, experimental).
    Use Language.RUBY (ruby-lsp) for better performance and modern LSP features.
    """
    PHP_PHPACTOR = "php_phpactor"
    """Phpactor language server for PHP (instead of Intelephense, which is the default).
    Requires PHP 8.1+ on the system. Fully open-source (MIT license).
    """
    PHP_PHPANTOM = "php_phpantom"
    """PHPantom language server for PHP (instead of Intelephense, which is the default).
    Uses the open-source Rust-based phpantom_lsp binary and can be auto-downloaded.
    """
    MARKDOWN = "markdown"
    """Marksman language server for Markdown (experimental).
    Must be explicitly specified as the main language, not auto-detected.
    This is an edge case primarily useful when working on documentation-heavy projects.
    """
    LATEX = "latex"
    """texlab language server for LaTeX/BibTeX (experimental).
    Must be explicitly specified as the main language, not auto-detected.
    Provides sectioning-hierarchy document symbols plus label/citation definitions and references.
    """
    YAML = "yaml"
    """YAML language server (experimental).
    Must be explicitly specified as the main language, not auto-detected.
    """
    JSON = "json"
    """JSON language server using vscode-json-languageserver (experimental).
    Provides document symbol navigation and hover for JSON files.
    Must be explicitly specified as the main language, not auto-detected.
    Requires Node.js and npm.
    """
    TOML = "toml"
    """TOML language server using Taplo.
    Supports TOML validation, formatting, and schema support.
    """
    HLSL = "hlsl"
    """Shader language server using shader-language-server (antaalt/shader-sense).
    Supports .hlsl, .hlsli, .fx, .fxh, .cginc, .compute, .shader, .glsl, .vert, .frag, .geom, .tesc, .tese, .comp, .wgsl files.
    Automatically downloads shader-language-server binary.
    """
    SYSTEMVERILOG = "systemverilog"
    """SystemVerilog language server using verible-verilog-ls.
    Supports .sv, .svh, .v, .vh files.
    Automatically downloads verible binary.
    """
    SOLIDITY = "solidity"
    """Solidity language server using the Nomic Foundation Solidity Language Server
    (@nomicfoundation/solidity-language-server).
    Supports .sol files. Provides go-to-definition, find references, document symbols,
    hover, and diagnostics. Requires Node.js and npm.
    Works best with a foundry.toml or hardhat.config.js in the project root.
    """
    ANSIBLE = "ansible"
    """Ansible language server (experimental) using @ansible/ansible-language-server.
    Supports .yaml and .yml files (same extensions as YAML, hence experimental).
    Must be explicitly specified in project.yml. Requires Node.js and npm.
    Requires ``ansible`` in PATH for full functionality.
    """
    HTML = "html"
    """HTML language server (experimental) using vscode-html-language-server from
    Microsoft's vscode-langservers-extracted npm package. Supports *.html and *.htm files.
    Must be explicitly specified in project.yml. Requires Node.js and npm.
    Note: HTML LSP provides in-file element/id symbols only; cross-file references
    are not meaningful for HTML. Also used as a companion server by Angular LS for
    plain HTML documentSymbol support.
    """
    SCSS = "scss"
    """SCSS / Sass / CSS language server (experimental) using some-sass-language-server
    (https://github.com/wkillerud/some-sass). Handles *.scss, *.sass, and *.css.
    Must be explicitly specified in project.yml. Requires Node.js and npm.
    Provides full @use/@forward workspace navigation across SCSS files; CSS support
    relies on the same vscode-css-languageservice engine and is enabled at startup
    via the somesass.css.* feature toggles (which default to off upstream).
    """
    ANGULAR = "angular"
    """Angular Language Server (experimental) using the official @angular/language-server
    (ngserver). Supports *.ts and *.html files (Angular templates can be external or inline).
    Understands Angular template syntax (*ngIf, [prop], (event), {{ interpolation }},
    @if/@for blocks, etc.) and provides type-aware navigation between templates and
    component classes — which the plain HTML and TypeScript LSPs cannot.
    Requires Node.js, npm, and a valid Angular workspace (angular.json or Nx project.json
    at the repository root). When activated, do not also enable typescript or html in
    project.yml — Angular LS supersedes both for Angular projects.
    Must be explicitly specified in project.yml.
    """

    @classmethod
    def iter_all(cls, include_experimental: bool = False, include_non_programming_languages: bool = True) -> Iterable[Self]:
        for lang in cls:
            if include_experimental or not lang.is_experimental():
                if include_non_programming_languages or lang.is_programming_language():
                    yield lang

    def is_experimental(self) -> bool:
        """
        Check if the language server is experimental or deprecated.

        Note for serena users/developers:
        Experimental languages are not autodetected and must be explicitly specified
        in the project.yml configuration.
        """
        return self in {
            self.ANSIBLE,
            self.TYPESCRIPT_VTS,
            self.PYTHON_JEDI,
            self.PYTHON_TY,
            self.PYTHON_PYREFLY,
            self.CSHARP_OMNISHARP,
            self.RUBY_SOLARGRAPH,
            self.PHP_PHPACTOR,
            self.PHP_PHPANTOM,
            self.MARKDOWN,
            self.LATEX,
            self.YAML,
            self.JSON,
            self.TOML,
            self.GROOVY,
            self.CPP_CCLS,
            self.SOLIDITY,
            self.HTML,
            self.SCSS,
            self.ANGULAR,
        }

    def is_programming_language(self) -> bool:
        """Whether the supported language should be considered a programming language.
        Solidlsp supports languages like markdown or json, this method returns False for them.
        """
        return self not in frozenset((self.MARKDOWN, self.LATEX, self.JSON, self.TOML, self.YAML, self.ANSIBLE))

    def __str__(self) -> str:
        return self.value

    def get_priority(self) -> int:
        """
        :return: priority of the language for breaking ties between languages; higher is more important.
        """
        # experimental languages have the lowest priority
        if self.is_experimental():
            return 0
        # We assign lower priority to languages that are supersets of others, such that
        # the "larger" language is only chosen when it matches more strongly
        match self:
            # languages that are supersets of others (Vue/Svelte are supersets of TypeScript/JavaScript)
            case self.VUE | self.SVELTE:
                return 1
            # regular languages
            case _:
                return 2

    def supports_implementation_request(self) -> bool:
        """
        Return whether the default language server for this language supports ``textDocument/implementation``.
        """
        return self.get_ls_class().supports_implementation_request()

    # NOTE: Caching results in a singleton per enum item, which is a precondition for persistent configuration of the matcher.
    @cache
    def get_source_fn_matcher(self) -> FilenameMatcher:
        match self:
            case self.PYTHON | self.PYTHON_JEDI | self.PYTHON_TY | self.PYTHON_PYREFLY:
                return FilenameMatcher(".py", ".pyi")
            case self.JAVA:
                return FilenameMatcher(".java")
            case self.TYPESCRIPT | self.TYPESCRIPT_VTS:
                # see https://github.com/oraios/serena/issues/204
                path_patterns = []
                for prefix in ["c", "m", ""]:
                    for postfix in ["x", ""]:
                        for base_pattern in ["ts", "js"]:
                            path_patterns.append(f".{prefix}{base_pattern}{postfix}")
                return FilenameMatcher(*path_patterns)
            case self.CSHARP | self.CSHARP_OMNISHARP:
                return FilenameMatcher(".cs")
            case self.RUST:
                return FilenameMatcher(".rs")
            case self.GO:
                return FilenameMatcher(".go")
            case self.RUBY:
                return FilenameMatcher(".rb", ".erb")
            case self.RUBY_SOLARGRAPH:
                return FilenameMatcher(".rb")
            case self.CPP:
                # From llvm-project/clang/lib/Driver/Types.cpp types::lookupTypeForExtension:
                return FilenameMatcher(
                    # C
                    ".c",
                    ".h",
                    # C++
                    ".c++",
                    ".cc",
                    ".cp",
                    ".cpp",
                    ".cxx",
                    ".hh",
                    ".hpp",
                    ".hxx",
                    # C++ include files
                    ".inl",
                    ".ipp",
                    ".tpp",
                    ".txx",
                    # Objective-C
                    ".m",
                    ".mm",
                    # C++20 module interface files
                    ".c++m",
                    ".cppm",
                    ".cxxm",
                    ".ixx",
                    # CUDA
                    ".cu",
                    # HIP
                    ".hip",
                    # OpenCL
                    ".cl",
                    ".clcpp",
                    # Arduino sketch: not in clang's extension table, but it is C++.
                    # Routing it here lets clangd serve symbols; the project must
                    # tell clangd it is C++ (a .clangd with CompileFlags Add: [-xc++],
                    # or a compile DB), since the clang driver can't infer a job from .ino.
                    ".ino",
                    case_sensitive=False,
                )
            case self.CPP_CCLS:
                # From llvm-project/clang/lib/Driver/Types.cpp types::lookupTypeForExtension:
                return FilenameMatcher(
                    # C
                    ".c",
                    ".h",
                    # C++
                    ".c++",
                    ".cc",
                    ".cp",
                    ".cpp",
                    ".cxx",
                    ".hh",
                    ".hpp",
                    ".hxx",
                    # C++ include files
                    ".inl",
                    ".ipp",
                    ".tpp",
                    ".txx",
                    # Objective-C
                    ".m",
                    ".mm",
                    # Arduino sketch (C++); see note in the CPP case above.
                    ".ino",
                    case_sensitive=False,
                )
            case self.KOTLIN:
                return FilenameMatcher(".kt", ".kts")
            case self.DART:
                return FilenameMatcher(".dart")
            case self.PHP | self.PHP_PHPACTOR | self.PHP_PHPANTOM:
                return FilenameMatcher(".php")
            case self.R:
                return FilenameMatcher(".R", ".r", ".Rmd", ".Rnw")
            case self.PERL:
                return FilenameMatcher(".pl", ".pm", ".t")
            case self.CLOJURE:
                return FilenameMatcher(".clj", ".cljs", ".cljc", ".edn")  # codespell:ignore edn
            case self.ELIXIR:
                return FilenameMatcher(".ex", ".exs")
            case self.ELM:
                return FilenameMatcher(".elm")
            case self.TERRAFORM:
                return FilenameMatcher(".tf", ".tfvars", ".tfstate")
            case self.SWIFT:
                return FilenameMatcher(".swift")
            case self.BASH:
                return FilenameMatcher(".sh", ".bash")
            case self.CRYSTAL:
                return FilenameMatcher(".cr")
            case self.CUE:
                return FilenameMatcher(".cue")
            case self.YAML:
                return FilenameMatcher(".yaml", ".yml")
            case self.JSON:
                return FilenameMatcher(".json", ".jsonc")
            case self.TOML:
                return FilenameMatcher(".toml")
            case self.ZIG:
                return FilenameMatcher(".zig", ".zon")
            case self.LUA:
                return FilenameMatcher(".lua")
            case self.LUAU:
                return FilenameMatcher(".luau")
            case self.NIX:
                return FilenameMatcher(".nix")
            case self.ERLANG:
                return FilenameMatcher(".erl", ".hrl", ".escript", ".config", ".app", ".app.src")
            case self.OCAML:
                return FilenameMatcher(".ml", ".mli", ".re", ".rei")
            case self.AL:
                return FilenameMatcher(".al", ".dal")
            case self.FSHARP:
                return FilenameMatcher(".fs", ".fsx", ".fsi")
            case self.REGO:
                return FilenameMatcher(".rego")
            case self.MARKDOWN:
                return FilenameMatcher(".md", ".markdown")
            case self.LATEX:
                return FilenameMatcher(".tex", ".bib", ".sty", ".cls")
            case self.SCALA:
                return FilenameMatcher(".scala", ".sbt")
            case self.JULIA:
                return FilenameMatcher(".jl")
            case self.FORTRAN:
                return FilenameMatcher(".f90", ".f95", ".f03", ".f08", ".f", ".for", ".fpp", case_sensitive=False)
            case self.HASKELL:
                return FilenameMatcher(".hs", ".lhs")
            case self.HAXE:
                return FilenameMatcher(".hx")
            case self.LEAN4:
                return FilenameMatcher(".lean")
            case self.VUE:
                path_patterns = [".vue"]
                for prefix in ["c", "m", ""]:
                    for postfix in ["x", ""]:
                        for base_pattern in ["ts", "js"]:
                            path_patterns.append(f".{prefix}{base_pattern}{postfix}")
                return FilenameMatcher(*path_patterns)
            case self.SVELTE:
                path_patterns = [".svelte"]
                for prefix in ["c", "m", ""]:
                    for base_pattern in ["ts", "js"]:
                        path_patterns.append(f".{prefix}{base_pattern}")
                return FilenameMatcher(*path_patterns)
            case self.POWERSHELL:
                return FilenameMatcher(".ps1", ".psm1", ".psd1")
            case self.PASCAL:
                return FilenameMatcher(".pas", ".pp", ".lpr", ".dpr", ".dpk", ".inc")
            case self.GROOVY:
                return FilenameMatcher(".groovy", ".gvy")
            case self.MATLAB:
                return FilenameMatcher(".m", ".mlx", ".mlapp")
            case self.HLSL:
                return FilenameMatcher(
                    ".hlsl",
                    ".hlsli",
                    ".fx",
                    ".fxh",
                    ".cginc",
                    ".compute",
                    ".shader",
                    ".glsl",
                    ".vert",
                    ".frag",
                    ".geom",
                    ".tesc",
                    ".tese",
                    ".comp",
                    ".wgsl",
                )
            case self.SYSTEMVERILOG:
                return FilenameMatcher(".sv", ".svh", ".v", ".vh")
            case self.SOLIDITY:
                return FilenameMatcher(".sol")
            case self.ANSIBLE:
                return FilenameMatcher(".yaml", ".yml")
            case self.MSL:
                return FilenameMatcher(".mrc")
            case self.BSL:
                return FilenameMatcher(".bsl", ".os")
            case self.ADA:
                return FilenameMatcher(".ads", ".adb", ".ada", case_sensitive=False)
            case self.GDSCRIPT:
                return FilenameMatcher(".gd", ".gdscript")
            case self.QML:
                return FilenameMatcher(".qml")
            case self.HTML:
                return FilenameMatcher(".html", ".htm")
            case self.SCSS:
                # *.css is handled by the same engine (vscode-css-languageservice) that powers
                # Microsoft's CSS LS, so we route plain CSS through Some Sass too. The CSS feature
                # toggles default off upstream and are flipped on at initialization time.
                return FilenameMatcher(".scss", ".sass", ".css")
            case self.ANGULAR:
                # Angular templates can be standalone .html files or inline templates
                # within .ts component files; the dual-server architecture handles both.
                # SCSS / styles are deliberately NOT subsumed — use Language.SCSS for those.
                path_patterns = [".html", ".htm"]
                for prefix in ["c", "m", ""]:
                    for postfix in ["x", ""]:
                        path_patterns.append(f".{prefix}ts{postfix}")
                return FilenameMatcher(*path_patterns)
            case _:
                raise ValueError(f"Unhandled language: {self}")

    def get_ls_class(self) -> type["SolidLanguageServer"]:
        match self:
            case self.PYTHON:
                from solidlsp.language_servers.pyright_server import PyrightServer

                return PyrightServer
            case self.PYTHON_JEDI:
                from solidlsp.language_servers.jedi_server import JediServer

                return JediServer
            case self.PYTHON_TY:
                from solidlsp.language_servers.ty_server import TyLanguageServer

                return TyLanguageServer
            case self.PYTHON_PYREFLY:
                from solidlsp.language_servers.pyrefly_server import PyreflyLanguageServer

                return PyreflyLanguageServer
            case self.JAVA:
                from solidlsp.language_servers.eclipse_jdtls import EclipseJDTLS

                return EclipseJDTLS
            case self.KOTLIN:
                from solidlsp.language_servers.kotlin_language_server import KotlinLanguageServer

                return KotlinLanguageServer
            case self.RUST:
                from solidlsp.language_servers.rust_analyzer import RustAnalyzer

                return RustAnalyzer
            case self.CSHARP:
                from solidlsp.language_servers.csharp_language_server import CSharpLanguageServer

                return CSharpLanguageServer
            case self.CSHARP_OMNISHARP:
                from solidlsp.language_servers.omnisharp import OmniSharp

                return OmniSharp
            case self.TYPESCRIPT:
                from solidlsp.language_servers.typescript_language_server import TypeScriptLanguageServer

                return TypeScriptLanguageServer
            case self.TYPESCRIPT_VTS:
                from solidlsp.language_servers.vts_language_server import VtsLanguageServer

                return VtsLanguageServer
            case self.VUE:
                from solidlsp.language_servers.vue_language_server import VueLanguageServer

                return VueLanguageServer
            case self.SVELTE:
                from solidlsp.language_servers.svelte_language_server import SvelteLanguageServer

                return SvelteLanguageServer
            case self.GO:
                from solidlsp.language_servers.gopls import Gopls

                return Gopls
            case self.RUBY:
                from solidlsp.language_servers.ruby_lsp import RubyLsp

                return RubyLsp
            case self.RUBY_SOLARGRAPH:
                from solidlsp.language_servers.solargraph import Solargraph

                return Solargraph
            case self.DART:
                from solidlsp.language_servers.dart_language_server import DartLanguageServer

                return DartLanguageServer
            case self.CPP:
                from solidlsp.language_servers.clangd_language_server import ClangdLanguageServer

                return ClangdLanguageServer
            case self.CPP_CCLS:
                from solidlsp.language_servers.ccls_language_server import CCLS

                return CCLS
            case self.PHP:
                from solidlsp.language_servers.intelephense import Intelephense

                return Intelephense
            case self.PHP_PHPACTOR:
                from solidlsp.language_servers.phpactor import PhpactorServer

                return PhpactorServer
            case self.PHP_PHPANTOM:
                from solidlsp.language_servers.phpantom import PHPantomServer

                return PHPantomServer
            case self.PERL:
                from solidlsp.language_servers.perl_language_server import PerlLanguageServer

                return PerlLanguageServer
            case self.CLOJURE:
                from solidlsp.language_servers.clojure_lsp import ClojureLSP

                return ClojureLSP
            case self.ELIXIR:
                from solidlsp.language_servers.elixir_tools.elixir_tools import ElixirTools

                return ElixirTools
            case self.ELM:
                from solidlsp.language_servers.elm_language_server import ElmLanguageServer

                return ElmLanguageServer
            case self.TERRAFORM:
                from solidlsp.language_servers.terraform_ls import TerraformLS

                return TerraformLS
            case self.SWIFT:
                from solidlsp.language_servers.sourcekit_lsp import SourceKitLSP

                return SourceKitLSP
            case self.BASH:
                from solidlsp.language_servers.bash_language_server import BashLanguageServer

                return BashLanguageServer
            case self.CRYSTAL:
                from solidlsp.language_servers.crystal_language_server import CrystalLanguageServer

                return CrystalLanguageServer
            case self.CUE:
                from solidlsp.language_servers.cue_language_server import CueLanguageServer

                return CueLanguageServer
            case self.YAML:
                from solidlsp.language_servers.yaml_language_server import YamlLanguageServer

                return YamlLanguageServer
            case self.JSON:
                from solidlsp.language_servers.json_language_server import JsonLanguageServer

                return JsonLanguageServer
            case self.TOML:
                from solidlsp.language_servers.taplo_server import TaploServer

                return TaploServer
            case self.ZIG:
                from solidlsp.language_servers.zls import ZigLanguageServer

                return ZigLanguageServer
            case self.NIX:
                from solidlsp.language_servers.nixd_ls import NixLanguageServer

                return NixLanguageServer
            case self.LUA:
                from solidlsp.language_servers.lua_ls import LuaLanguageServer

                return LuaLanguageServer

            case self.LUAU:
                from solidlsp.language_servers.luau_lsp import LuauLanguageServer

                return LuauLanguageServer

            case self.ERLANG:
                from solidlsp.language_servers.erlang_language_server import ErlangLanguageServer

                return ErlangLanguageServer
            case self.OCAML:
                from solidlsp.language_servers.ocaml_lsp_server import OcamlLanguageServer

                return OcamlLanguageServer
            case self.AL:
                from solidlsp.language_servers.al_language_server import ALLanguageServer

                return ALLanguageServer
            case self.REGO:
                from solidlsp.language_servers.regal_server import RegalLanguageServer

                return RegalLanguageServer
            case self.MARKDOWN:
                from solidlsp.language_servers.marksman import Marksman

                return Marksman
            case self.LATEX:
                from solidlsp.language_servers.texlab_language_server import TexlabLanguageServer

                return TexlabLanguageServer
            case self.R:
                from solidlsp.language_servers.r_language_server import RLanguageServer

                return RLanguageServer
            case self.SCALA:
                from solidlsp.language_servers.scala_language_server import ScalaLanguageServer

                return ScalaLanguageServer
            case self.JULIA:
                from solidlsp.language_servers.julia_server import JuliaLanguageServer

                return JuliaLanguageServer
            case self.FORTRAN:
                from solidlsp.language_servers.fortran_language_server import FortranLanguageServer

                return FortranLanguageServer
            case self.HASKELL:
                from solidlsp.language_servers.haskell_language_server import HaskellLanguageServer

                return HaskellLanguageServer
            case self.HAXE:
                from solidlsp.language_servers.haxe_language_server import HaxeLanguageServer

                return HaxeLanguageServer
            case self.LEAN4:
                from solidlsp.language_servers.lean4_language_server import Lean4LanguageServer

                return Lean4LanguageServer
            case self.FSHARP:
                from solidlsp.language_servers.fsharp_language_server import FSharpLanguageServer

                return FSharpLanguageServer
            case self.POWERSHELL:
                from solidlsp.language_servers.powershell_language_server import PowerShellLanguageServer

                return PowerShellLanguageServer
            case self.PASCAL:
                from solidlsp.language_servers.pascal_server import PascalLanguageServer

                return PascalLanguageServer
            case self.GROOVY:
                from solidlsp.language_servers.groovy_language_server import GroovyLanguageServer

                return GroovyLanguageServer
            case self.MATLAB:
                from solidlsp.language_servers.matlab_language_server import MatlabLanguageServer

                return MatlabLanguageServer
            case self.HLSL:
                from solidlsp.language_servers.hlsl_language_server import HlslLanguageServer

                return HlslLanguageServer
            case self.SYSTEMVERILOG:
                from solidlsp.language_servers.systemverilog_server import SystemVerilogLanguageServer

                return SystemVerilogLanguageServer
            case self.SOLIDITY:
                from solidlsp.language_servers.solidity_language_server import SolidityLanguageServer

                return SolidityLanguageServer
            case self.ANSIBLE:
                from solidlsp.language_servers.ansible_language_server import AnsibleLanguageServer

                return AnsibleLanguageServer
            case self.MSL:
                from solidlsp.language_servers.msl_language_server import MslLanguageServer

                return MslLanguageServer
            case self.BSL:
                from solidlsp.language_servers.bsl_language_server import BSLLanguageServer

                return BSLLanguageServer
            case self.ADA:
                from solidlsp.language_servers.ada_language_server import AdaLanguageServer

                return AdaLanguageServer
            case self.GDSCRIPT:
                from solidlsp.language_servers.godot_language_server import GodotLanguageServer

                return GodotLanguageServer
            case self.QML:
                from solidlsp.language_servers.qml_language_server import QmlLanguageServer

                return QmlLanguageServer
            case self.HTML:
                from solidlsp.language_servers.vscode_html_language_server import VsCodeHtmlLanguageServer

                return VsCodeHtmlLanguageServer
            case self.SCSS:
                from solidlsp.language_servers.some_sass_language_server import SomeSassLanguageServer

                return SomeSassLanguageServer
            case self.ANGULAR:
                from solidlsp.language_servers.angular_language_server import AngularLanguageServer

                return AngularLanguageServer
            case _:
                raise ValueError(f"Unhandled language: {self}")

    @classmethod
    def from_ls_class(cls, ls_class: type["SolidLanguageServer"]) -> Self:
        """
        Get the Language enum value from a SolidLanguageServer class.

        :param ls_class: The SolidLanguageServer class to find the corresponding Language for
        :return: The Language enum value
        :raises ValueError: If the language server class is not supported
        """
        for enum_instance in cls:
            if enum_instance.get_ls_class() == ls_class:
                return enum_instance
        raise ValueError(f"Unhandled language server class: {ls_class}")


@dataclass(frozen=True)
class LanguageServerConfig:
    """
    Configuration parameters for a language server instance
    """

    code_language: Language
    """
    defines the language server to use
    """
    workspace_folders: list[str] = field(default_factory=lambda: ["."])
    """
    list of workspace folders to be used by the language server and to be fully indexed by SolidLSP.
    Paths can either be absolute or relative to the project root.
    These folders must be descendants of the project root.
    """
    additional_workspace_folders: list[str] = field(default_factory=list)
    """
    list of additional workspace folders to be passed to the language server, but which are not to be indexed by SolidLSP. 
    Paths can either be absolute or relative to the project root.
    These folders can potentially be outside of the project root, e.g. for cross-package reference support.
    """
    trace_lsp_communication: bool = False
    start_independent_lsp_process: bool = True
    ignored_paths: list[str] = field(default_factory=list)
    """Paths, dirs or glob-like patterns. The matching will follow the same logic as for .gitignore entries"""
    encoding: str = "utf-8"
    """File encoding to use when reading source files"""

    @classmethod
    def from_dict(cls, env: dict) -> Self:
        import inspect

        return cls(**{k: v for k, v in env.items() if k in inspect.signature(cls).parameters})

    @staticmethod
    def _absolute_workspace_folders(folders: list[str], project_root: str) -> list[str]:
        abs_workspace_folders = []
        for path in folders:
            if os.path.isabs(path):
                abs_path = str(Path(path).resolve())
            else:
                abs_path = os.path.realpath(os.path.join(project_root, path))
            if not os.path.exists(abs_path):
                log.error("Workspace folder does not exist: %s; skipping", abs_path)
                continue
            if abs_path in abs_workspace_folders:
                log.warning("Duplicate workspace folder: %s; skipping", abs_path)
                continue
            abs_workspace_folders.append(abs_path)
        return abs_workspace_folders

    def get_absolute_workspace_folders(self, project_root: str) -> list[str]:
        """
        Get the absolute paths of the workspace folders, resolving relative paths against the project root.

        :param project_root: The root path of the project
        :return: List of absolute workspace folder paths
        """
        return self._absolute_workspace_folders(self.workspace_folders, project_root)

    def get_absolute_additional_workspace_folders(self, project_root: str) -> list[str]:
        """
        Get the absolute paths of the additional workspace folders, resolving relative paths against the project root.

        :param project_root: The root path of the project
        :return: List of absolute additional workspace folder paths
        """
        return self._absolute_workspace_folders(self.additional_workspace_folders, project_root)
