import dataclasses
import hashlib
import logging
import os
import pathlib
import re
import shutil
import threading
import uuid
from pathlib import PurePath
from time import sleep
from typing import cast
from overrides import override
from solidlsp.language_servers._adapter_common import ensure_paths_exist
from solidlsp import ls_types
from solidlsp.ls import LanguageServerDependencyProvider, LSPFileBuffer, SolidLanguageServer
from solidlsp.ls import get_current_process_env_snapshot
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_types import UnifiedSymbolInformation
from solidlsp.ls_utils import FileUtils, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import DocumentSymbol, InitializeParams, SymbolInformation
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)


def _env_get(key: str, default: str = "") -> str:
    return str(get_current_process_env_snapshot().get(key, default))


def _project_ready_timeout_seconds() -> int:
    raw = _env_get("SARI_JDTLS_PROJECT_READY_TIMEOUT_SEC", "").strip()
    if raw == "":
        return 20
    try:
        return max(0, int(raw))
    except ValueError:
        return 20


def _startup_mode() -> str:
    raw = _env_get("SARI_JDTLS_STARTUP_MODE", "interactive").strip().lower()
    if raw in {"indexing", "interactive"}:
        return raw
    return "interactive"


def _service_ready_timeout_seconds() -> int:
    raw = _env_get("SARI_JDTLS_SERVICE_READY_TIMEOUT_SEC", "").strip()
    if raw != "":
        try:
            return max(0, int(raw))
        except ValueError:
            ...
    if _startup_mode() == "indexing":
        return 2
    return 0


def _intellicode_wait_timeout_seconds() -> int:
    raw = _env_get("SARI_JDTLS_INTELLICODE_WAIT_TIMEOUT_SEC", "").strip()
    if raw != "":
        try:
            return max(0, int(raw))
        except ValueError:
            ...
    if _startup_mode() == "indexing":
        return 1
    return 0


def _gradle_wrapper_first_enabled() -> bool:
    raw = _env_get("SARI_JDTLS_GRADLE_WRAPPER_FIRST", "").strip().lower()
    if raw in {"1", "true", "yes", "on"}:
        return True
    if raw in {"0", "false", "no", "off"}:
        return False
    return True


def _parse_gradle_wrapper_version(repository_root_path: str) -> tuple[int, int, int] | None:
    wrapper_props = pathlib.Path(repository_root_path) / "gradle" / "wrapper" / "gradle-wrapper.properties"
    if not wrapper_props.exists():
        return None
    try:
        text = wrapper_props.read_text(encoding="utf-8", errors="ignore")
    except OSError:
        return None
    match = re.search(r"distributionUrl=.*gradle-([0-9]+)\.([0-9]+)(?:\.([0-9]+))?", text)
    if match is None:
        return None
    major = int(match.group(1))
    minor = int(match.group(2))
    patch_raw = match.group(3)
    patch = int(patch_raw) if patch_raw is not None else 0
    return (major, minor, patch)


def _auto_wrapper_first_enabled(repository_root_path: str) -> bool:
    """Env 미지정 시 프로젝트 상태로 wrapper-first를 자동 선택한다."""
    wrapper_jar = pathlib.Path(repository_root_path) / "gradle" / "wrapper" / "gradle-wrapper.jar"
    wrapper_props = pathlib.Path(repository_root_path) / "gradle" / "wrapper" / "gradle-wrapper.properties"
    if not wrapper_props.exists():
        log.info("JDTLS wrapper-first auto fallback: gradle-wrapper.properties 미존재로 bundled gradle 사용")
        return False
    if not wrapper_jar.exists():
        # 일부 프로젝트는 properties만 커밋하는 경우가 있어 jar 부재만으로 차단하지 않는다.
        log.info("JDTLS wrapper-first auto fallback: gradle-wrapper.jar 미존재(계속 진행)")
    version = _parse_gradle_wrapper_version(repository_root_path)
    if version is None:
        log.info("JDTLS wrapper-first auto mode: wrapper 버전 파싱 실패, wrapper-first 유지")
        return True
    major, minor, _patch = version
    # Gradle 7.6 미만은 최신 Java/JDTLS 조합에서 import 실패 확률이 높아 bundled gradle로 내린다.
    if major < 7 or (major == 7 and minor < 6):
        log.info(
            "JDTLS wrapper-first auto fallback: legacy wrapper version=%s.%s -> bundled gradle 사용",
            major,
            minor,
        )
        return False
    return True


def _resolve_gradle_wrapper_first(repository_root_path: str) -> bool:
    raw = _env_get("SARI_JDTLS_GRADLE_WRAPPER_FIRST", "").strip().lower()
    if raw in {"1", "true", "yes", "on"}:
        return True
    if raw in {"0", "false", "no", "off"}:
        return False
    return _auto_wrapper_first_enabled(repository_root_path)


def _gradle_user_home_isolated_enabled() -> bool:
    raw = _env_get("SARI_JDTLS_GRADLE_USER_HOME_ISOLATED", "1").strip().lower()
    return raw not in {"0", "false", "no", "off"}


def _project_ready_required() -> bool:
    raw = _env_get("SARI_JDTLS_REQUIRE_PROJECT_READY", "0").strip().lower()
    return raw not in {"0", "false", "no", "off"}


def _set_java_home_env_for_ls_enabled() -> bool:
    raw = _env_get("SARI_JDTLS_SET_JAVA_HOME_FOR_LS", "0").strip().lower()
    return raw in {"1", "true", "yes", "on"}


def _isolated_gradle_user_home(*, ls_resources_dir: str, repository_root_path: str) -> str:
    normalized = os.path.abspath(repository_root_path)
    digest = hashlib.sha1(normalized.encode("utf-8"), usedforsecurity=False).hexdigest()[:16]
    return str(PurePath(ls_resources_dir, "EclipseJDTLS", ".isolated-gradle-home", digest))


def _wait_project_ready_or_raise(project_ready_event: threading.Event, *, timeout_sec: int) -> None:
    if not _project_ready_required():
        if project_ready_event.is_set():
            log.info("Project is ready")
        else:
            log.warning("Project ready status is not required in current mode; continuing without blocking wait")
        return
    if project_ready_event.wait(timeout=max(0, int(timeout_sec))):
        log.info("Project is ready")
        return
    raise RuntimeError(f"Project readiness event was not received within {timeout_sec} seconds")


def _resolve_ls_resources_dir_for_instance(instance: object) -> str:
    settings = getattr(instance, "_solidlsp_settings", None)
    ls_resources_dir = getattr(settings, "ls_resources_dir", None)
    if isinstance(ls_resources_dir, str) and ls_resources_dir.strip():
        return ls_resources_dir
    return str(pathlib.Path.home() / ".solidlsp" / "language_servers" / "static")


@dataclasses.dataclass
class RuntimeDependencyPaths:
    gradle_path: str
    lombok_jar_path: str
    jre_path: str
    jre_home_path: str
    jdtls_launcher_jar_path: str
    jdtls_readonly_config_path: str
    intellicode_jar_path: str
    intellisense_members_path: str
class EclipseJDTLS(SolidLanguageServer):
    def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
        super().__init__(config, repository_root_path, None, "java", solidlsp_settings)
        assert isinstance(self._dependency_provider, self.DependencyProvider)
        self.runtime_dependency_paths = self._dependency_provider.runtime_dependency_paths
        self._service_ready_event = threading.Event()
        self._project_ready_event = threading.Event()
        self._intellicode_enable_command_available = threading.Event()
    def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
        ls_resources_dir = self.ls_resources_dir(self._solidlsp_settings)
        return self.DependencyProvider(self._custom_settings, ls_resources_dir, self._solidlsp_settings, self.repository_root_path)
    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        return super().is_ignored_dirname(dirname) or dirname in [
            "target",  # Maven
            "build",  # Gradle
            "bin",  # Eclipse
            "out",  # IntelliJ IDEA
            "classes",  # General
            "dist",  # General
            "lib",  # General
        ]
    class DependencyProvider(LanguageServerDependencyProvider):
        def __init__(
            self,
            custom_settings: SolidLSPSettings.CustomLSSettings,
            ls_resources_dir: str,
            solidlsp_settings: SolidLSPSettings,
            repository_root_path: str,
        ):
            super().__init__(custom_settings, ls_resources_dir)
            self._solidlsp_settings = solidlsp_settings
            self._repository_root_path = repository_root_path
            self.runtime_dependency_paths = self._setup_runtime_dependencies(ls_resources_dir)
        @classmethod
        def _setup_runtime_dependencies(cls, ls_resources_dir: str) -> RuntimeDependencyPaths:
            platformId = PlatformUtils.get_platform_id()
            runtime_dependencies = {
                "gradle": {
                    "platform-agnostic": {
                        "url": "https://services.gradle.org/distributions/gradle-8.14.2-bin.zip",
                        "archiveType": "zip",
                        "relative_extraction_path": ".",
                    }
                },
                "vscode-java": {
                    "darwin-arm64": {
                        "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-arm64-1.42.0-561.vsix",
                        "archiveType": "zip",
                        "relative_extraction_path": "vscode-java",
                    },
                    "osx-arm64": {
                        "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-arm64-1.42.0-561.vsix",
                        "archiveType": "zip",
                        "relative_extraction_path": "vscode-java",
                        "jre_home_path": "extension/jre/21.0.7-macosx-aarch64",
                        "jre_path": "extension/jre/21.0.7-macosx-aarch64/bin/java",
                        "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
                        "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar",
                        "jdtls_readonly_config_path": "extension/server/config_mac_arm",
                    },
                    "osx-x64": {
                        "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-x64-1.42.0-561.vsix",
                        "archiveType": "zip",
                        "relative_extraction_path": "vscode-java",
                        "jre_home_path": "extension/jre/21.0.7-macosx-x86_64",
                        "jre_path": "extension/jre/21.0.7-macosx-x86_64/bin/java",
                        "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
                        "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar",
                        "jdtls_readonly_config_path": "extension/server/config_mac",
                    },
                    "linux-arm64": {
                        "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-arm64-1.42.0-561.vsix",
                        "archiveType": "zip",
                        "relative_extraction_path": "vscode-java",
                        "jre_home_path": "extension/jre/21.0.7-linux-aarch64",
                        "jre_path": "extension/jre/21.0.7-linux-aarch64/bin/java",
                        "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
                        "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar",
                        "jdtls_readonly_config_path": "extension/server/config_linux_arm",
                    },
                    "linux-x64": {
                        "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-x64-1.42.0-561.vsix",
                        "archiveType": "zip",
                        "relative_extraction_path": "vscode-java",
                        "jre_home_path": "extension/jre/21.0.7-linux-x86_64",
                        "jre_path": "extension/jre/21.0.7-linux-x86_64/bin/java",
                        "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
                        "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar",
                        "jdtls_readonly_config_path": "extension/server/config_linux",
                    },
                    "win-x64": {
                        "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-win32-x64-1.42.0-561.vsix",
                        "archiveType": "zip",
                        "relative_extraction_path": "vscode-java",
                        "jre_home_path": "extension/jre/21.0.7-win32-x86_64",
                        "jre_path": "extension/jre/21.0.7-win32-x86_64/bin/java.exe",
                        "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
                        "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar",
                        "jdtls_readonly_config_path": "extension/server/config_win",
                    },
                },
                "intellicode": {
                    "platform-agnostic": {
                        "url": "https://VisualStudioExptTeam.gallery.vsassets.io/_apis/public/gallery/publisher/VisualStudioExptTeam/extension/vscodeintellicode/1.2.30/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage",
                        "alternate_url": "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/VisualStudioExptTeam/vsextensions/vscodeintellicode/1.2.30/vspackage",
                        "archiveType": "zip",
                        "relative_extraction_path": "intellicode",
                        "intellicode_jar_path": "extension/dist/com.microsoft.jdtls.intellicode.core-0.7.0.jar",
                        "intellisense_members_path": "extension/dist/bundledModels/java_intellisense-members",
                    }
                },
            }
            gradle_path = str(
                PurePath(
                    ls_resources_dir,
                    "gradle-8.14.2",
                )
            )
            if not os.path.exists(gradle_path):
                FileUtils.download_and_extract_archive(
                    runtime_dependencies["gradle"]["platform-agnostic"]["url"],
                    str(PurePath(gradle_path).parent),
                    runtime_dependencies["gradle"]["platform-agnostic"]["archiveType"],
                )
            ensure_paths_exist([gradle_path], context="eclipse_jdtls.gradle")
            dependency = runtime_dependencies["vscode-java"][platformId.value]
            vscode_java_path = str(PurePath(ls_resources_dir, dependency["relative_extraction_path"]))
            os.makedirs(vscode_java_path, exist_ok=True)
            jre_home_path = str(PurePath(vscode_java_path, dependency["jre_home_path"]))
            jre_path = str(PurePath(vscode_java_path, dependency["jre_path"]))
            lombok_jar_path = str(PurePath(vscode_java_path, dependency["lombok_jar_path"]))
            jdtls_launcher_jar_path = str(PurePath(vscode_java_path, dependency["jdtls_launcher_jar_path"]))
            jdtls_readonly_config_path = str(PurePath(vscode_java_path, dependency["jdtls_readonly_config_path"]))
            if not all(
                [
                    os.path.exists(vscode_java_path),
                    os.path.exists(jre_home_path),
                    os.path.exists(jre_path),
                    os.path.exists(lombok_jar_path),
                    os.path.exists(jdtls_launcher_jar_path),
                    os.path.exists(jdtls_readonly_config_path),
                ]
            ):
                FileUtils.download_and_extract_archive(dependency["url"], vscode_java_path, dependency["archiveType"])
            os.chmod(jre_path, 0o755)
            ensure_paths_exist(
                [
                    vscode_java_path,
                    jre_home_path,
                    jre_path,
                    lombok_jar_path,
                    jdtls_launcher_jar_path,
                    jdtls_readonly_config_path,
                ],
                context="eclipse_jdtls.vscode_java",
            )
            dependency = runtime_dependencies["intellicode"]["platform-agnostic"]
            intellicode_directory_path = str(PurePath(ls_resources_dir, dependency["relative_extraction_path"]))
            os.makedirs(intellicode_directory_path, exist_ok=True)
            intellicode_jar_path = str(PurePath(intellicode_directory_path, dependency["intellicode_jar_path"]))
            intellisense_members_path = str(PurePath(intellicode_directory_path, dependency["intellisense_members_path"]))
            if not all(
                [
                    os.path.exists(intellicode_directory_path),
                    os.path.exists(intellicode_jar_path),
                    os.path.exists(intellisense_members_path),
                ]
            ):
                FileUtils.download_and_extract_archive(dependency["url"], intellicode_directory_path, dependency["archiveType"])
            ensure_paths_exist(
                [intellicode_directory_path, intellicode_jar_path, intellisense_members_path],
                context="eclipse_jdtls.intellicode",
            )
            return RuntimeDependencyPaths(
                gradle_path=gradle_path,
                lombok_jar_path=lombok_jar_path,
                jre_path=jre_path,
                jre_home_path=jre_home_path,
                jdtls_launcher_jar_path=jdtls_launcher_jar_path,
                jdtls_readonly_config_path=jdtls_readonly_config_path,
                intellicode_jar_path=intellicode_jar_path,
                intellisense_members_path=intellisense_members_path,
            )
        def create_launch_command(self) -> list[str] | str:
            ws_dir = str(
                PurePath(
                    self._solidlsp_settings.ls_resources_dir,
                    "EclipseJDTLS",
                    "workspaces",
                    uuid.uuid4().hex,
                )
            )
            shared_cache_location = str(PurePath(self._solidlsp_settings.ls_resources_dir, "lsp", "EclipseJDTLS", "sharedIndex"))
            os.makedirs(shared_cache_location, exist_ok=True)
            os.makedirs(ws_dir, exist_ok=True)
            jre_path = self.runtime_dependency_paths.jre_path
            lombok_jar_path = self.runtime_dependency_paths.lombok_jar_path
            jdtls_launcher_jar = self.runtime_dependency_paths.jdtls_launcher_jar_path
            data_dir = str(PurePath(ws_dir, "data_dir"))
            jdtls_config_path = str(PurePath(ws_dir, "config_path"))
            jdtls_readonly_config_path = self.runtime_dependency_paths.jdtls_readonly_config_path
            if not os.path.exists(jdtls_config_path):
                shutil.copytree(jdtls_readonly_config_path, jdtls_config_path)
            static_paths = [
                jre_path,
                lombok_jar_path,
                jdtls_launcher_jar,
                jdtls_config_path,
                jdtls_readonly_config_path,
            ]
            ensure_paths_exist(static_paths, context="eclipse_jdtls.launch")
            cmd = [
                jre_path,
                "--add-modules=ALL-SYSTEM",
                "--add-opens",
                "java.base/java.util=ALL-UNNAMED",
                "--add-opens",
                "java.base/java.lang=ALL-UNNAMED",
                "--add-opens",
                "java.base/sun.nio.fs=ALL-UNNAMED",
                "-Declipse.application=org.eclipse.jdt.ls.core.id1",
                "-Dosgi.bundles.defaultStartLevel=4",
                "-Declipse.product=org.eclipse.jdt.ls.core.product",
                "-Djava.import.generatesMetadataFilesAtProjectRoot=false",
                "-Dfile.encoding=utf8",
                "-noverify",
                "-XX:+UseParallelGC",
                "-XX:GCTimeRatio=4",
                "-XX:AdaptiveSizePolicyWeight=90",
                "-Dsun.zip.disableMemoryMapping=true",
                "-Djava.lsp.joinOnCompletion=true",
                "-Xmx3G",
                "-Xms100m",
                "-Xlog:disable",
                "-Dlog.level=ALL",
                f"-javaagent:{lombok_jar_path}",
                f"-Djdt.core.sharedIndexLocation={shared_cache_location}",
                "-jar",
                f"{jdtls_launcher_jar}",
                "-configuration",
                f"{jdtls_config_path}",
                "-data",
                f"{data_dir}",
            ]
            return cmd
        def create_launch_command_env(self) -> dict[str, str]:
            env = {"syntaxserver": "false"}
            if _set_java_home_env_for_ls_enabled():
                env["JAVA_HOME"] = self.runtime_dependency_paths.jre_home_path
            if _gradle_user_home_isolated_enabled():
                gradle_user_home = _isolated_gradle_user_home(
                    ls_resources_dir=self._solidlsp_settings.ls_resources_dir,
                    repository_root_path=self._repository_root_path,
                )
                os.makedirs(gradle_user_home, exist_ok=True)
                env["GRADLE_USER_HOME"] = gradle_user_home
            return env
    def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
        if not os.path.isabs(repository_absolute_path):
            repository_absolute_path = os.path.abspath(repository_absolute_path)
        repo_uri = pathlib.Path(repository_absolute_path).as_uri()
        default_maven_settings_path = os.path.join(os.path.expanduser("~"), ".m2", "settings.xml")
        custom_maven_settings_path = self._custom_settings.get("maven_user_settings")
        if custom_maven_settings_path is not None:
            if not os.path.exists(custom_maven_settings_path):
                error_msg = (
                    f"Provided maven settings file not found: {custom_maven_settings_path}. "
                    f"Fix: create the file, update path in ~/.serena/serena_config.yml (ls_specific_settings -> java -> maven_user_settings), "
                    f"or remove the setting to use default ({default_maven_settings_path})"
                )
                log.error(error_msg)
                raise FileNotFoundError(error_msg)
            maven_settings_path = custom_maven_settings_path
            log.info(f"Using Maven settings from custom location: {maven_settings_path}")
        elif os.path.exists(default_maven_settings_path):
            maven_settings_path = default_maven_settings_path
            log.info(f"Using Maven settings from default location: {maven_settings_path}")
        else:
            maven_settings_path = None
            log.info(f"Maven settings not found at default location ({default_maven_settings_path}), will use JDTLS defaults")
        custom_gradle_home = self._custom_settings.get("gradle_user_home")
        if custom_gradle_home is not None:
            if not os.path.exists(custom_gradle_home):
                error_msg = (
                    f"Gradle user home directory not found: {custom_gradle_home}. "
                    f"Fix: create the directory, update path in ~/.serena/serena_config.yml (ls_specific_settings -> java -> gradle_user_home), "
                    f"or remove the setting to use default (~/.gradle)"
                )
                log.error(error_msg)
                raise FileNotFoundError(error_msg)
            gradle_user_home = custom_gradle_home
            log.info(f"Using Gradle user home from custom location: {gradle_user_home}")
        elif _gradle_user_home_isolated_enabled():
            gradle_user_home = _isolated_gradle_user_home(
                ls_resources_dir=_resolve_ls_resources_dir_for_instance(self),
                repository_root_path=repository_absolute_path,
            )
            os.makedirs(gradle_user_home, exist_ok=True)
            log.info("Using isolated Gradle user home: %s", gradle_user_home)
        else:
            default_gradle_home = os.path.join(os.path.expanduser("~"), ".gradle")
            if os.path.exists(default_gradle_home):
                gradle_user_home = default_gradle_home
                log.info(f"Using Gradle user home from default location: {gradle_user_home}")
            else:
                gradle_user_home = None
                log.info(f"Gradle user home not found at default location ({default_gradle_home}), will use JDTLS defaults")
        initialize_params = {
            "locale": "en",
            "rootPath": repository_absolute_path,
            "rootUri": pathlib.Path(repository_absolute_path).as_uri(),
            "capabilities": {
                "workspace": {
                    "applyEdit": True,
                    "workspaceEdit": {
                        "documentChanges": True,
                        "resourceOperations": ["create", "rename", "delete"],
                        "failureHandling": "textOnlyTransactional",
                        "normalizesLineEndings": True,
                        "changeAnnotationSupport": {"groupsOnLabel": True},
                    },
                    "didChangeConfiguration": {"dynamicRegistration": True},
                    "didChangeWatchedFiles": {"dynamicRegistration": True, "relativePatternSupport": True},
                    "symbol": {
                        "dynamicRegistration": True,
                        "symbolKind": {"valueSet": list(range(1, 27))},
                        "tagSupport": {"valueSet": [1]},
                        "resolveSupport": {"properties": ["location.range"]},
                    },
                    "codeLens": {"refreshSupport": True},
                    "executeCommand": {"dynamicRegistration": True},
                    "configuration": True,
                    "workspaceFolders": True,
                    "semanticTokens": {"refreshSupport": True},
                    "fileOperations": {
                        "dynamicRegistration": True,
                        "didCreate": True,
                        "didRename": True,
                        "didDelete": True,
                        "willCreate": True,
                        "willRename": True,
                        "willDelete": True,
                    },
                    "inlineValue": {"refreshSupport": True},
                    "inlayHint": {"refreshSupport": True},
                    "diagnostics": {"refreshSupport": True},
                },
                "textDocument": {
                    "publishDiagnostics": {
                        "relatedInformation": True,
                        "versionSupport": False,
                        "tagSupport": {"valueSet": [1, 2]},
                        "codeDescriptionSupport": True,
                        "dataSupport": True,
                    },
                    "synchronization": {"dynamicRegistration": True, "willSave": True, "willSaveWaitUntil": True, "didSave": True},
                    "completion": {
                        "dynamicRegistration": True,
                        "contextSupport": True,
                        "completionItem": {
                            "snippetSupport": False,
                            "commitCharactersSupport": True,
                            "documentationFormat": ["markdown", "plaintext"],
                            "deprecatedSupport": True,
                            "preselectSupport": True,
                            "tagSupport": {"valueSet": [1]},
                            "insertReplaceSupport": False,
                            "resolveSupport": {"properties": ["documentation", "detail", "additionalTextEdits"]},
                            "insertTextModeSupport": {"valueSet": [1, 2]},
                            "labelDetailsSupport": True,
                        },
                        "insertTextMode": 2,
                        "completionItemKind": {
                            "valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
                        },
                        "completionList": {"itemDefaults": ["commitCharacters", "editRange", "insertTextFormat", "insertTextMode"]},
                    },
                    "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
                    "signatureHelp": {
                        "dynamicRegistration": True,
                        "signatureInformation": {
                            "documentationFormat": ["markdown", "plaintext"],
                            "parameterInformation": {"labelOffsetSupport": True},
                            "activeParameterSupport": True,
                        },
                    },
                    "definition": {"dynamicRegistration": True, "linkSupport": True},
                    "references": {"dynamicRegistration": True},
                    "documentSymbol": {
                        "dynamicRegistration": True,
                        "symbolKind": {"valueSet": list(range(1, 27))},
                        "hierarchicalDocumentSymbolSupport": True,
                        "tagSupport": {"valueSet": [1]},
                        "labelSupport": True,
                    },
                    "rename": {
                        "dynamicRegistration": True,
                        "prepareSupport": True,
                        "prepareSupportDefaultBehavior": 1,
                        "honorsChangeAnnotations": True,
                    },
                    "documentLink": {"dynamicRegistration": True, "tooltipSupport": True},
                    "typeDefinition": {"dynamicRegistration": True, "linkSupport": True},
                    "implementation": {"dynamicRegistration": True, "linkSupport": True},
                    "colorProvider": {"dynamicRegistration": True},
                    "declaration": {"dynamicRegistration": True, "linkSupport": True},
                    "selectionRange": {"dynamicRegistration": True},
                    "callHierarchy": {"dynamicRegistration": True},
                    "semanticTokens": {
                        "dynamicRegistration": True,
                        "tokenTypes": [
                            "namespace",
                            "type",
                            "class",
                            "enum",
                            "interface",
                            "struct",
                            "typeParameter",
                            "parameter",
                            "variable",
                            "property",
                            "enumMember",
                            "event",
                            "function",
                            "method",
                            "macro",
                            "keyword",
                            "modifier",
                            "comment",
                            "string",
                            "number",
                            "regexp",
                            "operator",
                            "decorator",
                        ],
                        "tokenModifiers": [
                            "declaration",
                            "definition",
                            "readonly",
                            "static",
                            "deprecated",
                            "abstract",
                            "async",
                            "modification",
                            "documentation",
                            "defaultLibrary",
                        ],
                        "formats": ["relative"],
                        "requests": {"range": True, "full": {"delta": True}},
                        "multilineTokenSupport": False,
                        "overlappingTokenSupport": False,
                        "serverCancelSupport": True,
                        "augmentsSyntaxTokens": True,
                    },
                    "typeHierarchy": {"dynamicRegistration": True},
                    "inlineValue": {"dynamicRegistration": True},
                    "diagnostic": {"dynamicRegistration": True, "relatedDocumentSupport": False},
                },
                "general": {
                    "staleRequestSupport": {
                        "cancel": True,
                        "retryOnContentModified": [
                            "textDocument/semanticTokens/full",
                            "textDocument/semanticTokens/range",
                            "textDocument/semanticTokens/full/delta",
                        ],
                    },
                    "regularExpressions": {"engine": "ECMAScript", "version": "ES2020"},
                    "positionEncodings": ["utf-16"],
                },
                "notebookDocument": {"synchronization": {"dynamicRegistration": True, "executionSummarySupport": True}},
            },
            "initializationOptions": {
                "bundles": ["intellicode-core.jar"],
                "settings": {
                    "java": {
                        "home": None,
                        "jdt": {
                            "ls": {
                                "java": {"home": None},
                                "vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx1G -Xms100m -Xlog:disable",
                                "lombokSupport": {"enabled": True},
                                "protobufSupport": {"enabled": True},
                                "androidSupport": {"enabled": True},
                            }
                        },
                        "errors": {"incompleteClasspath": {"severity": "error"}},
                        "configuration": {
                            "checkProjectSettingsExclusions": False,
                            "updateBuildConfiguration": "interactive",
                            "maven": {
                                "userSettings": maven_settings_path,
                                "globalSettings": None,
                                "notCoveredPluginExecutionSeverity": "warning",
                                "defaultMojoExecutionAction": "ignore",
                            },
                            "workspaceCacheLimit": 90,
                            "runtimes": [
                                {"name": "JavaSE-21", "path": "static/vscode-java/extension/jre/21.0.7-linux-x86_64", "default": True}
                            ],
                        },
                        "trace": {"server": "verbose"},
                        "import": {
                            "maven": {
                                "enabled": True,
                                "offline": {"enabled": False},
                                "disableTestClasspathFlag": False,
                            },
                            "gradle": {
                                "enabled": True,
                                "wrapper": {"enabled": False},
                                "version": None,
                                "home": "abs(static/gradle-7.3.3)",
                                "java": {"home": "abs(static/launch_jres/21.0.7-linux-x86_64)"},
                                "offline": {"enabled": False},
                                "arguments": None,
                                "jvmArguments": None,
                                "user": {"home": gradle_user_home},
                                "annotationProcessing": {"enabled": True},
                            },
                            "exclusions": [
                                "**/node_modules/**",
                                "**/.metadata/**",
                                "**/archetype-resources/**",
                                "**/META-INF/maven/**",
                            ],
                            "generatesMetadataFilesAtProjectRoot": False,
                        },
                        "maven": {"downloadSources": True, "updateSnapshots": False},
                        "eclipse": {"downloadSources": True},
                        "signatureHelp": {"enabled": True, "description": {"enabled": True}},
                        "hover": {"javadoc": {"enabled": True}},
                        "implementationsCodeLens": {"enabled": True},
                        "format": {
                            "enabled": True,
                            "settings": {"url": None, "profile": None},
                            "comments": {"enabled": True},
                            "onType": {"enabled": True},
                            "insertSpaces": True,
                            "tabSize": 4,
                        },
                        "saveActions": {"organizeImports": False},
                        "project": {
                            "referencedLibraries": ["lib/**/*.jar"],
                            "importOnFirstTimeStartup": "automatic",
                            "importHint": True,
                            "resourceFilters": ["node_modules", "\\.git"],
                            "encoding": "ignore",
                            "exportJar": {"targetPath": "${workspaceFolder}/${workspaceFolderBasename}.jar"},
                        },
                        "contentProvider": {"preferred": None},
                        "autobuild": {"enabled": True},
                        "maxConcurrentBuilds": 1,
                        "selectionRange": {"enabled": True},
                        "showBuildStatusOnStart": {"enabled": "notification"},
                        "server": {"launchMode": "Standard"},
                        "sources": {"organizeImports": {"starThreshold": 99, "staticStarThreshold": 99}},
                        "imports": {"gradle": {"wrapper": {"checksums": []}}},
                        "templates": {"fileHeader": [], "typeComment": []},
                        "references": {"includeAccessors": True, "includeDecompiledSources": True},
                        "typeHierarchy": {"lazyLoad": False},
                        "settings": {"url": None},
                        "symbols": {"includeSourceMethodDeclarations": False},
                        "inlayHints": {"parameterNames": {"enabled": "literals", "exclusions": []}},
                        "codeAction": {"sortMembers": {"avoidVolatileChanges": True}},
                        "compile": {
                            "nullAnalysis": {
                                "nonnull": [
                                    "javax.annotation.Nonnull",
                                    "org.eclipse.jdt.annotation.NonNull",
                                    "org.springframework.lang.NonNull",
                                ],
                                "nullable": [
                                    "javax.annotation.Nullable",
                                    "org.eclipse.jdt.annotation.Nullable",
                                    "org.springframework.lang.Nullable",
                                ],
                                "mode": "automatic",
                            }
                        },
                        "sharedIndexes": {"enabled": "auto", "location": ""},
                        "silentNotification": False,
                        "dependency": {
                            "showMembers": False,
                            "syncWithFolderExplorer": True,
                            "autoRefresh": True,
                            "refreshDelay": 2000,
                            "packagePresentation": "flat",
                        },
                        "help": {"firstView": "auto", "showReleaseNotes": True, "collectErrorLog": False},
                        "test": {"defaultConfig": "", "config": {}},
                    }
                },
            },
            "trace": "verbose",
            "processId": os.getpid(),
            "workspaceFolders": [
                {
                    "uri": repo_uri,
                    "name": os.path.basename(repository_absolute_path),
                }
            ],
        }
        initialize_params["initializationOptions"]["workspaceFolders"] = [repo_uri]  # type: ignore
        bundles = [self.runtime_dependency_paths.intellicode_jar_path]
        initialize_params["initializationOptions"]["bundles"] = bundles  # type: ignore
        initialize_params["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"] = [  # type: ignore
            {"name": "JavaSE-21", "path": self.runtime_dependency_paths.jre_home_path, "default": True}
        ]
        for runtime in initialize_params["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"]:  # type: ignore
            assert "name" in runtime
            assert "path" in runtime
            ensure_paths_exist([runtime["path"]], context="eclipse_jdtls.runtime")
        gradle_settings = initialize_params["initializationOptions"]["settings"]["java"]["import"]["gradle"]  # type: ignore
        if _resolve_gradle_wrapper_first(repository_absolute_path):
            gradle_settings["wrapper"]["enabled"] = True
            gradle_settings.pop("home", None)
            gradle_java = gradle_settings.get("java")
            if isinstance(gradle_java, dict):
                gradle_java.pop("home", None)
        else:
            gradle_settings["wrapper"]["enabled"] = False
            gradle_settings["home"] = self.runtime_dependency_paths.gradle_path
            gradle_settings["java"]["home"] = self.runtime_dependency_paths.jre_path
        return cast(InitializeParams, initialize_params)
    def _start_server(self) -> None:
        def register_capability_handler(params: dict) -> None:
            assert "registrations" in params
            for registration in params["registrations"]:
                if registration["method"] == "textDocument/completion":
                    assert registration["registerOptions"]["resolveProvider"] == True
                    assert registration["registerOptions"]["triggerCharacters"] == [
                        ".",
                        "@",
                        "#",
                        "*",
                        " ",
                    ]
                if registration["method"] == "workspace/executeCommand":
                    if "java.intellicode.enable" in registration["registerOptions"]["commands"]:
                        self._intellicode_enable_command_available.set()
            return
        def lang_status_handler(params: dict) -> None:
            log.info("Language status update: %s", params)
            if params["type"] == "ServiceReady" and params["message"] == "ServiceReady":
                self._service_ready_event.set()
            if params["type"] == "ProjectStatus":
                if params["message"] == "OK":
                    self._project_ready_event.set()
        def execute_client_command_handler(params: dict) -> list:
            assert params["command"] == "_java.reloadBundles.command"
            assert params["arguments"] == []
            return []
        def window_log_message(msg: dict) -> None:
            log.info(f"LSP: window/logMessage: {msg}")
        def do_nothing(params: dict) -> None:
            return
        self.server.on_request("client/registerCapability", register_capability_handler)
        self.server.on_notification("language/status", lang_status_handler)
        self.server.on_notification("window/logMessage", window_log_message)
        self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
        self.server.on_notification("$/progress", do_nothing)
        self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
        self.server.on_notification("language/actionableNotification", do_nothing)
        log.info("Starting EclipseJDTLS server process")
        self.server.start()
        initialize_params = self._get_initialize_params(self.repository_root_path)
        log.info("Sending initialize request from LSP client to LSP server and awaiting response")
        init_response = self.server.send.initialize(initialize_params)
        assert init_response["capabilities"]["textDocumentSync"]["change"] == 2  # type: ignore
        assert "completionProvider" not in init_response["capabilities"]
        assert "executeCommandProvider" not in init_response["capabilities"]
        self.server.notify.initialized({})
        self.server.notify.workspace_did_change_configuration({"settings": initialize_params["initializationOptions"]["settings"]})  # type: ignore
        intellicode_wait_timeout = _intellicode_wait_timeout_seconds()
        if intellicode_wait_timeout == 0:
            self._intellicode_enable_command_available.wait()
        elif not self._intellicode_enable_command_available.wait(timeout=intellicode_wait_timeout):
            log.warning(
                "Intellicode enable command was not registered within %ss; continuing startup",
                intellicode_wait_timeout,
            )
        if self._intellicode_enable_command_available.is_set():
            java_intellisense_members_path = self.runtime_dependency_paths.intellisense_members_path
            ensure_paths_exist([java_intellisense_members_path], context="eclipse_jdtls.intellisense_members")
            intellicode_enable_result = self.server.send.execute_command(
                {
                    "command": "java.intellicode.enable",
                    "arguments": [True, java_intellisense_members_path],
                }
            )
            assert intellicode_enable_result
        if not self._service_ready_event.is_set():
            log.info("Waiting for service to be ready ...")
            service_ready_timeout = _service_ready_timeout_seconds()
            if service_ready_timeout == 0:
                self._service_ready_event.wait()
            elif not self._service_ready_event.wait(timeout=service_ready_timeout):
                log.warning(
                    "ServiceReady event was not received within %ss; continuing startup",
                    service_ready_timeout,
                )
        log.info("Service is ready")
        if not self._project_ready_event.is_set():
            log.info("Waiting for project to be ready ...")
            project_ready_timeout = _project_ready_timeout_seconds()  # Configurable timeout for indexing/perf tuning
            _wait_project_ready_or_raise(self._project_ready_event, timeout_sec=project_ready_timeout)
        else:
            log.info("Project is ready")
        log.info("Startup complete")
    @override
    def _request_hover(self, uri: str, line: int, column: int) -> ls_types.Hover | None:
        def content_score(result: ls_types.Hover | None) -> tuple[int, int]:
            if result is None:
                return (0, 0)
            contents = result["contents"]
            if isinstance(contents, list):
                return (2, len(contents))  # List format (has javadoc) is best
            elif isinstance(contents, dict):
                return (1, len(contents.get("value", "")))
            else:
                return (1, len(contents))
        max_retries = 5
        best_result = super()._request_hover(uri, line, column)
        best_score = content_score(best_result)
        for _ in range(max_retries):
            sleep(0.05)
            new_result = super()._request_hover(uri, line, column)
            new_score = content_score(new_result)
            if new_score > best_score:
                best_result = new_result
                best_score = new_score
        return best_result
    def _request_document_symbols(
        self, relative_file_path: str, file_data: LSPFileBuffer | None
    ) -> list[SymbolInformation] | list[DocumentSymbol] | None:
        result = super()._request_document_symbols(relative_file_path, file_data=file_data)
        if result is None:
            return None
        def fix_name(symbol: SymbolInformation | DocumentSymbol | UnifiedSymbolInformation) -> None:
            if "(" in symbol["name"]:
                symbol["name"] = symbol["name"][: symbol["name"].index("(")]
            children = symbol.get("children")
            if children:
                for child in children:  # type: ignore
                    fix_name(child)
        for root_symbol in result:
            fix_name(root_symbol)
        return result
