// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

import slint_init, * as slint_lsp from "@lsp/slint_lsp_wasm.js";
import type { InitializeParams, InitializeResult } from "vscode-languageserver";
import {
    createConnection,
    BrowserMessageReader,
    BrowserMessageWriter,
} from "vscode-languageserver/browser";

function post_panic(message: string) {
    self.postMessage({ type: "slintpad/panic", message });
}

// Backstop: catch failures that happen before the Rust panic hook is armed
// (e.g. the wasm module itself failing to instantiate).
self.addEventListener("error", (e) =>
    post_panic(e.message || "Uncaught error in LSP worker"),
);
self.addEventListener("unhandledrejection", (e) =>
    post_panic(String(e.reason ?? "Unhandled rejection in LSP worker")),
);

// Wait for the main thread to share the compiled wasm module instead of
// fetching and compiling it a second time here.
const wasm_module_promise = new Promise<WebAssembly.Module>((resolve) => {
    function handler(m: MessageEvent) {
        const data = m.data;
        if (
            data &&
            typeof data === "object" &&
            data.type === "slintpad/init_wasm"
        ) {
            self.removeEventListener("message", handler);
            resolve(data.module as WebAssembly.Module);
        }
    }
    self.addEventListener("message", handler);
});

wasm_module_promise
    .then((wasm_module) => slint_init({ module_or_path: wasm_module }))
    .then(() => {
        slint_lsp.set_panic_hook(post_panic);

        const reader = new BrowserMessageReader(self);
        const writer = new BrowserMessageWriter(self);

        let the_lsp: slint_lsp.SlintServer;

        const connection = createConnection(reader, writer);

        function send_notification(method: string, params: unknown): boolean {
            void connection.sendNotification(method, params).catch((error) => {
                post_panic(String(error ?? "Failed to send LSP notification"));
            });
            return true;
        }

        async function send_request(
            method: string,
            params: unknown,
        ): Promise<unknown> {
            return await connection.sendRequest(method, params);
        }

        async function load_file(path: string): Promise<string> {
            return await connection.sendRequest("slint/load_file", path);
        }

        connection.onInitialize(
            (params: InitializeParams): InitializeResult => {
                the_lsp = slint_lsp.create(
                    params,
                    send_notification,
                    send_request,
                    load_file,
                );

                setTimeout(() => {
                    the_lsp.startup_lsp();
                }, 0);

                return the_lsp.server_initialize_result(params.capabilities);
            },
        );

        connection.onRequest(async (method, params, token) => {
            return await the_lsp.handle_request(token, method, params);
        });

        connection.onNotification(
            "slint/preview_to_lsp",
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            async (params: any) => {
                await the_lsp.process_preview_to_lsp_message(params);
            },
        );

        connection.onDidChangeWatchedFiles(async (param) => {
            for (const event of param.changes) {
                await the_lsp.trigger_file_watcher(event.uri, event.type);
            }
        });

        connection.onDidOpenTextDocument(async (param) => {
            await the_lsp.open_document(
                param.textDocument.text,
                param.textDocument.uri,
                param.textDocument.version,
            );
        });

        connection.onDidChangeTextDocument(async (param) => {
            await the_lsp.load_document(
                param.contentChanges[param.contentChanges.length - 1].text,
                param.textDocument.uri,
                param.textDocument.version,
            );
        });

        connection.onDidCloseTextDocument(async (param) => {
            await the_lsp.close_document(param.textDocument.uri);
        });

        connection.onDidChangeConfiguration(async (_param) => {
            await the_lsp.reload_config();
        });

        // Listen on the connection
        connection.listen();

        // Now that we listen, the client is ready to send the init message
        self.postMessage("OK");
    })
    .catch((e) => post_panic(String(e)));
