"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExtHostDebug = exports.ExtHostDebugSession = void 0;
exports.createDebugApiFactory = createDebugApiFactory;
const ide_core_common_1 = require("@opensumi/ide-core-common");
const vscode_1 = require("../../../../common/vscode");
const ext_types_1 = require("../../../../common/vscode/ext-types");
const extension_debug_adapter_excutable_resolver_1 = require("./extension-debug-adapter-excutable-resolver");
const extension_debug_adapter_session_1 = require("./extension-debug-adapter-session");
const extension_debug_adapter_starter_1 = require("./extension-debug-adapter-starter");
const extension_debug_adapter_tracker_1 = require("./extension-debug-adapter-tracker");
const { Path } = ide_core_common_1.path;
function createDebugApiFactory(extHostDebugService) {
    const debug = {
        get activeDebugSession() {
            return extHostDebugService.activeDebugSession;
        },
        get activeDebugConsole() {
            return extHostDebugService.activeDebugConsole;
        },
        get breakpoints() {
            return extHostDebugService.breakpoints;
        },
        onDidStartDebugSession(listener, thisArg, disposables) {
            return extHostDebugService.onDidStartDebugSession(listener, thisArg, disposables);
        },
        onDidTerminateDebugSession(listener, thisArg, disposables) {
            return extHostDebugService.onDidTerminateDebugSession(listener, thisArg, disposables);
        },
        onDidChangeActiveDebugSession(listener, thisArg, disposables) {
            return extHostDebugService.onDidChangeActiveDebugSession(listener, thisArg, disposables);
        },
        onDidReceiveDebugSessionCustomEvent(listener, thisArg, disposables) {
            return extHostDebugService.onDidReceiveDebugSessionCustomEvent(listener, thisArg, disposables);
        },
        onDidChangeBreakpoints(listener, thisArgs, disposables) {
            return extHostDebugService.onDidChangeBreakpoints(listener, thisArgs, disposables);
        },
        /** @stubbed */
        get activeStackItem() {
            return undefined;
        },
        /** @stubbed */
        get onDidChangeActiveStackItem() {
            return ide_core_common_1.Event.None;
        },
        registerDebugConfigurationProvider(debugType, provider, triggerKind) {
            return extHostDebugService.registerDebugConfigurationProvider(debugType, provider, triggerKind || ext_types_1.DebugConfigurationProviderTriggerKind.Initial);
        },
        registerDebugAdapterDescriptorFactory(debugType, factory) {
            return extHostDebugService.registerDebugAdapterDescriptorFactory(debugType, factory);
        },
        registerDebugAdapterTrackerFactory(debugType, factory) {
            return extHostDebugService.registerDebugAdapterTrackerFactory(debugType, factory);
        },
        startDebugging(folder, nameOrConfig, parentSession) {
            return extHostDebugService.startDebugging(folder, nameOrConfig, parentSession);
        },
        stopDebugging(session) {
            return extHostDebugService.stopDebugging(session);
        },
        addBreakpoints(breakpoints) {
            return extHostDebugService.addBreakpoints(breakpoints);
        },
        removeBreakpoints(breakpoints) {
            return extHostDebugService.removeBreakpoints(breakpoints);
        },
        asDebugSourceUri(source, session) {
            return extHostDebugService.asDebugSourceUri(source, session);
        },
        registerDebugVisualizationTreeProvider() {
            return ext_types_1.Disposable.create(() => { });
        },
        registerDebugVisualizationProvider() {
            return ext_types_1.Disposable.create(() => { });
        },
    };
    return debug;
}
class ExtHostDebugSession {
    constructor(_debugServiceProxy, _id, _type, _name, _workspaceFolder, _configuration, _parentSession) {
        this._debugServiceProxy = _debugServiceProxy;
        this._id = _id;
        this._type = _type;
        this._name = _name;
        this._workspaceFolder = _workspaceFolder;
        this._configuration = _configuration;
        this._parentSession = _parentSession;
    }
    get id() {
        return this._id;
    }
    get type() {
        return this._type;
    }
    get name() {
        return this._name;
    }
    set name(name) {
        this._name = name;
    }
    get parentSession() {
        return this._parentSession;
    }
    get workspaceFolder() {
        return this._workspaceFolder;
    }
    get configuration() {
        return this._configuration;
    }
    customRequest(command, args) {
        return this._debugServiceProxy.$customRequest(this._id, command, args);
    }
    getDebugProtocolBreakpoint(breakpoint) {
        return this._debugServiceProxy.$getDebugProtocolBreakpoint(this._id, breakpoint.id);
    }
}
exports.ExtHostDebugSession = ExtHostDebugSession;
class ExtHostDebug {
    constructor(rpc, extHostConnectionService, extHostCommand, cp) {
        this.extHostConnectionService = extHostConnectionService;
        this.extHostCommand = extHostCommand;
        this.cp = cp;
        this.onDidChangeBreakpointsEmitter = new ide_core_common_1.Emitter();
        this.onDidChangeActiveDebugSessionEmitter = new ide_core_common_1.Emitter();
        this.onDidTerminateDebugSessionEmitter = new ide_core_common_1.Emitter();
        this.onDidStartDebugSessionEmitter = new ide_core_common_1.Emitter();
        this.onDidReceiveDebugSessionCustomEmitter = new ide_core_common_1.Emitter();
        this.sessions = new Map();
        this.debuggersContributions = new Map();
        this.contributionPaths = new Map();
        this.configurationProviders = new Map();
        this.trackerFactories = [];
        this.descriptorFactories = new Map();
        this.proxy = rpc.getProxy(vscode_1.MainThreadAPIIdentifier.MainThreadDebug);
        this.activeDebugConsole = {
            append: (value) => this.proxy.$appendToDebugConsole(value),
            appendLine: (value) => this.proxy.$appendLineToDebugConsole(value),
        };
    }
    // debug API
    get onDidChangeBreakpoints() {
        return this.onDidChangeBreakpointsEmitter.event;
    }
    get onDidReceiveDebugSessionCustomEvent() {
        return this.onDidReceiveDebugSessionCustomEmitter.event;
    }
    get onDidChangeActiveDebugSession() {
        return this.onDidChangeActiveDebugSessionEmitter.event;
    }
    get onDidTerminateDebugSession() {
        return this.onDidTerminateDebugSessionEmitter.event;
    }
    get onDidStartDebugSession() {
        return this.onDidStartDebugSessionEmitter.event;
    }
    /**
     * 注册贡献点
     * @param extensionPath 拓展路径
     * @param contributions 有效的贡献点
     */
    registerDebuggersContributions(extensionPath, contributions) {
        contributions.forEach((contribution) => {
            this.contributionPaths.set(contribution.type, extensionPath);
            this.debuggersContributions.set(contribution.type, contribution);
            this.proxy.$registerDebuggerContribution({
                type: contribution.type,
                label: contribution.label || contribution.type,
            });
        });
    }
    /**
     * 添加断点
     */
    async addBreakpoints(breakpoints) {
        this.proxy.$addBreakpoints(breakpoints);
    }
    /**
     * 移除断点
     * @param breakpoints
     */
    async removeBreakpoints(breakpoints) {
        this.proxy.$removeBreakpoints(breakpoints);
    }
    /**
     * 将调试资源转换为可访问的Uri
     * @param {vscode.DebugProtocolSource} source
     * @param {vscode.DebugSession} [session]
     */
    asDebugSourceUri(src, session) {
        const source = src;
        if (typeof source.sourceReference === 'number') {
            // src 可以通过 DAP 的 source 请求 转化为对应路径
            let debug = `debug:${encodeURIComponent(source.path || '')}`;
            let sep = '?';
            if (session) {
                debug += `${sep}session=${encodeURIComponent(session.id)}`;
                sep = '&';
            }
            debug += `${sep}ref=${source.sourceReference}`;
            return ext_types_1.Uri.parse(debug);
        }
        else if (source.path) {
            // src 就是一个本地路径
            return ext_types_1.Uri.file(source.path);
        }
        else {
            throw new Error("cannot create uri from DAP 'source' object; properties 'path' and 'sourceReference' are both missing.");
        }
    }
    /**
     * 启动调试
     * @param folder
     * @param nameOrConfig
     * @param parentSession
     */
    async startDebugging(folder, nameOrConfig, parentSessionOrOptions) {
        if (!parentSessionOrOptions ||
            (typeof parentSessionOrOptions === 'object' && 'configuration' in parentSessionOrOptions)) {
            return this.proxy.$startDebugging(folder, nameOrConfig, {
                parentSessionID: parentSessionOrOptions ? parentSessionOrOptions.id : undefined,
                compact: parentSessionOrOptions && !!parentSessionOrOptions.compact,
                lifecycleManagedByParent: parentSessionOrOptions && !!parentSessionOrOptions.lifecycleManagedByParent,
            });
        }
        return this.proxy.$startDebugging(folder, nameOrConfig, {
            parentSessionID: parentSessionOrOptions.parentSession ? parentSessionOrOptions.parentSession.id : undefined,
            repl: parentSessionOrOptions.consoleMode === ext_types_1.DebugConsoleMode.MergeWithParent ? 'mergeWithParent' : 'separate',
            compact: parentSessionOrOptions && !!parentSessionOrOptions.compact,
            lifecycleManagedByParent: parentSessionOrOptions && !!parentSessionOrOptions.lifecycleManagedByParent,
        });
    }
    stopDebugging(session) {
        return this.proxy.$stopDebugging(session ? session.id : undefined);
    }
    registerDebugConfigurationProvider(type, provider, trigger) {
        var _a, _b, _c;
        const providers = this.configurationProviders.get(type) || new Set();
        this.configurationProviders.set(type, providers);
        /**
         * ********
         * 由于目前还未实现 debugQuickAccess [https://github.com/microsoft/vscode/blob/414e5dbf1f870bc527ebc587cbbb5f6eee9bfba6/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts#L19]
         * 所以对于 DebugConfigurationProviderTriggerKind 的配置不作任何处理
         */
        providers.add({
            resolveDebugConfiguration: (_a = provider.resolveDebugConfiguration) === null || _a === void 0 ? void 0 : _a.bind(provider),
            provideDebugConfigurations: (_b = provider.provideDebugConfigurations) === null || _b === void 0 ? void 0 : _b.bind(provider),
            resolveDebugConfigurationWithSubstitutedVariables: (_c = provider.resolveDebugConfigurationWithSubstitutedVariables) === null || _c === void 0 ? void 0 : _c.bind(provider),
            type,
            triggerKind: trigger,
        });
        return ext_types_1.Disposable.create(() => {
            const providers = this.configurationProviders.get(type);
            if (providers) {
                providers.delete(provider);
                if (providers.size === 0) {
                    this.configurationProviders.delete(type);
                }
            }
        });
    }
    registerDebugAdapterDescriptorFactory(type, factory) {
        if (this.descriptorFactories.has(type)) {
            throw new Error(`Descriptor factory for ${type} has been already registered`);
        }
        this.descriptorFactories.set(type, factory);
        return ext_types_1.Disposable.create(() => this.descriptorFactories.delete(type));
    }
    registerDebugAdapterTrackerFactory(type, factory) {
        if (!factory) {
            return ext_types_1.Disposable.create(() => { });
        }
        this.trackerFactories.push([type, factory]);
        return ext_types_1.Disposable.create(() => {
            this.trackerFactories = this.trackerFactories.filter((tuple) => tuple[1] !== factory);
        });
    }
    // RPC methods
    async $onSessionCustomEvent(sessionId, event, body) {
        const session = this.sessions.get(sessionId);
        if (session) {
            this.onDidReceiveDebugSessionCustomEmitter.fire({ event, body, session });
        }
    }
    async $sessionDidStart(sessionId) {
        const session = this.sessions.get(sessionId);
        if (session) {
            this.onDidStartDebugSessionEmitter.fire(session);
        }
    }
    async $sessionDidDestroy(sessionId) {
        const session = this.sessions.get(sessionId);
        if (session) {
            this.onDidTerminateDebugSessionEmitter.fire(session);
        }
    }
    async $sessionDidChange(sessionId) {
        this.activeDebugSession = sessionId ? this.sessions.get(sessionId) : undefined;
        this.onDidChangeActiveDebugSessionEmitter.fire(this.activeDebugSession);
    }
    async $breakpointsDidChange(all, added, removed, changed) {
        this.breakpoints = all;
        this.onDidChangeBreakpointsEmitter.fire({ added, removed, changed });
    }
    async $createDebugSession(debugConfigurationDTO) {
        const { configuration, parent } = debugConfigurationDTO;
        const sessionId = (0, ide_core_common_1.uuid)();
        // TODO: 再通过 `debuggers.variables` 处理一遍 `configuration`
        // 否则如 `PickProcess` 等一些调试变量将不可用
        const debugSession = new ExtHostDebugSession(this.proxy, sessionId, configuration.type, configuration.name, undefined, configuration, parent ? this.sessions.get(parent) : undefined);
        const tracker = await this.createDebugAdapterTracker(debugSession);
        const communicationProvider = await this.createCommunicationProvider(debugSession, configuration);
        const debugAdapterSession = new extension_debug_adapter_session_1.ExtensionDebugAdapterSession(communicationProvider, tracker, debugSession);
        this.sessions.set(sessionId, debugAdapterSession);
        const connection = await this.extHostConnectionService.ensureConnection(sessionId);
        debugAdapterSession.start(connection);
        return sessionId;
    }
    async $terminateDebugSession(sessionId) {
        const debugAdapterSession = this.sessions.get(sessionId);
        if (debugAdapterSession) {
            // if (debugAdapterSession instanceof ExtensionDebugAdapterSession) {
            //   await debugAdapterSession.stop();
            // }
            this.onDidTerminateDebugSessionEmitter.fire(debugAdapterSession);
            this.sessions.delete(sessionId);
        }
    }
    async $getSupportedLanguages(debugType) {
        const contribution = this.debuggersContributions.get(debugType);
        return (contribution && contribution.languages) || [];
    }
    async $getSchemaAttributes(debugType) {
        const contribution = this.debuggersContributions.get(debugType);
        return (contribution && contribution.configurationAttributes) || [];
    }
    async $getConfigurationSnippets(debugType) {
        const contribution = this.debuggersContributions.get(debugType);
        return (contribution && contribution.configurationSnippets) || [];
    }
    async $getTerminalCreationOptions(debugType) {
        return this.doGetTerminalCreationOptions(debugType);
    }
    async doGetTerminalCreationOptions(debugType) {
        return undefined;
    }
    // 根据 debugType 和 triggerKind 来检查 DebugConfigurationProvider 的数目
    async $getDebugConfigurationProvidersCount(debugType, triggerKind) {
        let providers = this.configurationProviders.get(debugType);
        if (providers) {
            if (triggerKind) {
                providers = new Set(Array.from(providers).filter((provider) => provider.triggerKind === triggerKind));
            }
            return providers.size;
        }
        else {
            return 0;
        }
    }
    async $provideDebugConfigurations(debugType, workspaceFolderUri, token, triggerKind) {
        let result = [];
        let providers = this.configurationProviders.get(debugType);
        if (providers) {
            if (triggerKind) {
                providers = new Set(Array.from(providers).filter((provider) => provider.triggerKind === triggerKind));
            }
            for (const provider of providers) {
                if (provider.provideDebugConfigurations) {
                    result = result.concat((await provider.provideDebugConfigurations(this.toWorkspaceFolder(workspaceFolderUri), token)) || []);
                }
            }
        }
        return result;
    }
    async $resolveDebugConfigurations(debugConfiguration, workspaceFolderUri, token) {
        const current = debugConfiguration;
        const providers = this.configurationProviders.get(debugConfiguration.type);
        if (providers) {
            for (const provider of providers) {
                if (provider.resolveDebugConfiguration) {
                    const next = await provider.resolveDebugConfiguration(this.toWorkspaceFolder(workspaceFolderUri), current, token);
                    // 对齐 VS Code 实现，当 resolveDebugConfiguration 返回值为 `undefined` 时，中断调试进程
                    // 当返回值是 `null` 时，中断调试进程并打开 `launch.json` 文件
                    // ref: https://code.visualstudio.com/api/references/vscode-api#DebugConfigurationProvider
                    return next;
                }
            }
        }
        return current;
    }
    async $resolveDebugConfigurationWithSubstitutedVariables(debugConfiguration, workspaceFolderUri, token) {
        const current = debugConfiguration;
        const providers = this.configurationProviders.get(debugConfiguration.type);
        if (providers) {
            for (const provider of providers) {
                if (provider.resolveDebugConfigurationWithSubstitutedVariables) {
                    const next = await provider.resolveDebugConfigurationWithSubstitutedVariables(this.toWorkspaceFolder(workspaceFolderUri), current, token);
                    // 对齐 VS Code 实现，当 resolveDebugConfigurationWithSubstitutedVariables 返回值为 `undefined` 时，中断调试进程
                    // 当返回值是 `null` 时，中断调试进程并打开 `launch.json` 文件
                    // ref: https://code.visualstudio.com/api/references/vscode-api#DebugConfigurationProvider
                    return next;
                }
            }
        }
        return current;
    }
    async $registerDebuggerContributions(extensionFolder, contributions) {
        contributions.forEach((contribution) => {
            this.contributionPaths.set(contribution.type, extensionFolder);
            this.debuggersContributions.set(contribution.type, contribution);
        });
    }
    async $unregisterDebuggerContributions(contributions) {
        contributions.forEach((contribution) => {
            this.contributionPaths.delete(contribution.type);
            this.debuggersContributions.delete(contribution.type);
        });
    }
    async createDebugAdapterTracker(session) {
        return extension_debug_adapter_tracker_1.ExtensionDebugAdapterTracker.create(session, this.trackerFactories);
    }
    async getDebugAdapterDescriptor(session, executable) {
        const descriptorFactory = this.descriptorFactories.get(session.type);
        if (descriptorFactory) {
            return await descriptorFactory.createDebugAdapterDescriptor(session, executable);
        }
        return undefined;
    }
    async createCommunicationProvider(session, debugConfiguration) {
        const executable = await this.resolveDebugAdapterExecutable(debugConfiguration);
        const descriptor = await this.getDebugAdapterDescriptor(session, executable);
        if (descriptor) {
            // 'createDebugAdapterDescriptor' 方法会在Debug session启动时被调用，主要用于提供调试适配器需要的信息。
            // 返回的信息必须为 vscode.DebugAdapterDescriptor 类型
            // 当前支持两种调试适配器：
            // - 可以通过命令行执行特定命令的执行器，类型如 DebugAdapterExecutable
            // - 可以通过通信端口访问的适配器服务器，类型如 DebugAdapterServer
            // 如果没有实现对应方法，默认的行为如下：
            //
            //   createDebugAdapter(session: DebugSession, executable: DebugAdapterExecutable) {
            //      if (typeof session.configuration.debugServer === 'number') {
            //         return new DebugAdapterServer(session.configuration.debugServer);
            //      }
            //      return executable;
            //   }
            //  @param session The [debug session](#DebugSession) for which the debug adapter will be used.
            //  @param executable The debug adapter's executable information as specified in the package.json (or undefined if no such information exists).
            const adapterDescriptor = this.convertToDto(descriptor);
            if (adapterDescriptor) {
                const { type, adapter } = adapterDescriptor;
                switch (type) {
                    case 'server':
                        return (0, extension_debug_adapter_starter_1.connectDebugAdapter)(adapter);
                    case 'executable':
                        return (0, extension_debug_adapter_starter_1.startDebugAdapter)(adapter);
                    case 'pipeServer':
                        return (0, extension_debug_adapter_starter_1.namedPipeDebugAdapter)(adapter);
                    case 'implementation':
                        return (0, extension_debug_adapter_starter_1.directDebugAdapter)(session.id, adapter.implementation);
                    default:
                        break;
                }
            }
        }
        if (typeof debugConfiguration.debugServer === 'number') {
            return (0, extension_debug_adapter_starter_1.connectDebugAdapter)({ port: debugConfiguration.debugServer });
        }
        else {
            if (!executable) {
                throw new Error('It is not possible to provide debug adapter executable.');
            }
            return (0, extension_debug_adapter_starter_1.startDebugAdapter)(executable, this.cp);
        }
    }
    async resolveDebugAdapterExecutable(debugConfiguration) {
        const { type } = debugConfiguration;
        const contribution = this.debuggersContributions.get(type);
        if (contribution) {
            if (contribution.adapterExecutableCommand) {
                const executable = await this.extHostCommand.executeCommand(contribution.adapterExecutableCommand);
                if (executable) {
                    return executable;
                }
            }
            else {
                const contributionPath = this.contributionPaths.get(type);
                if (contributionPath) {
                    return (0, extension_debug_adapter_excutable_resolver_1.resolveDebugAdapterExecutable)(contributionPath, contribution);
                }
            }
        }
        throw new Error(`It is not possible to provide debug adapter executable for '${debugConfiguration.type}'.`);
    }
    toWorkspaceFolder(folder) {
        if (!folder) {
            return undefined;
        }
        const uri = ext_types_1.Uri.parse(folder);
        const path = new Path(uri.path);
        return {
            uri,
            name: path.base,
            index: 0,
        };
    }
    convertToDto(x) {
        if (x instanceof ext_types_1.DebugAdapterExecutable) {
            return {
                type: 'executable',
                adapter: x,
            };
        }
        else if (x instanceof ext_types_1.DebugAdapterServer) {
            return {
                type: 'server',
                adapter: x,
            };
        }
        else if (x instanceof ext_types_1.DebugAdapterNamedPipeServer) {
            return {
                type: 'pipeServer',
                adapter: x,
            };
        }
        else if (x instanceof ext_types_1.DebugAdapterInlineImplementation) {
            return {
                type: 'implementation',
                adapter: x,
            };
        }
        else {
            throw new Error('convertToDto unexpected type');
        }
    }
}
exports.ExtHostDebug = ExtHostDebug;
//# sourceMappingURL=ext.host.debug.js.map