"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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Editor = void 0;
const path = __importStar(require("path"));
const events_1 = require("events");
const fs_1 = require("fs");
const bson_1 = require("bson");
const js_multiline_to_singleline_1 = require("@mongosh/js-multiline-to-singleline");
const shell_api_1 = require("@mongosh/shell-api");
const child_process_1 = require("child_process");
const beautify = require('js-beautify').js;
class Editor {
    constructor({ input, vscodeDir, tmpDir, instanceState, loadExternalCode, }) {
        this._input = input;
        this._vscodeDir = vscodeDir;
        this._tmpDir = tmpDir;
        this._instanceState = instanceState;
        this._loadExternalCode = loadExternalCode;
        this._lastContent = '';
        this._lastInputCode = '';
        this.print = instanceState.context.print;
        const wrapperFn = (code) => {
            return Object.assign(this.runEditCommand(code), {
                [Symbol.for('@@mongosh.syntheticPromise')]: true,
            });
        };
        wrapperFn.isDirectShellCommand = true;
        wrapperFn.returnsPromise = true;
        wrapperFn.acceptsRawInput = true;
        instanceState.shellApi.edit = instanceState.context.edit =
            wrapperFn;
        shell_api_1.signatures.ShellApi.attributes.edit = Editor.signature;
    }
    static create(options) {
        return new Editor(options);
    }
    async _getExtension(cmd) {
        if (!this._isVscodeApp(cmd)) {
            return 'js';
        }
        try {
            const extensions = await fs_1.promises.readdir(path.join(this._vscodeDir, 'extensions'));
            const hasMongodbExtension = !!extensions.find((name) => name.includes('mongodb.mongodb-vscode'));
            this.messageBus.emit('mongosh-editor:read-vscode-extensions-done', {
                vscodeDir: this._vscodeDir,
                hasMongodbExtension,
            });
            return hasMongodbExtension ? 'mongodb' : 'js';
        }
        catch (error) {
            this.messageBus.emit('mongosh-editor:read-vscode-extensions-failed', {
                vscodeDir: this._vscodeDir,
                error: error,
            });
            return 'js';
        }
    }
    async _getEditor() {
        let editor = await this._instanceState.shellApi.config.get('editor');
        if (!editor && process.env.EDITOR) {
            editor = process.env.EDITOR;
        }
        return editor;
    }
    async _createTempFile({ content, ext, }) {
        const tmpDoc = path.join(this._tmpDir, `edit-${new bson_1.ObjectId().toHexString()}.${ext}`);
        await fs_1.promises.mkdir(path.dirname(tmpDoc), { recursive: true, mode: 0o700 });
        await fs_1.promises.writeFile(tmpDoc, beautify(content), { mode: 0o600 });
        return tmpDoc;
    }
    async _readAndDeleteTempFile(tmpDoc) {
        this._lastContent = await fs_1.promises.readFile(tmpDoc, 'utf8');
        await fs_1.promises.unlink(tmpDoc);
        return (0, js_multiline_to_singleline_1.makeMultilineJSIntoSingleLine)(this._lastContent);
    }
    _isVscodeApp(cmd) {
        const regex = /[/\\]?[cC]ode(.exe)?(\s|$)/;
        return regex.test(cmd);
    }
    _isIdentifier(code) {
        if (this._isNumeric(code)) {
            return false;
        }
        const regex = /^([^!"#%&'()*+,\-/[\]\\^`{|}~]+)$/;
        return regex.test(code);
    }
    _isNumeric(code) {
        const regex = /^-?\d+$/;
        return regex.test(code);
    }
    async _getEditorContent(code) {
        if (!code) {
            return this._lastContent;
        }
        if (!this._isIdentifier(code)) {
            return code;
        }
        const evalResult = await this._loadExternalCode(code, '@(editor)');
        if (typeof evalResult === 'function') {
            return evalResult.toString();
        }
        return JSON.stringify(evalResult);
    }
    _prepareResult({ originalCode, modifiedCode, }) {
        if (!this._isIdentifier(originalCode)) {
            return modifiedCode;
        }
        return `${originalCode} = ${modifiedCode}`;
    }
    _setLastInputCode(code) {
        if (code !== '') {
            this._lastInputCode = code;
        }
    }
    _getLastInputCode(code) {
        if (code !== '') {
            return code;
        }
        return this._lastInputCode;
    }
    async runEditCommand(code) {
        await this.print('Opening an editor...');
        const editor = await this._getEditor();
        if (!editor) {
            throw new Error('Command failed with an error: please define an external editor');
        }
        this._setLastInputCode(code);
        const content = await this._getEditorContent(code);
        const ext = await this._getExtension(editor);
        const tmpDoc = await this._createTempFile({ content, ext });
        this.messageBus.emit('mongosh-editor:run-edit-command', {
            tmpDoc,
            editor,
            code,
        });
        const proc = (0, child_process_1.spawn)(editor, [path.basename(tmpDoc)], {
            stdio: 'inherit',
            cwd: path.dirname(tmpDoc),
            shell: true,
        });
        this._input.pause();
        try {
            const [exitCode] = await (0, events_1.once)(proc, 'exit');
            if (exitCode === 0) {
                const modifiedCode = await this._readAndDeleteTempFile(tmpDoc);
                const result = this._prepareResult({
                    originalCode: this._getLastInputCode(code),
                    modifiedCode,
                });
                this._input.unshift(result);
                return;
            }
            throw new Error(`Command '${editor} ${path.basename(tmpDoc)}' failed with an exit code ${exitCode}`);
        }
        finally {
            this._input.resume();
            if (proc.exitCode === null && proc.signalCode === null) {
                proc.kill();
            }
        }
    }
    get messageBus() {
        return this._instanceState.messageBus;
    }
}
exports.Editor = Editor;
Editor.signature = {
    type: 'function',
    returnsPromise: true,
    isDirectShellCommand: true,
    acceptsRawInput: true,
};
//# sourceMappingURL=editor.js.map