"""
VB.NET Language Server using Microsoft.CodeAnalysis.LanguageServer (Official Roslyn-based LSP server)
"""

import json
import logging
import os
import platform
import shutil
import subprocess
import tarfile
import threading
import urllib.request
import zipfile
from collections.abc import Iterable
from pathlib import Path
from typing import Any, cast

from overrides import override

from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_utils import PathUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams, InitializeResult
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
from solidlsp.util.zip import SafeZipExtractor

from .common import RuntimeDependency, RuntimeDependencyCollection

log = logging.getLogger(__name__)

_RUNTIME_DEPENDENCIES = [
    RuntimeDependency(
        id="VBNetLanguageServer",
        description="Microsoft.CodeAnalysis.LanguageServer for Windows (x64)",
        package_name="Microsoft.CodeAnalysis.LanguageServer.win-x64",
        package_version="5.0.0-1.25329.6",
        platform_id="win-x64",
        archive_type="nupkg",
        binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
        extract_path="content/LanguageServer/win-x64",
    ),
    RuntimeDependency(
        id="VBNetLanguageServer",
        description="Microsoft.CodeAnalysis.LanguageServer for Windows (ARM64)",
        package_name="Microsoft.CodeAnalysis.LanguageServer.win-arm64",
        package_version="5.0.0-1.25329.6",
        platform_id="win-arm64",
        archive_type="nupkg",
        binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
        extract_path="content/LanguageServer/win-arm64",
    ),
    RuntimeDependency(
        id="VBNetLanguageServer",
        description="Microsoft.CodeAnalysis.LanguageServer for macOS (x64)",
        package_name="Microsoft.CodeAnalysis.LanguageServer.osx-x64",
        package_version="5.0.0-1.25329.6",
        platform_id="osx-x64",
        archive_type="nupkg",
        binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
        extract_path="content/LanguageServer/osx-x64",
    ),
    RuntimeDependency(
        id="VBNetLanguageServer",
        description="Microsoft.CodeAnalysis.LanguageServer for macOS (ARM64)",
        package_name="Microsoft.CodeAnalysis.LanguageServer.osx-arm64",
        package_version="5.0.0-1.25329.6",
        platform_id="osx-arm64",
        archive_type="nupkg",
        binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
        extract_path="content/LanguageServer/osx-arm64",
    ),
    RuntimeDependency(
        id="VBNetLanguageServer",
        description="Microsoft.CodeAnalysis.LanguageServer for Linux (x64)",
        package_name="Microsoft.CodeAnalysis.LanguageServer.linux-x64",
        package_version="5.0.0-1.25329.6",
        platform_id="linux-x64",
        archive_type="nupkg",
        binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
        extract_path="content/LanguageServer/linux-x64",
    ),
    RuntimeDependency(
        id="VBNetLanguageServer",
        description="Microsoft.CodeAnalysis.LanguageServer for Linux (ARM64)",
        package_name="Microsoft.CodeAnalysis.LanguageServer.linux-arm64",
        package_version="5.0.0-1.25329.6",
        platform_id="linux-arm64",
        archive_type="nupkg",
        binary_name="Microsoft.CodeAnalysis.LanguageServer.dll",
        extract_path="content/LanguageServer/linux-arm64",
    ),
    RuntimeDependency(
        id="DotNetRuntime",
        description=".NET 8 Runtime for Windows (x64)",
        url="https://download.visualstudio.microsoft.com/download/pr/7f3a766e-9516-4579-aaf2-2b150caa465c/d57665f880cdcce816b278a944092b90/dotnet-runtime-8.0.11-win-x64.zip",
        platform_id="win-x64",
        archive_type="zip",
        binary_name="dotnet.exe",
    ),
    RuntimeDependency(
        id="DotNetRuntime",
        description=".NET 8 Runtime for Linux (x64)",
        url="https://download.visualstudio.microsoft.com/download/pr/68c87f8a-862c-4870-a792-9c89b3c8aa2d/2319ebfb46d3a903341966586e8b0898/dotnet-runtime-8.0.11-linux-x64.tar.gz",
        platform_id="linux-x64",
        archive_type="tar.gz",
        binary_name="dotnet",
    ),
    RuntimeDependency(
        id="DotNetRuntime",
        description=".NET 8 Runtime for Linux (ARM64)",
        url="https://download.visualstudio.microsoft.com/download/pr/5ebc1a83-e6e0-4c4b-93c9-e0e9055a9a3c/4a2b74f66a6d5dce1b16dc7a3c4b5edb/dotnet-runtime-8.0.11-linux-arm64.tar.gz",
        platform_id="linux-arm64",
        archive_type="tar.gz",
        binary_name="dotnet",
    ),
    RuntimeDependency(
        id="DotNetRuntime",
        description=".NET 8 Runtime for macOS (x64)",
        url="https://download.visualstudio.microsoft.com/download/pr/31e3c8ba-6b3c-46f2-8b4a-ddc4dc0e4e07/ba278d9305c23cbdc58b1e8f15467261/dotnet-runtime-8.0.11-osx-x64.tar.gz",
        platform_id="osx-x64",
        archive_type="tar.gz",
        binary_name="dotnet",
    ),
    RuntimeDependency(
        id="DotNetRuntime",
        description=".NET 8 Runtime for macOS (ARM64)",
        url="https://download.visualstudio.microsoft.com/download/pr/cc021630-e2e0-4587-b904-81c98d3a2303/25b5a12e4dd2d3a313b91c72bf7ad3c7/dotnet-runtime-8.0.11-osx-arm64.tar.gz",
        platform_id="osx-arm64",
        archive_type="tar.gz",
        binary_name="dotnet",
    ),
]


def breadth_first_file_scan(root_dir: str) -> Iterable[str]:
    """
    Perform a breadth-first scan of files in the given directory.
    Yields file paths in breadth-first order.
    """
    queue = [root_dir]
    while queue:
        current_dir = queue.pop(0)
        try:
            for item in os.listdir(current_dir):
                if item.startswith("."):
                    continue
                item_path = os.path.join(current_dir, item)
                if os.path.isdir(item_path):
                    queue.append(item_path)
                elif os.path.isfile(item_path):
                    yield item_path
        except (PermissionError, OSError):
            # Skip directories we can't access
            pass


def find_solution_or_project_file(root_dir: str) -> str | None:
    """
    Find the first .sln file in breadth-first order.
    If no .sln file is found, look for a .vbproj file.
    """
    sln_file = None
    vbproj_file = None

    for filename in breadth_first_file_scan(root_dir):
        if filename.endswith(".sln") and sln_file is None:
            sln_file = filename
        elif filename.endswith(".vbproj") and vbproj_file is None:
            vbproj_file = filename

        # If we found a .sln file, return it immediately
        if sln_file:
            return sln_file

    # If no .sln file was found, return the first .vbproj file
    return vbproj_file


class VBNetLanguageServer(SolidLanguageServer):
    """
    Provides VB.NET specific instantiation of the LanguageServer class using `Microsoft.CodeAnalysis.LanguageServer`,
    the official Roslyn-based language server from Microsoft.

    You can pass a list of runtime dependency overrides in ls_specific_settings["vbnet"]. This is a list of
    dicts, each containing at least the "id" key, and optionally "platform_id" to uniquely identify the dependency to override.
    For example, to override the URL of the .NET runtime on windows-x64, add the entry:

    ```
        {
            "id": "DotNetRuntime",
            "platform_id": "win-x64",
            "url": "https://example.com/custom-dotnet-runtime.zip"
        }
    ```

    See the `_RUNTIME_DEPENDENCIES` variable above for the available dependency ids and platform_ids.
    """

    def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
        """
        Creates a VBNetLanguageServer instance. This class is not meant to be instantiated directly.
        Use LanguageServer.create() instead.
        """
        dotnet_path, language_server_path = self._ensure_server_installed(config, solidlsp_settings)

        # Find solution or project file
        solution_or_project = find_solution_or_project_file(repository_root_path)

        # Create log directory
        log_dir = Path(self.ls_resources_dir(solidlsp_settings)) / "logs"
        log_dir.mkdir(parents=True, exist_ok=True)

        # Build command using dotnet directly
        cmd = [dotnet_path, language_server_path, "--logLevel=Information", f"--extensionLogDirectory={log_dir}", "--stdio"]

        # The language server will discover the solution/project from the workspace root
        if solution_or_project:
            log.info(f"Found solution/project file: {solution_or_project}")
        else:
            log.warning("No .sln or .vbproj file found, language server will attempt auto-discovery")

        log.debug(f"Language server command: {' '.join(cmd)}")

        # Use "vbnet" as the language ID for Serena (LSP protocol uses "vb" internally in Roslyn)
        super().__init__(config, repository_root_path, ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path), "vbnet", solidlsp_settings)

        self.initialization_complete = threading.Event()

    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        return super().is_ignored_dirname(dirname) or dirname in ["bin", "obj", "packages", ".vs"]

    @classmethod
    def _ensure_server_installed(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> tuple[str, str]:
        """
        Ensure .NET runtime and Microsoft.CodeAnalysis.LanguageServer are available.
        Returns a tuple of (dotnet_path, language_server_dll_path).
        """
        # Handle older versions of Serena that don't have get_ls_specific_settings
        try:
            language_specific_config = solidlsp_settings.get_ls_specific_settings(cls.get_language_enum_instance())
        except AttributeError:
            # Fallback for older Serena versions without get_ls_specific_settings
            language_specific_config = {}

        runtime_dependency_overrides = cast(list[dict[str, Any]], language_specific_config.get("runtime_dependencies", []))

        log.debug("Resolving runtime dependencies")

        # Handle older versions of Serena where RuntimeDependencyCollection doesn't support overrides
        try:
            runtime_dependencies = RuntimeDependencyCollection(
                _RUNTIME_DEPENDENCIES,
                overrides=runtime_dependency_overrides,
            )
        except TypeError:
            # Fallback for older Serena versions - just use base dependencies
            log.debug("RuntimeDependencyCollection doesn't support overrides, using base dependencies only")
            runtime_dependencies = RuntimeDependencyCollection(_RUNTIME_DEPENDENCIES)

        log.debug(
            f"Available runtime dependencies: {runtime_dependencies.get_dependencies_for_current_platform}",
        )

        # Find the dependencies for our platform
        lang_server_dep = runtime_dependencies.get_single_dep_for_current_platform("VBNetLanguageServer")
        dotnet_runtime_dep = runtime_dependencies.get_single_dep_for_current_platform("DotNetRuntime")
        dotnet_path = VBNetLanguageServer._ensure_dotnet_runtime(dotnet_runtime_dep, solidlsp_settings)
        server_dll_path = VBNetLanguageServer._ensure_language_server(lang_server_dep, solidlsp_settings)

        return dotnet_path, server_dll_path

    @classmethod
    def _ensure_dotnet_runtime(cls, dotnet_runtime_dep: RuntimeDependency, solidlsp_settings: SolidLSPSettings) -> str:
        """Ensure .NET runtime is available and return the dotnet executable path."""
        # TODO: use RuntimeDependency util methods instead of custom validation/download logic

        # First, check for bundled .NET runtime with custom binary
        custom_dotnet_dir = Path.home() / ".serena" / "language_servers" / "roslyn_vbnet" / "dotnet"
        custom_dotnet = custom_dotnet_dir / "dotnet"
        if custom_dotnet.exists():
            log.info(f"Using bundled .NET runtime from {custom_dotnet}")
            return str(custom_dotnet)

        # Check if dotnet is already available on the system
        system_dotnet = shutil.which("dotnet")
        if system_dotnet:
            # Check if it's .NET 8 or later
            try:
                result = subprocess.run([system_dotnet, "--list-runtimes"], capture_output=True, text=True, check=True)
                if "Microsoft.NETCore.App 8." in result.stdout:
                    log.info("Found system .NET 8 runtime")
                    return system_dotnet
                elif "Microsoft.NETCore.App 9." in result.stdout:
                    log.info("Found system .NET 9 runtime")
                    return system_dotnet
                elif "Microsoft.NETCore.App 10." in result.stdout:
                    log.info("Found system .NET 10 runtime")
                    return system_dotnet
            except subprocess.CalledProcessError:
                pass

        # Download .NET 8 runtime using config
        return cls._ensure_dotnet_runtime_from_config(dotnet_runtime_dep, solidlsp_settings)

    @classmethod
    def _ensure_language_server(cls, lang_server_dep: RuntimeDependency, solidlsp_settings: SolidLSPSettings) -> str:
        """Ensure language server is available and return the DLL path."""

        # First, check for custom-built Roslyn LS with VB.NET support
        custom_ls_dir = Path.home() / ".serena" / "language_servers" / "roslyn_vbnet"
        custom_ls_dll = custom_ls_dir / "Microsoft.CodeAnalysis.LanguageServer.dll"

        if custom_ls_dll.exists():
            log.info(f"Using custom Roslyn LanguageServer with VB.NET support from {custom_ls_dll}")
            # Verify VB.NET assemblies are present
            vb_assemblies = [
                "Microsoft.CodeAnalysis.VisualBasic.dll",
                "Microsoft.CodeAnalysis.VisualBasic.Features.dll",
                "Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll"
            ]
            for assembly in vb_assemblies:
                if not (custom_ls_dir / assembly).exists():
                    raise SolidLSPException(
                        f"Custom LanguageServer found but missing VB.NET assembly: {assembly}\n"
                        f"Please rebuild the custom binary using: ./build-roslyn-vbnet.sh"
                    )
            return str(custom_ls_dll)

        # If no custom binary, raise error with instructions
        raise SolidLSPException(
            "VB.NET language server requires a custom-built Roslyn LanguageServer binary.\n"
            "The distributed NuGet packages do not include VB.NET support.\n\n"
            "To build and install:\n"
            "1. Run: ./build-roslyn-vbnet.sh\n"
            "2. Run: ./install-vbnet-ls.sh\n"
            "Or manually copy to: ~/.serena/language_servers/roslyn_vbnet/\n\n"
            f"Expected location: {custom_ls_dll}"
        )

        # OLD CODE (no longer used, but kept for reference):
        package_name = lang_server_dep.package_name
        package_version = lang_server_dep.package_version

        server_dir = Path(cls.ls_resources_dir(solidlsp_settings)) / f"{package_name}.{package_version}"
        assert lang_server_dep.binary_name is not None
        server_dll = server_dir / lang_server_dep.binary_name

        if server_dll.exists():
            log.info(f"Using cached Microsoft.CodeAnalysis.LanguageServer from {server_dll}")
            return str(server_dll)

        # Download and install the language server
        log.info(f"Downloading {package_name} version {package_version}...")
        assert package_version is not None
        assert package_name is not None
        package_path = cls._download_nuget_package_direct(package_name, package_version, solidlsp_settings)

        # Extract and install
        cls._extract_language_server(lang_server_dep, package_path, server_dir)

        if not server_dll.exists():
            raise SolidLSPException("Microsoft.CodeAnalysis.LanguageServer.dll not found after extraction")

        # Make executable on Unix systems
        if platform.system().lower() != "windows":
            server_dll.chmod(0o755)

        log.info(f"Successfully installed Microsoft.CodeAnalysis.LanguageServer to {server_dll}")
        return str(server_dll)

    @staticmethod
    def _extract_language_server(lang_server_dep: RuntimeDependency, package_path: Path, server_dir: Path) -> None:
        """Extract language server files from downloaded package."""
        extract_path = lang_server_dep.extract_path or "lib/net9.0"
        source_dir = package_path / extract_path

        if not source_dir.exists():
            # Try alternative locations
            for possible_dir in [
                package_path / "tools" / "net9.0" / "any",
                package_path / "lib" / "net9.0",
                package_path / "contentFiles" / "any" / "net9.0",
            ]:
                if possible_dir.exists():
                    source_dir = possible_dir
                    break
            else:
                raise SolidLSPException(f"Could not find language server files in package. Searched in {package_path}")

        # Copy files to cache directory
        server_dir.mkdir(parents=True, exist_ok=True)
        shutil.copytree(source_dir, server_dir, dirs_exist_ok=True)

    @classmethod
    def _download_nuget_package_direct(cls, package_name: str, package_version: str, solidlsp_settings: SolidLSPSettings) -> Path:
        """
        Download a NuGet package directly from the Azure NuGet feed.
        Returns the path to the extracted package directory.
        """
        azure_feed_url = "https://pkgs.dev.azure.com/azure-public/vside/_packaging/vs-impl/nuget/v3/index.json"

        # Create temporary directory for package download
        temp_dir = Path(cls.ls_resources_dir(solidlsp_settings)) / "temp_downloads"
        temp_dir.mkdir(parents=True, exist_ok=True)

        try:
            # First, get the service index from the Azure feed
            log.debug("Fetching NuGet service index from Azure feed...")
            with urllib.request.urlopen(azure_feed_url) as response:
                service_index = json.loads(response.read().decode())

            # Find the package base address (for downloading packages)
            package_base_address = None
            for resource in service_index.get("resources", []):
                if resource.get("@type") == "PackageBaseAddress/3.0.0":
                    package_base_address = resource.get("@id")
                    break

            if not package_base_address:
                raise SolidLSPException("Could not find package base address in Azure NuGet feed")

            # Construct the download URL for the specific package
            package_id_lower = package_name.lower()
            package_version_lower = package_version.lower()
            package_url = f"{package_base_address.rstrip('/')}/{package_id_lower}/{package_version_lower}/{package_id_lower}.{package_version_lower}.nupkg"

            log.debug(f"Downloading package from: {package_url}")

            # Download the .nupkg file
            nupkg_file = temp_dir / f"{package_name}.{package_version}.nupkg"
            urllib.request.urlretrieve(package_url, nupkg_file)

            # Extract the .nupkg file (it's just a zip file)
            package_extract_dir = temp_dir / f"{package_name}.{package_version}"
            package_extract_dir.mkdir(exist_ok=True)

            # Use SafeZipExtractor to handle long paths and skip errors
            extractor = SafeZipExtractor(archive_path=nupkg_file, extract_dir=package_extract_dir, verbose=False)
            extractor.extract_all()

            # Clean up the nupkg file
            nupkg_file.unlink()

            log.info(f"Successfully downloaded and extracted {package_name} version {package_version}")
            return package_extract_dir

        except Exception as e:
            raise SolidLSPException(
                f"Failed to download package {package_name} version {package_version} from Azure NuGet feed: {e}"
            ) from e

    @classmethod
    def _ensure_dotnet_runtime_from_config(cls, dotnet_runtime_dep: RuntimeDependency, solidlsp_settings: SolidLSPSettings) -> str:
        """
        Ensure .NET 8 runtime is available using runtime dependency configuration.
        Returns the path to the dotnet executable.
        """
        # TODO: use RuntimeDependency util methods instead of custom download logic

        # Check if dotnet is already available on the system
        system_dotnet = shutil.which("dotnet")
        if system_dotnet:
            # Check if it's .NET 8 or later
            try:
                result = subprocess.run([system_dotnet, "--list-runtimes"], capture_output=True, text=True, check=True)
                if "Microsoft.NETCore.App 8." in result.stdout:
                    log.info("Found system .NET 8 runtime")
                    return system_dotnet
                elif "Microsoft.NETCore.App 9." in result.stdout:
                    log.info("Found system .NET 9 runtime")
                    return system_dotnet
                elif "Microsoft.NETCore.App 10." in result.stdout:
                    log.info("Found system .NET 10 runtime")
                    return system_dotnet
            except subprocess.CalledProcessError:
                pass

        # Download .NET 8 runtime using config
        dotnet_dir = Path(cls.ls_resources_dir(solidlsp_settings)) / "dotnet-runtime-8.0"
        assert dotnet_runtime_dep.binary_name is not None, "Runtime dependency must have a binary_name"
        dotnet_exe = dotnet_dir / dotnet_runtime_dep.binary_name

        if dotnet_exe.exists():
            log.info(f"Using cached .NET runtime from {dotnet_exe}")
            return str(dotnet_exe)

        # Download .NET runtime
        log.info("Downloading .NET 8 runtime...")
        dotnet_dir.mkdir(parents=True, exist_ok=True)

        # Handle older versions of Serena that don't have get_ls_specific_settings
        try:
            custom_settings = solidlsp_settings.get_ls_specific_settings(cls.get_language_enum_instance())
        except AttributeError:
            custom_settings = {}

        custom_dotnet_runtime_url = custom_settings.get("dotnet_runtime_url")
        if custom_dotnet_runtime_url is not None:
            log.info(f"Using custom .NET runtime url: {custom_dotnet_runtime_url}")
            url = custom_dotnet_runtime_url
        else:
            url = dotnet_runtime_dep.url

        archive_type = dotnet_runtime_dep.archive_type

        # Download the runtime
        download_path = dotnet_dir / f"dotnet-runtime.{archive_type}"
        try:
            log.debug(f"Downloading from {url}")
            urllib.request.urlretrieve(url, download_path)

            # Extract the archive
            if archive_type == "zip":
                with zipfile.ZipFile(download_path, "r") as zip_ref:
                    zip_ref.extractall(dotnet_dir)
            else:
                # tar.gz
                with tarfile.open(download_path, "r:gz") as tar_ref:
                    tar_ref.extractall(dotnet_dir)

            # Remove the archive
            download_path.unlink()

            # Make dotnet executable on Unix
            if platform.system().lower() != "windows":
                dotnet_exe.chmod(0o755)

            log.info(f"Successfully installed .NET 8 runtime to {dotnet_exe}")
            return str(dotnet_exe)

        except Exception as e:
            raise SolidLSPException(f"Failed to download .NET 8 runtime from {url}: {e}") from e

    def _get_initialize_params(self) -> InitializeParams:
        """
        Returns the initialize params for the Microsoft.CodeAnalysis.LanguageServer.
        """
        root_uri = PathUtils.path_to_uri(self.repository_root_path)
        root_name = os.path.basename(self.repository_root_path)
        return cast(
            InitializeParams,
            {
                "workspaceFolders": [{"uri": root_uri, "name": root_name}],
                "processId": os.getpid(),
                "rootPath": self.repository_root_path,
                "rootUri": root_uri,
                "capabilities": {
                    "window": {
                        "workDoneProgress": True,
                        "showMessage": {"messageActionItem": {"additionalPropertiesSupport": True}},
                        "showDocument": {"support": True},
                    },
                    "workspace": {
                        "applyEdit": True,
                        "workspaceEdit": {"documentChanges": True},
                        "didChangeConfiguration": {"dynamicRegistration": True},
                        "didChangeWatchedFiles": {"dynamicRegistration": True},
                        "symbol": {
                            "dynamicRegistration": True,
                            "symbolKind": {"valueSet": list(range(1, 27))},
                        },
                        "executeCommand": {"dynamicRegistration": True},
                        "configuration": True,
                        "workspaceFolders": True,
                        "workDoneProgress": True,
                    },
                    "textDocument": {
                        "synchronization": {"dynamicRegistration": True, "willSave": True, "willSaveWaitUntil": True, "didSave": True},
                        "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
                        "signatureHelp": {
                            "dynamicRegistration": True,
                            "signatureInformation": {
                                "documentationFormat": ["markdown", "plaintext"],
                                "parameterInformation": {"labelOffsetSupport": True},
                            },
                        },
                        "definition": {"dynamicRegistration": True},
                        "references": {"dynamicRegistration": True},
                        "documentSymbol": {
                            "dynamicRegistration": True,
                            "symbolKind": {"valueSet": list(range(1, 27))},
                            "hierarchicalDocumentSymbolSupport": True,
                        },
                    },
                },
            },
        )

    def _start_server(self) -> None:
        def do_nothing(params: dict) -> None:
            return

        def window_log_message(msg: dict) -> None:
            """Log messages from the language server."""
            message_text = msg.get("message", "")
            level = msg.get("type", 4)  # Default to Log level

            # Map LSP message types to Python logging levels
            level_map = {1: logging.ERROR, 2: logging.WARNING, 3: logging.INFO, 4: logging.DEBUG}  # Error  # Warning  # Info  # Log

            log.log(level_map.get(level, logging.DEBUG), f"LSP: {message_text}")

        def handle_progress(params: dict) -> None:
            """Handle progress notifications from the language server."""
            token = params.get("token", "")
            value = params.get("value", {})

            # Log raw progress for debugging
            log.debug(f"Progress notification received: {params}")

            # Handle different progress notification types
            kind = value.get("kind")

            if kind == "begin":
                title = value.get("title", "Operation in progress")
                message = value.get("message", "")
                percentage = value.get("percentage")

                if percentage is not None:
                    log.debug(f"Progress [{token}]: {title} - {message} ({percentage}%)")
                else:
                    log.info(f"Progress [{token}]: {title} - {message}")

            elif kind == "report":
                message = value.get("message", "")
                percentage = value.get("percentage")

                if percentage is not None:
                    log.info(f"Progress [{token}]: {message} ({percentage}%)")
                elif message:
                    log.info(f"Progress [{token}]: {message}")

            elif kind == "end":
                message = value.get("message", "Operation completed")
                log.info(f"Progress [{token}]: {message}")

        def handle_workspace_configuration(params: dict) -> list:
            """Handle workspace/configuration requests from the server."""
            items = params.get("items", [])
            result: list[Any] = []

            for item in items:
                section = item.get("section", "")

                # Provide default values based on the configuration section
                # VB.NET uses similar dotnet/vb configuration sections like C#
                if section.startswith(("dotnet", "vb", "visualbasic")):
                    # Default configuration for VB.NET settings
                    if "enable" in section or "show" in section or "suppress" in section or "navigate" in section:
                        # Boolean settings
                        result.append(False)
                    elif "scope" in section:
                        # Scope settings - use appropriate enum values
                        if "analyzer_diagnostics_scope" in section:
                            result.append("openFiles")  # BackgroundAnalysisScope
                        elif "compiler_diagnostics_scope" in section:
                            result.append("openFiles")  # CompilerDiagnosticsScope
                        else:
                            result.append("openFiles")
                    elif section == "dotnet_member_insertion_location":
                        # ImplementTypeInsertionBehavior enum
                        result.append("with_other_members_of_the_same_kind")
                    elif section == "dotnet_property_generation_behavior":
                        # ImplementTypePropertyGenerationBehavior enum
                        result.append("prefer_throwing_properties")
                    elif "location" in section or "behavior" in section:
                        # Other enum settings - return null to avoid parsing errors
                        result.append(None)
                    else:
                        # Default for other dotnet/vb settings
                        result.append(None)
                elif section == "tab_width" or section == "indent_size":
                    # Tab and indent settings
                    result.append(4)
                elif section == "insert_final_newline":
                    # Editor settings
                    result.append(True)
                else:
                    # Unknown configuration - return null
                    result.append(None)

            return result

        def handle_work_done_progress_create(params: dict) -> None:
            """Handle work done progress create requests."""
            # Just acknowledge the request
            return

        def handle_register_capability(params: dict) -> None:
            """Handle client/registerCapability requests."""
            # Just acknowledge the request - we don't need to track these for now
            return

        def handle_project_needs_restore(params: dict) -> None:
            return

        def handle_workspace_indexing_complete(params: dict) -> None:
            self.completions_available.set()

        # Set up notification handlers
        self.server.on_notification("window/logMessage", window_log_message)
        self.server.on_notification("$/progress", handle_progress)
        self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
        self.server.on_notification("workspace/projectInitializationComplete", handle_workspace_indexing_complete)
        self.server.on_request("workspace/configuration", handle_workspace_configuration)
        self.server.on_request("window/workDoneProgress/create", handle_work_done_progress_create)
        self.server.on_request("client/registerCapability", handle_register_capability)
        self.server.on_request("workspace/_roslyn_projectNeedsRestore", handle_project_needs_restore)

        log.info("Starting Microsoft.CodeAnalysis.LanguageServer process for VB.NET")

        try:
            self.server.start()
        except Exception as e:
            log.info(f"Failed to start language server process: {e}", logging.ERROR)
            raise SolidLSPException(f"Failed to start VB.NET language server: {e}")

        # Send initialization
        initialize_params = self._get_initialize_params()

        log.info("Sending initialize request to language server")
        try:
            init_response = self.server.send.initialize(initialize_params)
            log.info(f"Received initialize response: {init_response}")
        except Exception as e:
            raise SolidLSPException(f"Failed to initialize VB.NET language server for {self.repository_root_path}: {e}") from e

        # Apply diagnostic capabilities
        self._force_pull_diagnostics(init_response)

        # Verify required capabilities
        capabilities = init_response.get("capabilities", {})
        required_capabilities = [
            "textDocumentSync",
            "definitionProvider",
            "referencesProvider",
            "documentSymbolProvider",
        ]
        missing = [cap for cap in required_capabilities if cap not in capabilities]
        if missing:
            raise RuntimeError(
                f"Language server is missing required capabilities: {', '.join(missing)}. "
                "Initialization failed. Please ensure the correct version of Microsoft.CodeAnalysis.LanguageServer is installed and the .NET runtime is working."
            )

        # Complete initialization
        self.server.notify.initialized({})

        # Open solution and project files
        self._open_solution_and_projects()

        self.initialization_complete.set()

        log.info(
            "Microsoft.CodeAnalysis.LanguageServer initialized and ready for VB.NET\n"
            "Waiting for language server to index project files...\n"
            "This may take a while for large projects"
        )

        if self.completions_available.wait(30):  # Wait up to 30 seconds for indexing
            log.info("Indexing complete")
        else:
            log.warning("Timeout waiting for indexing to complete, proceeding anyway")
            self.completions_available.set()

    def _force_pull_diagnostics(self, init_response: dict | InitializeResult) -> None:
        """
        Apply the diagnostic capabilities hack.
        Forces the server to support pull diagnostics.
        """
        capabilities = init_response.get("capabilities", {})
        diagnostic_provider: Any = capabilities.get("diagnosticProvider", {})

        # Add the diagnostic capabilities hack
        if isinstance(diagnostic_provider, dict):
            diagnostic_provider.update(
                {
                    "interFileDependencies": True,
                    "workDoneProgress": True,
                    "workspaceDiagnostics": True,
                }
            )
            log.debug("Applied diagnostic capabilities hack for better VB.NET diagnostics")

    def _open_solution_and_projects(self) -> None:
        """
        Open solution and project files using notifications.
        """
        # Find solution file
        solution_file = None
        for filename in breadth_first_file_scan(self.repository_root_path):
            if filename.endswith(".sln"):
                solution_file = filename
                break

        # Send solution/open notification if solution file found
        if solution_file:
            solution_uri = PathUtils.path_to_uri(solution_file)
            self.server.notify.send_notification("solution/open", {"solution": solution_uri})
            log.debug(f"Opened solution file: {solution_file}")

        # Find and open project files
        project_files = []
        for filename in breadth_first_file_scan(self.repository_root_path):
            if filename.endswith(".vbproj"):
                project_files.append(filename)

        # Send project/open notifications for each project file
        if project_files:
            project_uris = [PathUtils.path_to_uri(project_file) for project_file in project_files]
            self.server.notify.send_notification("project/open", {"projects": project_uris})
            log.debug(f"Opened project files: {project_files}")

    @override
    def _get_wait_time_for_cross_file_referencing(self) -> float:
        return 2
