import { createService } from "dot-language-support";
import * as rpc from "vscode-jsonrpc";
import * as lsp from "vscode-languageserver";
import { TextDocumentSyncKind } from "vscode-languageserver";
import { TextDocument } from "vscode-languageserver-textdocument";
const defaultSettings = { maxNumberOfProblems: 100 };
export function runServer(connection) {
    if (!connection)
        throw "connection is missing";
    const languageService = createService();
    const documents = new lsp.TextDocuments(TextDocument);
    const astOfFile = new Map();
    documents.listen(connection);
    let shouldSendDiagnosticRelatedInformation = false;
    connection.onInitialize((_params) => {
        const a = _params.capabilities?.textDocument?.publishDiagnostics?.relatedInformation;
        shouldSendDiagnosticRelatedInformation = !!a;
        return {
            capabilities: {
                textDocumentSync: TextDocumentSyncKind.Full,
                completionProvider: {
                    triggerCharacters: ["="],
                    resolveProvider: false,
                },
                hoverProvider: true,
                referencesProvider: true,
                definitionProvider: true,
                renameProvider: true,
                codeActionProvider: true,
                executeCommandProvider: {
                    commands: languageService.getAvailableCommands(),
                },
                colorProvider: true,
            },
        };
    });
    function rebuildAll() {
        for (const uri of astOfFile.keys())
            updateAst(uri);
    }
    function updateAst(uri, doc) {
        const d = doc === undefined ? documents.get(uri) : doc;
        if (!d) {
            return undefined;
        }
        const ast = languageService.parseDocument(d);
        astOfFile.set(uri, ast);
        return ast;
    }
    function ensureAst(uri, doc) {
        let ast = astOfFile.get(uri);
        if (ast === undefined)
            ast = updateAst(uri, doc);
        return ast;
    }
    connection.onHover(req => {
        const uri = req.textDocument.uri;
        const doc = documents.get(uri);
        const ast = ensureAst(uri, doc);
        return doc && ast ? languageService.hover(doc, ast, req.position) : invalidRequest();
    });
    connection.onReferences(req => {
        const uri = req.textDocument.uri;
        const doc = documents.get(uri);
        const ast = ensureAst(uri, doc);
        return doc && ast
            ? languageService.findReferences(doc, ast, req.position, req.context)
            : invalidRequest();
    });
    connection.onDefinition(req => {
        const uri = req.textDocument.uri;
        const doc = documents.get(uri);
        const ast = ensureAst(uri, doc);
        return doc && ast
            ? languageService.findDefinition(doc, ast, req.position)
            : invalidRequest();
    });
    connection.onDocumentColor(req => {
        const uri = req.textDocument.uri;
        const doc = documents.get(uri);
        const ast = ensureAst(uri, doc);
        return doc && ast ? languageService.getDocumentColors(doc, ast) : invalidRequest();
    });
    connection.onColorPresentation(req => {
        const uri = req.textDocument.uri;
        const doc = documents.get(uri);
        const ast = ensureAst(uri, doc);
        return doc && ast
            ? languageService.getColorRepresentations(doc, ast, req.color, req.range)
            : invalidRequest();
    });
    connection.onCodeAction(req => {
        const uri = req.textDocument.uri;
        const doc = documents.get(uri);
        const ast = ensureAst(uri, doc);
        if (doc && ast) {
            const r = languageService.getCodeActions(doc, ast, req.range, req.context);
            if (r) {
                for (const command of r) {
                    if (command.arguments)
                        command.arguments.push(uri);
                    else
                        command.arguments = [uri];
                }
            }
            return r;
        }
        return invalidRequest();
    });
    connection.onExecuteCommand(req => {
        const args = req.arguments;
        if (!args || args.length < 1)
            return;
        const uri = args.pop();
        const doc = documents.get(uri);
        const ast = ensureAst(uri, doc);
        if (doc && ast) {
            req.arguments = args.length === 0 ? undefined : args;
            const edit = languageService.executeCommand(doc, ast, req);
            if (edit)
                connection.workspace.applyEdit(edit);
        }
    });
    connection.onRenameRequest(req => {
        const uri = req.textDocument.uri;
        const doc = documents.get(uri);
        const ast = ensureAst(uri, doc);
        if (doc && ast) {
            const r = languageService.renameSymbol(doc, ast, req.position, req.newName);
            return r ? r : invalidRequest();
        }
        return invalidRequest();
    });
    documents.onDidChangeContent(change => {
        const doc = change.document;
        const ast = updateAst(doc.uri, doc);
        if (ast === undefined)
            throw "This cannot happen";
        return validateDocument(doc, ast);
    });
    let currentSettings = { ...defaultSettings };
    connection.onDidChangeConfiguration(change => {
        const newSettings = change.settings.dotLanguageServer;
        if (newSettings)
            currentSettings = newSettings;
        rebuildAll();
        validateAll();
    });
    function validateAll() {
        for (const uri of astOfFile.keys()) {
            const doc = documents.get(uri);
            if (doc) {
                const ast = ensureAst(uri, doc);
                if (ast)
                    validateDocument(doc, ast);
            }
        }
    }
    function validateDocument(doc, sf) {
        const diagnostics = languageService.validateDocument(doc, sf);
        connection.sendDiagnostics({ uri: doc.uri, diagnostics });
    }
    connection.onDidChangeWatchedFiles(_change => {
        connection.console.log("We received an file change event");
    });
    connection.onCompletion(req => {
        const uri = req.textDocument.uri;
        const doc = documents.get(uri);
        const ast = ensureAst(uri, doc);
        return doc && ast
            ?
                languageService.getCompletions(doc, ast, req.position)
            : invalidRequest();
    });
    documents.onDidOpen(params => updateAst(params.document.uri, params.document));
    const invalidRequest = () => new rpc.ResponseError(rpc.ErrorCodes.InvalidRequest, "Invalid request");
    connection.listen();
}
