"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnsibleLint = void 0;
const path = __importStar(require("path"));
const vscode_uri_1 = require("vscode-uri");
const vscode_languageserver_1 = require("vscode-languageserver");
const misc_1 = require("../utils/misc");
const commandRunner_1 = require("../utils/commandRunner");
/**
 * Acts as and interface to ansible-lint and a cache of its output.
 *
 * ansible-lint may provide diagnostics for more than just the file for which
 * linting was triggered, and this is reflected in the implementation.
 */
class AnsibleLint {
    connection;
    context;
    useProgressTracker = false;
    _ansibleLintConfigFilePath = undefined;
    constructor(connection, context) {
        this.connection = connection;
        this.context = context;
        this.useProgressTracker =
            !!context.clientCapabilities.window?.workDoneProgress;
    }
    /**
     * Perform linting for the given document.
     *
     * In case no errors are found for the current document, and linting has been
     * performed on opening the document, then only the cache is cleared, and not
     * the diagnostics on the client side. That way old diagnostics will persist
     * until the file is changed. This allows inspecting more complex errors
     * reported in other files.
     */
    async doValidate(textDocument) {
        let diagnostics = new Map();
        const workingDirectory = vscode_uri_1.URI.parse(this.context.workspaceFolder.uri).path;
        const mountPaths = new Set([workingDirectory]);
        const settings = await this.context.documentSettings.get(textDocument.uri);
        let linterArguments = settings.validation.lint.arguments ?? "";
        // Determine linter config file
        let ansibleLintConfigPath = linterArguments.match(/(?:^|\s)-c\s*(?<sep>[\s'"])(?<conf>.+?)(?:\k<sep>|$)/)?.groups?.conf;
        if (!ansibleLintConfigPath) {
            // Config file not provided in arguments -> search for one mimicking the
            // way ansible-lint looks for it, going up the directory structure
            const ansibleLintConfigFile = await this.findAnsibleLintConfigFile(textDocument.uri);
            if (ansibleLintConfigFile) {
                ansibleLintConfigPath = vscode_uri_1.URI.parse(ansibleLintConfigFile).path;
                linterArguments = `${linterArguments} -c "${ansibleLintConfigPath}"`;
                mountPaths.add(path.dirname(ansibleLintConfigPath));
            }
        }
        this._ansibleLintConfigFilePath = ansibleLintConfigPath;
        linterArguments = `${linterArguments} --offline --nocolor -f codeclimate`;
        const docPath = vscode_uri_1.URI.parse(textDocument.uri).path;
        mountPaths.add(path.dirname(docPath));
        const progressTracker = this.useProgressTracker
            ? await this.connection.window.createWorkDoneProgress()
            : {
                begin: () => {
                    // do nothing
                },
                done: () => {
                    // do nothing
                },
            };
        progressTracker.begin("ansible-lint", undefined, "Processing files...");
        const commandRunner = new commandRunner_1.CommandRunner(this.connection, this.context, settings);
        try {
            // get ansible-lint result on the doc
            const result = await commandRunner.runCommand("ansible-lint", `${linterArguments} "${docPath}"`, workingDirectory, mountPaths);
            diagnostics = this.processReport(result.stdout, workingDirectory);
            if (result.stderr) {
                this.connection.console.info(`[ansible-lint] ${result.stderr}`);
            }
        }
        catch (error) {
            if (error instanceof Error) {
                const execError = error;
                if (execError.stdout) {
                    diagnostics = this.processReport(execError.stdout, workingDirectory);
                }
                else {
                    if (execError.stderr) {
                        this.connection.console.info(`[ansible-lint] ${execError.stderr}`);
                    }
                    progressTracker.done();
                    this.connection.window.showErrorMessage(execError.message);
                    return new Map();
                }
            }
            else {
                const exceptionString = `Exception in AnsibleLint service: ${JSON.stringify(error)}`;
                progressTracker.done();
                this.connection.console.error(exceptionString);
                this.connection.window.showErrorMessage(exceptionString);
                return new Map();
            }
        }
        progressTracker.done();
        return diagnostics;
    }
    processReport(result, workingDirectory) {
        const diagnostics = new Map();
        if (!result) {
            this.connection.console.warn("Standard output from ansible-lint is suspiciously empty.");
            return diagnostics;
        }
        try {
            const report = JSON.parse(result);
            if (report instanceof Array) {
                for (const item of report) {
                    if (typeof item.check_name === "string" &&
                        item.location &&
                        typeof item.location.path === "string" &&
                        item.location.lines &&
                        (item.location.lines.begin ||
                            typeof item.location.lines.begin === "number")) {
                        const begin_line = item.location.lines.begin.line || item.location.lines.begin || 1;
                        const begin_column = item.location.lines.begin.column || 1;
                        const start = {
                            line: begin_line - 1,
                            character: begin_column - 1,
                        };
                        const end = {
                            line: begin_line - 1,
                            character: vscode_languageserver_1.integer.MAX_VALUE,
                        };
                        const range = {
                            start: start,
                            end: end,
                        };
                        let severity = vscode_languageserver_1.DiagnosticSeverity.Error;
                        if (item.severity) {
                            if (item.severity === "major") {
                                severity = vscode_languageserver_1.DiagnosticSeverity.Error;
                            }
                            else if (item.severity === "minor") {
                                severity = vscode_languageserver_1.DiagnosticSeverity.Warning;
                            }
                        }
                        const path = `${workingDirectory}/${item.location.path}`;
                        const locationUri = vscode_uri_1.URI.file(path).toString();
                        const helpUri = item.url ? item.url : undefined;
                        const helpUrlName = helpUri ? item.check_name : undefined;
                        let fileDiagnostics = diagnostics.get(locationUri);
                        if (!fileDiagnostics) {
                            fileDiagnostics = [];
                            diagnostics.set(locationUri, fileDiagnostics);
                        }
                        let message = item.check_name;
                        if (item.description) {
                            message = item.description;
                        }
                        fileDiagnostics.push({
                            message: message,
                            range: range || vscode_languageserver_1.Range.create(0, 0, 0, 0),
                            severity: severity,
                            source: "ansible-lint",
                            code: helpUrlName,
                            codeDescription: { href: helpUri },
                        });
                    }
                }
            }
        }
        catch (error) {
            this.connection.window.showErrorMessage("Could not parse ansible-lint output. Please check your ansible-lint installation & configuration." +
                " More info in `Ansible Server` output.");
            let message;
            if (error instanceof Error) {
                message = error.message;
            }
            else {
                message = JSON.stringify(error);
            }
            this.connection.console.error(`Exception while parsing ansible-lint output: ${message}` +
                `\nTried to parse the following:\n${result}`);
        }
        return diagnostics;
    }
    async findAnsibleLintConfigFile(uri) {
        // find configuration path
        let configPath;
        const pathArray = uri.split("/");
        // Find first configuration file going up until workspace root
        for (let index = pathArray.length - 1; index >= 0; index--) {
            let candidatePath = pathArray
                .slice(0, index)
                .concat(".ansible-lint")
                .join("/");
            const workspacePath = vscode_uri_1.URI.parse(this.context.workspaceFolder.uri).path;
            candidatePath = vscode_uri_1.URI.parse(candidatePath).path;
            if (!candidatePath.startsWith(workspacePath)) {
                // we've gone out of the workspace folder
                break;
            }
            if (await (0, misc_1.fileExists)(candidatePath)) {
                configPath = vscode_uri_1.URI.parse(candidatePath).path;
                break;
            }
        }
        return configPath;
    }
    get ansibleLintConfigFilePath() {
        return this._ansibleLintConfigFilePath;
    }
}
exports.AnsibleLint = AnsibleLint;
