"""Generated by eglot-typescript-preset."""

import os
from typing import Any, cast
from urllib.parse import unquote

from rassumfrassum.frassum import DirectResponse, LspLogic, Server
from rassumfrassum.json import JSON
from rassumfrassum.util import dmerge

SERVERS: list[list[str]] = __SERVERS__  # type: ignore[name-defined]
INIT_OPTIONS: dict[str, Any] | None = __INIT_OPTIONS__  # type: ignore[name-defined]
ESLINT_LOGIC: bool = __ESLINT_LOGIC__  # type: ignore[name-defined]
VUE_TS_PLUGIN: str | None = __VUE_TS_PLUGIN__  # type: ignore[name-defined]


def _server_kind(name: str) -> str | None:
    # rass rewrites server.name mid-session: it starts as the binary name
    # (e.g. "vue-language-server") and is replaced with serverInfo.name from
    # the LSP initialize response (e.g. "@vue/language-server") once init
    # completes. The dual-name aliases below (vue-language-server +
    # @vue/language-server, typescript-language-server + tsserver, etc.) are
    # required so this function matches before and after that swap.
    base = name.lower().split("#", 1)[0]
    if "." in base:
        base = base.rsplit(".", 1)[0]
    if base in ("astro-ls", "astro-language-server"):
        return "astro-ls"
    if base == "biome":
        return "biome"
    if base == "deno":
        return "deno"
    if base in ("eslint", "eslint-language-server",
                "vscode-eslint-language-server"):
        return "eslint"
    if base == "oxfmt":
        return "oxfmt"
    if base == "oxlint":
        return "oxlint"
    if base in ("svelteserver", "svelte-language-server"):
        return "svelte-language-server"
    if base == "tailwindcss-language-server":
        return "tailwindcss-language-server"
    if base in ("typescript-language-server", "tsserver"):
        return "typescript-language-server"
    if base in ("vscode-css-language-server", "css-language-server",
                "css-languageserver"):
        return "vscode-css-language-server"
    if base in ("vue-language-server", "@vue/language-server"):
        return "vue-language-server"
    return None


def _find_workspace_folder(scope_uri: str) -> dict | None:
    """Find workspace folder by searching for package.json from scopeUri."""
    if not scope_uri.startswith("file://"):
        return None

    file_path = scope_uri[7:]
    current_dir = os.path.dirname(file_path)

    while current_dir and current_dir != "/":
        if os.path.exists(os.path.join(current_dir, "package.json")):
            return {
                "uri": f"file://{current_dir}",
                "name": os.path.basename(current_dir),
            }
        parent = os.path.dirname(current_dir)
        if parent == current_dir:
            break
        current_dir = parent

    return None


def _eslint_config(workspace_folder: dict | None = None) -> dict:
    """Return base ESLint configuration."""
    config: dict[str, Any] = {
        "experimental": {},
        "problems": {},
        "rulesCustomizations": [],
        "nodePath": None,
        "validate": "probe",
    }
    if workspace_folder:
        config["workspaceFolder"] = workspace_folder
    return config


class GeneratedTypeScriptLogic(LspLogic):
    def process_request(
        self, method: str, params: JSON, server: Server
    ) -> None:
        if method == "initialize":
            if INIT_OPTIONS and server == self.primary:
                params["initializationOptions"] = dmerge(
                    params.get("initializationOptions") or {},
                    INIT_OPTIONS,
                )
            if (
                VUE_TS_PLUGIN
                and _server_kind(server.name)
                == "typescript-language-server"
            ):
                init_opts = params.get("initializationOptions") or {}
                plugins = list(init_opts.get("plugins", []))
                plugins.append(
                    {
                        "name": "@vue/typescript-plugin",
                        "location": VUE_TS_PLUGIN,
                        "languages": ["vue"],
                    }
                )
                params["initializationOptions"] = dmerge(
                    init_opts, {"plugins": plugins}
                )
        super().process_request(method, params, server)

    # Servers that percent-encode characters the client sends literally.
    # svelteserver encodes ``+`` as ``%2B`` via vscode-uri; other servers
    # either pass through the client URI or re-encode consistently.
    _URI_FIXUP_SERVERS = frozenset({"svelte-language-server"})

    @staticmethod
    def _normalize_uri(uri: str) -> str:
        """Decode percent-encoded file URIs for consistent keying.

        svelteserver percent-encodes characters like ``+`` as ``%2B``
        while the client (Eglot) sends them unencoded.  Decoding the
        server's URI prevents diagnostic merging from treating them
        as different documents.

        Only applied for servers listed in ``_URI_FIXUP_SERVERS``.
        """
        if not uri.startswith("file://"):
            return uri
        decoded = unquote(uri)
        if decoded != uri:
            return decoded
        return uri

    async def on_server_notification(
        self, method: str, params: JSON, source: Server
    ) -> None:
        # vue-language-server v3 delegates TypeScript work back to the client
        # via tsserver/request notifications. Wire format is double-wrapped:
        # params = [[request_id, command, args]]. If nothing responds, the
        # server blocks forever and emits no diagnostics. Replying with
        # [[request_id, None]] makes it fall back to "simple project" mode,
        # which still surfaces template compilation errors (code 28) but not
        # TypeScript type errors. Returning before super() keeps the upstream
        # handler from re-dispatching a notification we've already consumed.
        if (
            method == "tsserver/request"
            and _server_kind(source.name) == "vue-language-server"
        ):
            req = params[0] if isinstance(params, list) else None
            req_id = req[0] if isinstance(req, list) else None
            if req_id is not None:
                await self.notify_server(
                    source, "tsserver/response", [[req_id, None]]
                )
            return
        if (
            method == "textDocument/publishDiagnostics"
            and _server_kind(source.name) in self._URI_FIXUP_SERVERS
        ):
            uri = params.get("uri", "")
            normalized = self._normalize_uri(uri)
            if normalized != uri:
                params["uri"] = normalized
        await super().on_server_notification(method, params, source)

    async def on_server_request(
        self, method: str, params: JSON, source: Server
    ) -> DirectResponse | None:
        result = await super().on_server_request(method, params, source)
        if result is not None:
            return result
        if method == "client/registerCapability":
            unsupported = {
                "workspace/didChangeWatchedFiles",
                "workspace/didChangeWorkspaceFolders",
            }
            regs = params.get("registrations", [])
            if regs and all(
                reg.get("method") in unsupported for reg in regs
            ):
                return DirectResponse(payload={})
        return None

    async def on_client_response(
        self,
        method: str,
        request_params: JSON,
        response_payload: JSON,
        is_error: bool,
        server: Server,
    ) -> None:
        if method == "workspace/configuration" and not is_error:
            req_items = request_params.get("items", [])
            res_items = cast(list[Any], response_payload)
            if len(res_items) < len(req_items):
                res_items.extend([None] * (len(req_items) - len(res_items)))

            for i, item in enumerate(req_items):
                section = item.get("section", "")
                if res_items[i] is None and section:
                    if section in ("css", "less", "scss"):
                        res_items[i] = {
                            "lint": {"unknownAtRules": "ignore"},
                        }
                    else:
                        res_items[i] = {}
                if (
                    ESLINT_LOGIC
                    and section == ""
                    and "eslint" in server.name.lower()
                ):
                    wfolder = _find_workspace_folder(
                        item.get("scopeUri", "")
                    )
                    cfg = _eslint_config(wfolder)
                    if isinstance(res_items[i], dict):
                        res_items[i] = dmerge(res_items[i], cfg)
                    else:
                        res_items[i] = cfg

        await super().on_client_response(
            method,
            request_params,
            response_payload,
            is_error,
            server,
        )


def servers():
    return SERVERS


def logic_class():
    return GeneratedTypeScriptLogic
