"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnsibleLanguageService = void 0;
const vscode_languageserver_1 = require("vscode-languageserver");
const completionProvider_1 = require("./providers/completionProvider");
const definitionProvider_1 = require("./providers/definitionProvider");
const hoverProvider_1 = require("./providers/hoverProvider");
const semanticTokenProvider_1 = require("./providers/semanticTokenProvider");
const validationProvider_1 = require("./providers/validationProvider");
const validationManager_1 = require("./services/validationManager");
const workspaceManager_1 = require("./services/workspaceManager");
const getAnsibleMetaData_1 = require("./utils/getAnsibleMetaData");
const axios_1 = __importDefault(require("axios"));
const webUtils_1 = require("./utils/webUtils");
/**
 * Initializes the connection and registers all lifecycle event handlers.
 *
 * The event handlers interact with the `WorkspaceManager` to find the relevant
 * context and service instance, and then perform the required actions.
 *
 * Providers are used here directly in the event handlers.
 */
class AnsibleLanguageService {
    connection;
    documents;
    workspaceManager;
    validationManager;
    constructor(connection, documents) {
        this.connection = connection;
        this.documents = documents;
        this.workspaceManager = new workspaceManager_1.WorkspaceManager(connection);
        this.validationManager = new validationManager_1.ValidationManager(connection, documents);
    }
    initialize() {
        this.initializeConnection();
        this.registerLifecycleEventHandlers();
    }
    initializeConnection() {
        this.connection.onInitialize((params) => {
            this.workspaceManager.setWorkspaceFolders(params.workspaceFolders || []);
            this.workspaceManager.setCapabilities(params.capabilities);
            const result = {
                capabilities: {
                    textDocumentSync: vscode_languageserver_1.TextDocumentSyncKind.Incremental,
                    semanticTokensProvider: {
                        documentSelector: [
                            {
                                language: "ansible",
                            },
                        ],
                        full: true,
                        legend: {
                            tokenTypes: semanticTokenProvider_1.tokenTypes,
                            tokenModifiers: semanticTokenProvider_1.tokenModifiers,
                        },
                    },
                    hoverProvider: true,
                    completionProvider: {
                        resolveProvider: true,
                    },
                    definitionProvider: true,
                    workspace: {},
                },
            };
            if (this.workspaceManager.clientCapabilities.workspace?.workspaceFolders) {
                result.capabilities.workspace = {
                    workspaceFolders: {
                        supported: true,
                        changeNotifications: true,
                    },
                };
            }
            return result;
        });
        this.connection.onInitialized(() => {
            if (this.workspaceManager.clientCapabilities.workspace?.configuration) {
                // register for all configuration changes
                this.connection.client.register(vscode_languageserver_1.DidChangeConfigurationNotification.type, {
                    section: "ansible",
                });
            }
            if (this.workspaceManager.clientCapabilities.workspace?.workspaceFolders) {
                this.connection.workspace.onDidChangeWorkspaceFolders((e) => {
                    this.workspaceManager.handleWorkspaceChanged(e);
                });
            }
            this.connection.client.register(vscode_languageserver_1.DidChangeWatchedFilesNotification.type, {
                watchers: [
                    {
                        // watch ansible configuration
                        globPattern: "**/ansible.cfg",
                    },
                    {
                        // watch ansible-lint configuration
                        globPattern: "**/.ansible-lint",
                    },
                    {
                        // watch role meta-configuration
                        globPattern: "**/meta/main.{yml,yaml}",
                    },
                ],
            });
        });
    }
    registerLifecycleEventHandlers() {
        this.connection.onDidChangeConfiguration(async (params) => {
            try {
                await this.workspaceManager.forEachContext((context) => context.documentSettings.handleConfigurationChanged(params));
            }
            catch (error) {
                this.handleError(error, "onDidChangeConfiguration");
            }
        });
        this.documents.onDidOpen(async (e) => {
            try {
                const context = this.workspaceManager.getContext(e.document.uri);
                if (context) {
                    // perform full validation
                    await (0, validationProvider_1.doValidate)(e.document, this.validationManager, false, context, this.connection);
                }
            }
            catch (error) {
                this.handleError(error, "onDidOpen");
            }
        });
        this.documents.onDidClose((e) => {
            try {
                this.validationManager.handleDocumentClosed(e.document.uri);
                const context = this.workspaceManager.getContext(e.document.uri);
                if (context) {
                    context.documentSettings.handleDocumentClosed(e.document.uri);
                }
            }
            catch (error) {
                this.handleError(error, "onDidClose");
            }
        });
        this.connection.onDidChangeWatchedFiles((params) => {
            try {
                this.workspaceManager.forEachContext((context) => context.handleWatchedDocumentChange(params));
            }
            catch (error) {
                this.handleError(error, "onDidChangeWatchedFiles");
            }
        });
        this.documents.onDidSave(async (e) => {
            try {
                const context = this.workspaceManager.getContext(e.document.uri);
                if (context) {
                    // perform full validation
                    await (0, validationProvider_1.doValidate)(e.document, this.validationManager, false, context, this.connection);
                }
            }
            catch (error) {
                this.handleError(error, "onDidSave");
            }
        });
        this.connection.onDidChangeTextDocument((e) => {
            try {
                this.validationManager.reconcileCacheItems(e.textDocument.uri, e.contentChanges);
            }
            catch (error) {
                this.handleError(error, "onDidChangeTextDocument");
            }
        });
        this.documents.onDidChangeContent(async (e) => {
            try {
                await (0, validationProvider_1.doValidate)(e.document, this.validationManager, true, this.workspaceManager.getContext(e.document.uri), this.connection);
            }
            catch (error) {
                this.handleError(error, "onDidChangeContent");
            }
        });
        this.connection.languages.semanticTokens.on(async (params) => {
            try {
                const document = this.documents.get(params.textDocument.uri);
                if (document) {
                    const context = this.workspaceManager.getContext(params.textDocument.uri);
                    if (context) {
                        return await (0, semanticTokenProvider_1.doSemanticTokens)(document, await context.docsLibrary);
                    }
                }
            }
            catch (error) {
                this.handleError(error, "onSemanticTokens");
            }
            return {
                data: [],
            };
        });
        this.connection.onHover(async (params) => {
            try {
                const document = this.documents.get(params.textDocument.uri);
                if (document) {
                    const context = this.workspaceManager.getContext(params.textDocument.uri);
                    if (context) {
                        return await (0, hoverProvider_1.doHover)(document, params.position, await context.docsLibrary);
                    }
                }
            }
            catch (error) {
                this.handleError(error, "onHover");
            }
            return null;
        });
        this.connection.onCompletion(async (params) => {
            try {
                const document = this.documents.get(params.textDocument.uri);
                if (document) {
                    const context = this.workspaceManager.getContext(params.textDocument.uri);
                    if (context) {
                        return await (0, completionProvider_1.doCompletion)(document, params.position, context);
                    }
                }
            }
            catch (error) {
                this.handleError(error, "onCompletion");
            }
            return null;
        });
        this.connection.onCompletionResolve(async (completionItem) => {
            try {
                if (completionItem.data?.documentUri) {
                    const context = this.workspaceManager.getContext(completionItem.data?.documentUri);
                    if (context) {
                        return await (0, completionProvider_1.doCompletionResolve)(completionItem, context);
                    }
                }
            }
            catch (error) {
                this.handleError(error, "onCompletionResolve");
            }
            return completionItem;
        });
        this.connection.onDefinition(async (params) => {
            try {
                const document = this.documents.get(params.textDocument.uri);
                if (document) {
                    const context = this.workspaceManager.getContext(params.textDocument.uri);
                    if (context) {
                        return await (0, definitionProvider_1.getDefinition)(document, params.position, await context.docsLibrary);
                    }
                }
            }
            catch (error) {
                this.handleError(error, "onDefinition");
            }
            return null;
        });
        // Custom actions that are performed on receiving special notifications from the client
        // Resync ansible inventory service by clearing the cached items
        this.connection.onNotification("resync/ansible-inventory", async () => {
            this.workspaceManager.forEachContext((e) => {
                // Invalidate ansible inventory cache
                e.clearAnsibleInventory();
                this.connection.window.showInformationMessage("Re-syncing ansible inventory. This might take some time.");
                // Run the ansible inventory service
                e.ansibleInventory.then(() => {
                    this.connection.window.showInformationMessage("Ansible Inventory re-synced.");
                });
            });
        });
        // Send ansible info to client on receive of notification
        this.connection.onNotification("update/ansible-metadata", async (activeFileUri) => {
            const ctx = this.workspaceManager.getContext(activeFileUri);
            if (ctx !== undefined) {
                const ansibleMetaData = await (0, getAnsibleMetaData_1.getAnsibleMetaData)(ctx, this.connection);
                this.connection.sendNotification("update/ansible-metadata", [
                    ansibleMetaData,
                ]);
            }
        });
        this.connection.onRequest("playbook/explanation", async (params) => {
            const accessToken = params["accessToken"];
            const URL = params["URL"];
            const content = params["content"];
            const headers = {
                "Content-Type": "application/json",
                Authorization: `Bearer ${accessToken}`,
            };
            const axiosInstance = axios_1.default.create({
                baseURL: `${(0, webUtils_1.getBaseUri)(URL)}/api/v0`,
                headers: headers,
            });
            const explanation = await axiosInstance
                .post("/ai/explanations/", { content: content })
                .then((response) => {
                return response.data.content;
            });
            return explanation;
        });
        this.connection.onRequest("playbook/summary", async (params) => {
            const accessToken = params["accessToken"];
            const URL = params["URL"];
            const content = params["content"];
            const headers = {
                "Content-Type": "application/json",
                Authorization: `Bearer ${accessToken}`,
            };
            const axiosInstance = axios_1.default.create({
                baseURL: `${(0, webUtils_1.getBaseUri)(URL)}/api/v0`,
                headers: headers,
            });
            const result = await axiosInstance
                .post("/ai/summaries/", { content: content })
                .then((response) => {
                return response.data.content;
            });
            return result;
        });
        this.connection.onRequest("playbook/generation", async (params) => {
            const accessToken = params["accessToken"];
            const URL = params["URL"];
            const content = params["content"];
            const headers = {
                "Content-Type": "application/json",
                Authorization: `Bearer ${accessToken}`,
            };
            const axiosInstance = axios_1.default.create({
                baseURL: `${(0, webUtils_1.getBaseUri)(URL)}/api/v0`,
                headers: headers,
            });
            const result = await axiosInstance
                .post("/ai/generations/", { content: content })
                .then((response) => {
                return response.data.content;
            });
            return result;
        });
    }
    handleError(error, contextName) {
        const leadMessage = `An error occurred in '${contextName}' handler: `;
        if (error instanceof Error) {
            const stack = error.stack ? `\n${error.stack}` : "";
            this.connection.console.error(`${leadMessage}[${error.name}] ${error.message}${stack}`);
        }
        else {
            this.connection.console.error(leadMessage + JSON.stringify(error));
        }
    }
}
exports.AnsibleLanguageService = AnsibleLanguageService;
