"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getShellDocumentation = void 0;
exports.execShellScript = execShellScript;
exports.getShellDocumentationWithoutCache = getShellDocumentationWithoutCache;
exports.formatManOutput = formatManOutput;
exports.memorize = memorize;
const ChildProcess = require("child_process");
const logger_1 = require("./logger");
const platform_1 = require("./platform");
/**
 * Execute the following sh program.
 */
function execShellScript(body, cmd = (0, platform_1.isWindows)() ? 'cmd.exe' : 'bash') {
    const args = [];
    if (cmd === 'cmd.exe') {
        args.push('/c', body);
    }
    else {
        args.push('--noprofile', '--norc', '-c', body);
    }
    const process = ChildProcess.spawn(cmd, args);
    return new Promise((resolve, reject) => {
        let output = '';
        const handleClose = (returnCode) => {
            if (returnCode === 0) {
                resolve(output);
            }
            else {
                reject(`Failed to execute ${body}`);
            }
        };
        process.stdout.on('data', (buffer) => {
            output += buffer;
        });
        process.on('close', handleClose);
        process.on('error', handleClose);
    });
}
// Currently only reserved words where documentation doesn't make sense.
// At least on OS X these just return the builtin man. On ubuntu there
// are no documentation for them.
const WORDS_WITHOUT_DOCUMENTATION = new Set([
    'else',
    'fi',
    'then',
    'esac',
    'elif',
    'done',
]);
/**
 * Get documentation for the given word by using help and man.
 */
function getShellDocumentationWithoutCache(_a) {
    return __awaiter(this, arguments, void 0, function* ({ word, }) {
        if (word.split(' ').length > 1) {
            throw new Error(`lookupDocumentation should be given a word, received "${word}"`);
        }
        if (WORDS_WITHOUT_DOCUMENTATION.has(word)) {
            return null;
        }
        const DOCUMENTATION_COMMANDS = [
            { type: 'help', command: `help ${word} | col -bx` },
            // We have experimented with setting MANWIDTH to different values for reformatting.
            // The default line width of the terminal works fine for hover, but could be better
            // for completions.
            { type: 'man', command: `man -P cat ${word} | col -bx` },
        ];
        for (const { type, command } of DOCUMENTATION_COMMANDS) {
            try {
                const documentation = yield execShellScript(command);
                if (documentation) {
                    let formattedDocumentation = documentation.trim();
                    if (type === 'man') {
                        formattedDocumentation = formatManOutput(formattedDocumentation);
                    }
                    if (formattedDocumentation) {
                        return formattedDocumentation;
                    }
                }
            }
            catch (error) {
                // Ignoring if command fails and store failure in cache
                logger_1.logger.error(`getShellDocumentation failed for "${word}"`, error);
            }
        }
        return null;
    });
}
function formatManOutput(manOutput) {
    const indexNameBlock = manOutput.indexOf('NAME');
    const indexBeforeFooter = manOutput.lastIndexOf('\n');
    if (indexNameBlock < 0 || indexBeforeFooter < 0) {
        return manOutput;
    }
    const formattedManOutput = manOutput.slice(indexNameBlock, indexBeforeFooter);
    if (!formattedManOutput) {
        logger_1.logger.error(`formatManOutput failed`, { manOutput });
        return manOutput;
    }
    return formattedManOutput;
}
/**
 * Only works for one-parameter (serializable) functions.
 */
/* eslint-disable @typescript-eslint/ban-types */
function memorize(func) {
    const cache = new Map();
    const returnFunc = function (arg) {
        return __awaiter(this, void 0, void 0, function* () {
            const cacheKey = JSON.stringify(arg);
            if (cache.has(cacheKey)) {
                return cache.get(cacheKey);
            }
            const result = yield func(arg);
            cache.set(cacheKey, result);
            return result;
        });
    };
    return returnFunc;
}
exports.getShellDocumentation = memorize(getShellDocumentationWithoutCache);
//# sourceMappingURL=sh.js.map