"""
This is an alternative to clangd for large C++ codebases where ccls may perform
better for indexing and navigation. Requires ccls to be installed and available
on PATH, or configured via ls_specific_settings with key "ls_path".

Installation
------------
ccls must be installed manually as there are no prebuilt binaries available for
direct download. Install using your system package manager:

**Linux:**
- Ubuntu/Debian (22.04+): ``sudo apt-get install ccls``
- Fedora/RHEL: ``sudo dnf install ccls``
- Arch Linux: ``sudo pacman -S ccls``
- openSUSE Tumbleweed: ``sudo zypper install ccls``
- Gentoo: ``sudo emerge dev-util/ccls``

**macOS:**
- Homebrew: ``brew install ccls``

**Windows:**
- Chocolatey: ``choco install ccls``

For alternative installation methods and build-from-source instructions, see:
https://github.com/MaskRay/ccls/wiki/Build

Official documentation:
https://github.com/MaskRay/ccls
"""

import logging
import os
import pathlib
import threading
from typing import Any, cast

from overrides import override

from solidlsp.language_servers.common import UE_IGNORED_DIRNAMES, is_unreal_engine_project
from solidlsp.ls import (
    LanguageServerDependencyProvider,
    LanguageServerDependencyProviderSinglePath,
    SolidLanguageServer,
)
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.settings import SolidLSPSettings

log = logging.getLogger(__name__)


class CCLS(SolidLanguageServer):
    """
    C/C++ language server implementation using ccls.

    Notes:
    - ccls should be installed and on PATH (or specify ls_path in settings)
    - compile_commands.json at repo root is recommended for accurate indexing

    """

    def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
        """
        Creates a CclsLanguageServer instance. This class is not meant to be instantiated directly.
        Use LanguageServer.create() instead.
        """
        super().__init__(config, repository_root_path, None, "cpp", solidlsp_settings)
        self.server_ready = threading.Event()

    def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
        return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)

    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        return (
            super().is_ignored_dirname(dirname)
            or dirname == ".ccls-cache"
            or (is_unreal_engine_project(self.repository_root_path) and dirname in UE_IGNORED_DIRNAMES)
        )

    class DependencyProvider(LanguageServerDependencyProviderSinglePath):
        def _get_or_install_core_dependency(self) -> str:
            """
            Resolve ccls path from system or raise helpful error if missing.
            Allows override via ls_specific_settings[language].ls_path.
            """
            import shutil

            ccls_path = shutil.which("ccls")
            if not ccls_path:
                raise FileNotFoundError(
                    "ccls is not installed on your system.\n"
                    "Please install ccls using your system package manager:\n"
                    "  Linux (Ubuntu/Debian): sudo apt-get install ccls\n"
                    "  Linux (Fedora/RHEL):   sudo dnf install ccls\n"
                    "  Linux (Arch):          sudo pacman -S ccls\n"
                    "  macOS (Homebrew):      brew install ccls\n"
                    "  Windows:               choco install ccls\n\n"
                    "For build instructions and more details, see:\n"
                    "  https://github.com/MaskRay/ccls/wiki/Build"
                )
            log.info(f"Using system-installed ccls at {ccls_path}")
            return ccls_path

        def _create_launch_command(self, core_path: str) -> list[str]:
            return [core_path]

    @staticmethod
    def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
        """
        Returns the initialize params for the ccls Language Server.
        """
        root_uri = pathlib.Path(repository_absolute_path).as_uri()
        initialize_params = {
            "locale": "en",
            "capabilities": {
                "textDocument": {
                    "synchronization": {"didSave": True, "dynamicRegistration": True},
                    "completion": {"dynamicRegistration": True, "completionItem": {"snippetSupport": True}},
                    "definition": {"dynamicRegistration": True},
                    "references": {"dynamicRegistration": True},
                    "documentSymbol": {"dynamicRegistration": True},
                },
                "workspace": {"workspaceFolders": True, "didChangeConfiguration": {"dynamicRegistration": True}},
            },
            "processId": os.getpid(),
            "rootPath": repository_absolute_path,
            "rootUri": root_uri,
            "workspaceFolders": [
                {
                    "uri": root_uri,
                    "name": os.path.basename(repository_absolute_path),
                }
            ],
            # ccls supports initializationOptions but none are required for basic functionality
        }
        return cast(InitializeParams, initialize_params)

    def _start_server(self) -> None:
        """
        Starts the ccls language server and initializes the LSP connection.
        """

        def do_nothing(params: Any) -> None:
            pass

        def window_log_message(msg: dict) -> None:
            log.info(f"LSP: window/logMessage: {msg}")

        # Register minimal handlers
        self.server.on_notification("window/logMessage", window_log_message)
        self.server.on_notification("$/progress", do_nothing)
        self.server.on_notification("textDocument/publishDiagnostics", do_nothing)

        log.info("Starting ccls server process")
        self.server.start()
        initialize_params = self._get_initialize_params(self.repository_root_path)

        log.info("Sending initialize request from LSP client to ccls and awaiting response")
        self.server.send.initialize(initialize_params)
        # Do not assert clangd-specific capability shapes; ccls differs
        self.server.notify.initialized({})

        # Basic readiness
        self.server_ready.set()
