"use strict";
var _ExtHostCommands_proxy, _CommandsConverter_commands;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandsConverter = exports.ExtHostCommands = void 0;
exports.createCommandsApiFactory = createCommandsApiFactory;
const tslib_1 = require("tslib");
const ide_core_common_1 = require("@opensumi/ide-core-common");
const vscode_1 = require("../../../common/vscode");
const extHostTypeConverter = tslib_1.__importStar(require("../../../common/vscode/converter"));
const ext_types_1 = require("../../../common/vscode/ext-types");
const ext_host_api_command_1 = require("./ext.host.api.command");
const util_1 = require("./language/util");
function createCommandsApiFactory(extHostCommands, extHostEditors, extension) {
    const commands = {
        registerCommand(id, command, thisArgs) {
            try {
                return extHostCommands.registerCommand(true, id, command, thisArgs);
            }
            catch (_e) {
                return new ext_types_1.Disposable(() => { });
            }
        },
        executeCommand(id, ...args) {
            const extensionInfo = {
                id: extension.id,
                extensionId: extension.extensionId,
                isBuiltin: extension.isBuiltin,
            };
            return extHostCommands.$executeCommandWithExtensionInfo(id, extensionInfo, ...args);
        },
        getCommands(filterInternal = false) {
            return extHostCommands.getCommands(filterInternal);
        },
        registerTextEditorCommand(id, callback, thisArg) {
            return extHostCommands.registerCommand(true, id, (...args) => {
                const activeTextEditor = extHostEditors.activeEditor ? extHostEditors.activeEditor.textEditor : undefined;
                if (!activeTextEditor) {
                    (0, ide_core_common_1.getDebugLogger)().warn('Cannot execute ' + id + ' because there is no active text editor.');
                    return undefined;
                }
                return activeTextEditor
                    .edit((edit) => {
                    args.unshift(activeTextEditor, edit);
                    callback.apply(thisArg, args);
                })
                    .then((result) => {
                    if (!result) {
                        (0, ide_core_common_1.getDebugLogger)().warn('Edits from command ' + id + ' were not applied.');
                    }
                }, (err) => {
                    (0, ide_core_common_1.getDebugLogger)().warn('An error occurred while running command ' + id, err);
                });
            });
        },
        registerDiffInformationCommand(id, callback, thisArg) {
            return extHostCommands.registerCommand(true, id, async (...args) => {
                const activeTextEditor = extHostEditors.activeEditor;
                if (!activeTextEditor) {
                    (0, ide_core_common_1.getDebugLogger)().warn('Cannot execute ' + id + ' because there is no active text editor.');
                    return undefined;
                }
                const diff = await extHostEditors.getDiffInformation(activeTextEditor.id);
                callback.apply(thisArg, [diff, ...args]);
            });
        },
    };
    return commands;
}
class ExtHostCommands {
    constructor(rpcProtocol, buildInCommands) {
        this.buildInCommands = buildInCommands;
        _ExtHostCommands_proxy.set(this, void 0);
        this.logger = (0, ide_core_common_1.getDebugLogger)();
        this.commands = new Map();
        this.argumentProcessors = [];
        this._apiCommands = new Map();
        tslib_1.__classPrivateFieldSet(this, _ExtHostCommands_proxy, rpcProtocol.getProxy(vscode_1.MainThreadAPIIdentifier.MainThreadCommands), "f");
        this.registerUriArgProcessor();
    }
    registerUriArgProcessor() {
        this.registerArgumentProcessor({
            processArgument: (arg) => {
                // 将通信后 toJSON 的 Uri object 转换回 Uri 实例
                // 插件里面用了 `instanceof Uri`
                if (ide_core_common_1.Uri.isUri(arg)) {
                    return ide_core_common_1.Uri.from(arg);
                }
                // 数组参数的处理
                if (ide_core_common_1.arrays.isNonEmptyArray(arg)) {
                    return arg.map((item) => {
                        if (ide_core_common_1.Uri.isUri(item)) {
                            return ide_core_common_1.Uri.from(item);
                        }
                        return item;
                    });
                }
                return arg;
            },
        });
    }
    // 需要在 $registerBuiltInCommands 一起注册 避免插件进程启动但浏览器未启动时报错
    $registerCommandConverter() {
        this.converter = new CommandsConverter(this, (id) => {
            // API commands that have no return type (void) can be
            // converted to their internal command and don't need
            // any indirection commands
            const candidate = this._apiCommands.get(id);
            return (candidate === null || candidate === void 0 ? void 0 : candidate.result) === ext_host_api_command_1.ApiCommandResult.Void ? candidate : undefined;
        });
    }
    $registerBuiltInCommands() {
        if (this.buildInCommands) {
            this.logger.log('register builtIn commands');
            for (const command of this.buildInCommands) {
                const { id, handler } = command;
                this.logger.verbose(`register builtIn command ${id}`);
                this.register(id, handler);
            }
        }
        for (const command of ext_host_api_command_1.newCommands) {
            this.registerApiCommand(command);
        }
    }
    registerApiCommand(apiCommand) {
        const registration = this.registerCommand(false, apiCommand.id, async (...apiArgs) => {
            const internalArgs = apiCommand.args.map((arg, i) => {
                if (!arg.validate(apiArgs[i])) {
                    throw new Error(`Invalid argument '${arg.name}' when running '${apiCommand.id}', received: ${apiArgs[i]}`);
                }
                return arg.convert(apiArgs[i]);
            });
            const internalResult = await this.executeCommand(apiCommand.internalId, ...internalArgs);
            return apiCommand.result.convert(internalResult, apiArgs, this.converter);
        }, undefined, {
            description: apiCommand.description,
            args: apiCommand.args,
            returns: apiCommand.result.description,
        });
        this._apiCommands.set(apiCommand.id, apiCommand);
        return new ext_types_1.Disposable(() => {
            registration.dispose();
            this._apiCommands.delete(apiCommand.id);
        });
    }
    register(id, commandHandler, description) {
        if ((0, ide_core_common_1.isFunction)(commandHandler)) {
            return this.registerCommand(false, id, {
                handler: commandHandler,
                thisArg: this,
                description,
            });
        }
        return this.registerCommand(false, id, { ...commandHandler, thisArg: this });
    }
    registerCommand(global, id, handler, thisArg, description) {
        this.logger.log('ExtHostCommands#registerCommand', id);
        if (!id.trim().length) {
            throw new Error('invalid id');
        }
        if (this.commands.has(id)) {
            throw new Error(`command '${id}' already exists`);
        }
        if ((0, ide_core_common_1.isFunction)(handler)) {
            this.commands.set(id, {
                handler,
                thisArg,
                description,
            });
        }
        else {
            this.commands.set(id, handler);
        }
        if (global) {
            tslib_1.__classPrivateFieldGet(this, _ExtHostCommands_proxy, "f").$registerCommand(id);
        }
        return ext_types_1.Disposable.create(() => {
            if (this.commands.delete(id)) {
                if (global) {
                    tslib_1.__classPrivateFieldGet(this, _ExtHostCommands_proxy, "f").$unregisterCommand(id);
                }
            }
        });
    }
    $executeContributedCommand(id, ...args) {
        this.logger.log('ExtHostCommands#$executeContributedCommand', id);
        if (!this.commands.has(id)) {
            return Promise.reject(new Error(`Contributed command '${id}' does not exist.`));
        }
        else {
            args = args.map((arg) => this.argumentProcessors.reduce((r, p) => p.processArgument(r), arg));
            return this.executeLocalCommand(id, args);
        }
    }
    convertArguments(args) {
        return ide_core_common_1.objects.cloneAndChange(args, (value) => {
            if (value instanceof ext_types_1.Position) {
                return extHostTypeConverter.fromPosition(value);
            }
            if (value instanceof ext_types_1.Range) {
                return extHostTypeConverter.fromRange(value);
            }
            if (value instanceof ext_types_1.Location) {
                return extHostTypeConverter.fromLocation(value);
            }
            if (!Array.isArray(value)) {
                return value;
            }
        });
    }
    async $executeCommandWithExtensionInfo(id, extensionInfo, ...args) {
        if (this.commands.has(id)) {
            const isPermitted = this.isPermittedCommand(id, extensionInfo, ...args);
            if (!isPermitted) {
                throw new Error(`Extension ${extensionInfo.id} has not permit to execute ${id}`);
            }
            return this.executeLocalCommand(id, args);
        }
        else {
            // automagically convert some argument types
            args = this.convertArguments(args);
            return tslib_1.__classPrivateFieldGet(this, _ExtHostCommands_proxy, "f")
                .$executeCommandWithExtensionInfo(id, extensionInfo, ...args)
                .then((result) => (0, ide_core_common_1.revive)(result, 0));
        }
    }
    async executeCommand(id, ...args) {
        this.logger.log('ExtHostCommands#executeCommand', id, args);
        if (this.commands.has(id)) {
            return this.executeLocalCommand(id, args);
        }
        else {
            // automagically convert some argument types
            args = this.convertArguments(args);
            return tslib_1.__classPrivateFieldGet(this, _ExtHostCommands_proxy, "f").$executeCommand(id, ...args).then((result) => (0, ide_core_common_1.revive)(result, 0));
        }
    }
    executeLocalCommand(id, args) {
        const commandHandler = this.commands.get(id);
        if (!commandHandler) {
            throw new Error(`Command ${id} no handler`);
        }
        const { handler, thisArg, description } = commandHandler;
        if (description && description.args) {
            for (let i = 0; i < description.args.length; i++) {
                try {
                    (0, ide_core_common_1.validateConstraint)(args[i], description.args[i].constraint);
                }
                catch (err) {
                    return Promise.reject(new Error(`Running the contributed command:'${id}' failed. Illegal argument '${description.args[i].name}' - ${description.args[i].description}`));
                }
            }
        }
        // TODO: 这里做拦截
        try {
            const result = handler.apply(thisArg, this.processArguments(args));
            return Promise.resolve(result);
        }
        catch (err) {
            this.logger.error(err, id);
            return Promise.reject(new Error(`Running the contributed command:'${id}' failed.`));
        }
    }
    processArguments(arg) {
        if (Array.isArray(arg) && Array.isArray(arg[0]) && arg[0].length === 2) {
            const position = arg[0];
            if (ext_types_1.Position.isPosition(position[0]) && ext_types_1.Position.isPosition(position[1])) {
                return [
                    new ext_types_1.Range(new ext_types_1.Position(position[0].line, position[0].character), new ext_types_1.Position(position[1].line, position[1].character)),
                ];
            }
        }
        return arg;
    }
    async getCommands(filterUnderscoreCommands = false) {
        this.logger.log('ExtHostCommands#getCommands', filterUnderscoreCommands);
        const result = await tslib_1.__classPrivateFieldGet(this, _ExtHostCommands_proxy, "f").$getCommands();
        if (filterUnderscoreCommands) {
            return result.filter((command) => command[0] !== '_');
        }
        return result;
    }
    registerArgumentProcessor(processor) {
        this.argumentProcessors.push(processor);
    }
    isPermittedCommand(commandId, extensionInfo, ...args) {
        const commandHandler = this.commands.get(commandId);
        if (!commandHandler) {
            // 说明不是插件进程命令，是主进程命令
            return true;
        }
        const { isPermitted } = commandHandler;
        return !isPermitted || isPermitted(extensionInfo, ...args);
    }
}
exports.ExtHostCommands = ExtHostCommands;
_ExtHostCommands_proxy = new WeakMap();
class CommandsConverter {
    // --- conversion between internal and api commands
    constructor(commands, _lookupApiCommand) {
        this._lookupApiCommand = _lookupApiCommand;
        _CommandsConverter_commands.set(this, void 0);
        this._cache = new Map();
        this._cachIdPool = 0;
        this._delegatingCommandId = `_vscode_delegate_cmd_${Date.now().toString(36)}`;
        tslib_1.__classPrivateFieldSet(this, _CommandsConverter_commands, commands, "f");
        tslib_1.__classPrivateFieldGet(this, _CommandsConverter_commands, "f").registerCommand(true, this._delegatingCommandId, this._executeConvertedCommand, this);
    }
    toInternal(command, disposables) {
        if (!command) {
            return undefined;
        }
        const result = {
            $ident: undefined,
            id: command.command,
            title: command.title,
            tooltip: command.tooltip,
        };
        const apiCommand = this._lookupApiCommand(command.command);
        if (apiCommand) {
            // API command with return-value can be converted inplace
            result.id = apiCommand.internalId;
            result.arguments = apiCommand.args.map((arg, i) => arg.convert(command.arguments && command.arguments[i]));
        }
        else if (ide_core_common_1.arrays.isNonEmptyArray(command.arguments)) {
            // we have a contributed command with arguments. that
            // means we don't want to send the arguments around
            const id = ++this._cachIdPool;
            this._cache.set(id, command);
            disposables.add((0, ide_core_common_1.toDisposable)(() => {
                this._cache.delete(id);
            }));
            result.$ident = id;
            result.id = this._delegatingCommandId;
            result.arguments = [id];
        }
        return result;
    }
    fromInternal(command) {
        const id = util_1.ObjectIdentifier.of(command);
        if (typeof id === 'number') {
            return this._cache.get(id);
        }
        else {
            return {
                command: command.id,
                title: command.title,
                arguments: command.arguments,
            };
        }
    }
    _executeConvertedCommand(...args) {
        // 默认取 args 末尾的参数，不影响本身 Command 执行逻辑
        const actualCmd = this._cache.get(args[args.length - 1]);
        if (!actualCmd) {
            return Promise.reject('actual command NOT FOUND');
        }
        return tslib_1.__classPrivateFieldGet(this, _CommandsConverter_commands, "f").executeCommand(actualCmd.command, ...(actualCmd.arguments || []));
    }
}
exports.CommandsConverter = CommandsConverter;
_CommandsConverter_commands = new WeakMap();
//# sourceMappingURL=ext.host.command.js.map