"use strict";
var ViewExtProcessService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ViewExtProcessService = void 0;
const tslib_1 = require("tslib");
const di_1 = require("@opensumi/di");
const warning_1 = require("@opensumi/ide-components/lib/utils/warning");
const ide_connection_1 = require("@opensumi/ide-connection");
const ide_core_browser_1 = require("@opensumi/ide-core-browser");
const static_resource_1 = require("@opensumi/ide-core-browser/lib/static-resource");
const ide_core_common_1 = require("@opensumi/ide-core-common");
const ide_scm_1 = require("@opensumi/ide-scm");
const common_1 = require("../common");
const extension_service_1 = require("../common/extension.service");
const components_1 = require("./components");
const proxies_1 = require("./proxies");
const retargetEvents_1 = require("./retargetEvents");
const shadowRoot_1 = require("./shadowRoot");
const contribution_1 = require("./sumi-browser/contribution");
const { Path, posix } = ide_core_browser_1.path;
const LOAD_FAILED_CODE = 'load';
let ViewExtProcessService = ViewExtProcessService_1 = class ViewExtProcessService extends ide_core_common_1.Disposable {
    constructor() {
        super(...arguments);
        this.debugLogger = (0, ide_core_common_1.getDebugLogger)();
        this.extensions = [];
        // 被激活且在 contributes 中注册了 browserView 的 sumi 插件
        this.activatedViewExtensionMap = new Map();
        this.portalShadowRootMap = new Map();
        this.shadowRootBodyMap = new Map();
    }
    getExtension(extensionId) {
        return this.extensions.find((n) => n.id === extensionId);
    }
    async initExtension(extensions) {
        this.extensions = extensions;
    }
    // noop
    async getActivatedExtensions() {
        return [];
    }
    getProxy() {
        return;
    }
    // noop
    async $activateExtension(extensionPath) {
        return;
    }
    // noop
    async $getExtensions() {
        return this.extensions.map((n) => n.toJSON());
    }
    // noop
    async disposeProcess() {
        return;
    }
    // 注册视图插件的公共依赖, 如 React/ReactDOM
    activate() {
        const contributions = this.requireInterceptorContributionProvider.getContributions();
        for (const contribution of contributions) {
            contribution.registerRequireInterceptor(this.requireInterceptorService);
        }
        this.extendExtensionErrorStackTrace();
    }
    extendExtensionErrorStackTrace() {
        Error.stackTraceLimit = 100;
        Error.prepareStackTrace = (error, stackTrace) => {
            let extension;
            let stackTraceMessage = `Error: ${error.message}`;
            for (const call of stackTrace) {
                stackTraceMessage += `\n\tat ${call.toString()}`;
                if (!extension && call.isEval()) {
                    const extensionId = call.getEvalOrigin();
                    const maybeExtension = this.getExtension.apply(this, [extensionId]);
                    if (maybeExtension) {
                        extension = maybeExtension;
                        const columnNumber = call.getColumnNumber();
                        const lineNumber = call.getLineNumber();
                        stackTraceMessage =
                            `\n\tat ${extension.name} (${extension.workerScriptPath}:${lineNumber}:${columnNumber})` +
                                stackTraceMessage;
                    }
                }
            }
            if (extension) {
                const traceMessage = `${extension && extension.name} - ${error.name || 'Error'}: ${error.message || ''}${stackTraceMessage}`;
                this.debugLogger.log('get error', traceMessage);
                this.reportRuntimeError(error, extension, traceMessage);
                return traceMessage;
            }
            return error.stack;
        };
    }
    reportRuntimeError(err, extension, stackTraceMessage) {
        var _a;
        if (err && err.message) {
            this.reporterService.point(ide_core_common_1.REPORT_NAME.RUNTIME_ERROR_EXTENSION, extension.id, {
                stackTraceMessage,
                error: err.message,
                version: (_a = extension.packageJSON) === null || _a === void 0 ? void 0 : _a.version,
            });
        }
    }
    /**
     * @see [KtViewLocation](#KtViewLocation) view location
     */
    getRegisterViewKind(location) {
        return ViewExtProcessService_1.tabBarLocation.includes(location) ? 'replace' : 'add';
    }
    async activeExtension(extension, protocol) {
        var _a;
        const { extendConfig, packageJSON, contributes } = extension;
        // 对使用 kaitian.js 的老插件兼容
        // 因为可能存在即用了 kaitian.js 作为入口，又注册了 sumiContributes 贡献点的插件
        if ((_a = extendConfig === null || extendConfig === void 0 ? void 0 : extendConfig.browser) === null || _a === void 0 ? void 0 : _a.main) {
            (0, warning_1.warning)(false, '[Deprecated warning]: kaitian.js is deprecated, please use `package.json#sumiContributes` instead');
            await this.activateExtensionByDeprecatedExtendConfig(extension);
            return;
        }
        // 激活 workerMain/browserMain 相关部分
        if (packageJSON.sumiContributes && (contributes === null || contributes === void 0 ? void 0 : contributes.browserMain)) {
            await this.activeExtensionContributes(extension);
        }
    }
    async activeExtensionContributes(extension) {
        var _a;
        const { contributes } = extension;
        // 这里路径遵循 posix 方式，fsPath 会自动根据平台转换
        const browserModuleUri = new ide_core_browser_1.URI(extension.extensionLocation.with({
            path: posix.join(extension.extensionLocation.path, contributes.browserMain),
        }));
        const { moduleExports, proxiedHead } = await this.getExtensionModuleExports(browserModuleUri.toString(), extension);
        if (contributes.browserViews) {
            const { browserViews } = contributes;
            if (this.appConfig.useExperimentalShadowDom) {
                this.registerPortalShadowRoot(extension.id);
            }
            const viewsConfig = Object.keys(browserViews).reduce((config, location) => {
                config[location] = {
                    type: this.getRegisterViewKind(location),
                    view: browserViews[location].view.map(({ id, titleComponentId, title, ...other }) => ({
                        ...other,
                        title: (0, ide_core_common_1.replaceLocalizePlaceholder)(title, extension.id),
                        id,
                        component: this.getModuleExportsComponent(moduleExports, extension, id, proxiedHead),
                        titleComponent: titleComponentId &&
                            this.getModuleExportsComponent(moduleExports, extension, titleComponentId, proxiedHead),
                    })),
                };
                return config;
            }, {});
            this.registerBrowserComponent(viewsConfig, extension);
        }
        // toolbar
        if (contributes.toolbar && ((_a = contributes.toolbar) === null || _a === void 0 ? void 0 : _a.actions)) {
            for (const action of contributes.toolbar.actions) {
                if (action.type === 'button' && action.popoverComponent) {
                    const popoverComponent = moduleExports[action.popoverComponent];
                    if (!popoverComponent) {
                        this.logger.error(`Can not find CustomPopover from extension ${extension.id}, id: ${action.popoverComponent}`);
                        continue;
                    }
                    if (this.appConfig.useExperimentalShadowDom) {
                        const shadowComponent = (props) => (0, shadowRoot_1.getShadowRoot)(popoverComponent, extension, props, action.popoverComponent, proxiedHead, this.appConfig.componentCDNType, this.appConfig.extensionBrowserStyleSheet);
                        this.toolbarPopoverRegistry.registerComponent(`${extension.id}:${action.popoverComponent}`, shadowComponent);
                    }
                    else {
                        this.toolbarPopoverRegistry.registerComponent(`${extension.id}:${action.popoverComponent}`, popoverComponent);
                    }
                }
            }
        }
        const handleScmContributes = (repo) => {
            // scm
            if (contributes.scm && contributes.scm.additional) {
                const { input } = contributes.scm.additional;
                if (!input) {
                    return;
                }
                const { addonBefore, addonAfter } = input;
                const appendComponent = (addon, key) => {
                    addon.forEach((addonId) => {
                        const component = moduleExports[addonId];
                        if (!component) {
                            this.logger.error(`Can not find ${key} from extension ${extension.id}, id: ${component}`);
                        }
                        if (this.appConfig.useExperimentalShadowDom) {
                            const shadowComponent = (props) => (0, shadowRoot_1.getShadowRoot)(component, extension, props, addon, proxiedHead, this.appConfig.componentCDNType, this.appConfig.extensionBrowserStyleSheet);
                            repo.input.appendProps({
                                [key]: shadowComponent,
                            });
                        }
                        else {
                            repo.input.appendProps({
                                [key]: component,
                            });
                        }
                    });
                };
                if (addonBefore) {
                    appendComponent(addonBefore, 'addonBefore');
                }
                if (addonAfter) {
                    appendComponent(addonAfter, 'addonAfter');
                }
            }
        };
        this.addDispose(this.scmService.onDidAddRepository((repo) => {
            handleScmContributes(repo);
        }));
        this.scmService.repositories.forEach((repo) => {
            handleScmContributes(repo);
        });
    }
    /**
     * @deprecated 废弃的用法兼容
     * 对于老的 kaitian.js 的兼容激活
     */
    async activateExtensionByDeprecatedExtendConfig(extension) {
        const { extendConfig } = extension;
        this.logger.verbose(`register view by Deprecated config ${extension.id}`);
        const browserScriptURI = await this.staticResourceService.resolveStaticResource(ide_core_browser_1.URI.file(new Path(extension.path).join(extendConfig.browser.main).toString()));
        try {
            // 备注: 这里 extension 就是 rawExtension 了
            const rawExtension = extension;
            if (this.appConfig.useExperimentalShadowDom) {
                this.registerPortalShadowRoot(extension.id);
                const { moduleExports, proxiedHead } = await this.loadBrowserModuleUseInterceptor(browserScriptURI.toString(), extension, true /** use export default ... */);
                this.registerBrowserComponent(this.normalizeDeprecatedViewsConfig(moduleExports, extension, proxiedHead), rawExtension);
            }
            else {
                const { moduleExports } = await this.loadBrowserModule(browserScriptURI.toString(), extension, true /** use export default ... */);
                this.registerBrowserComponent(this.normalizeDeprecatedViewsConfig(moduleExports, extension), rawExtension);
            }
        }
        catch (err) {
            if (err.errorCode === LOAD_FAILED_CODE) {
                this.logger.error(`[Extension-Host] failed to load ${extension.name} - browser module, path: \n\n ${err.moduleId}`);
            }
            else {
                this.logger.error(err);
            }
        }
    }
    async getExtensionModuleExports(url, extension) {
        if (this.appConfig.useExperimentalShadowDom) {
            return await this.loadBrowserModuleUseInterceptor(url, extension, false /** use named exports ... */);
        }
        const { moduleExports } = await this.loadBrowserModule(url, extension, false);
        return { moduleExports };
    }
    registerBrowserComponent(browserExported, extension) {
        this.activatedViewExtensionMap.set(extension.path, extension);
        if (browserExported.default) {
            browserExported = browserExported.default;
        }
        const contribution = browserExported;
        extension.addDispose(this.injector.get(contribution_1.SumiBrowserContributionRunner, [extension, contribution]).run({
            getExtensionExtendService: this.getExtensionExtendService.bind(this),
        }));
    }
    getExtensionExtendService(extension, id) {
        const protocol = this.createExtensionExtendProtocol(extension, id);
        this.logger.log(`bind extend service for ${extension.id}:${id}`);
        return {
            extendProtocol: protocol,
            extendService: protocol.getProxy(common_1.MOCK_EXTENSION_EXTEND_PROXY_IDENTIFIER),
        };
    }
    createExtensionExtendProtocol(extension, componentId) {
        const { id: extensionId } = extension;
        const extendProtocol = new Proxy(Object.create(null), {
            get: (obj, prop) => {
                if (typeof prop === 'symbol') {
                    return obj[prop];
                }
                if (prop === 'getProxy') {
                    return () => {
                        let nodeProxy;
                        let workerProxy;
                        if (this.nodeExtensionService.protocol) {
                            nodeProxy = this.createExtendProxy(this.nodeExtensionService.protocol, extensionId);
                        }
                        if (this.workerExtensionService.protocol) {
                            workerProxy = this.createExtendProxy(this.workerExtensionService.protocol, extensionId);
                        }
                        return {
                            node: nodeProxy,
                            worker: workerProxy,
                        };
                    };
                }
                else if (prop === 'set') {
                    const componentProxyIdentifier = {
                        serviceId: `${common_1.EXTENSION_EXTEND_SERVICE_PREFIX}:${extensionId}:${componentId}`,
                    };
                    return (componentService) => {
                        const service = {};
                        for (const key in componentService) {
                            if (componentService.hasOwnProperty(key)) {
                                service[`$${key}`] = componentService[key];
                            }
                        }
                        this.logger.log('componentProxyIdentifier', componentProxyIdentifier, 'service', service);
                        if (this.workerExtensionService.protocol) {
                            this.workerExtensionService.protocol.set(componentProxyIdentifier, service);
                        }
                        if (this.nodeExtensionService.protocol) {
                            return this.nodeExtensionService.protocol.set(componentProxyIdentifier, service);
                        }
                    };
                }
            },
        });
        return extendProtocol;
    }
    createExtendProxy(protocol, extensionId) {
        const proxy = protocol.getProxy(new ide_connection_1.ProxyIdentifier(`${common_1.EXTENSION_EXTEND_SERVICE_PREFIX}:${extensionId}`));
        return this.dollarProxy(proxy);
    }
    dollarProxy(proxy) {
        return new Proxy(proxy, {
            get: (obj, prop) => {
                if (typeof prop === 'symbol') {
                    return obj[prop];
                }
                return obj[`$${prop}`];
            },
        });
    }
    getModuleExportsComponent(moduleExports, extension, id, proxiedHead) {
        if (!moduleExports[id]) {
            return () => (0, components_1.ExtensionNoExportsView)(extension.id, id);
        }
        if (this.appConfig.useExperimentalShadowDom) {
            return (props) => (0, shadowRoot_1.getShadowRoot)(moduleExports[id], extension, props, id, proxiedHead, this.appConfig.componentCDNType, this.appConfig.extensionBrowserStyleSheet);
        }
        return moduleExports[id];
    }
    registerPortalShadowRoot(extensionId) {
        if (!this.portalShadowRootMap.has(extensionId)) {
            const portal = document.createElement('div');
            portal.setAttribute('id', `portal-shadow-root-${extensionId}`);
            document.body.appendChild(portal);
            const portalRoot = portal.attachShadow({ mode: 'open' });
            // const body = document.createElement('body');
            // portalRoot.appendChild(body);
            (0, retargetEvents_1.retargetEvents)(portalRoot);
            this.portalShadowRootMap.set(extensionId, portalRoot);
        }
    }
    getPortalShadowRoot(extensionId) {
        return this.portalShadowRootMap.get(extensionId);
    }
    getShadowRootBody(id) {
        return this.shadowRootBodyMap.get(id);
    }
    doFetch(url) {
        const options = {};
        if (this.appConfig.extensionFetchCredentials) {
            options.credentials = this.appConfig.extensionFetchCredentials;
        }
        const pendingFetch = fetch(url, options);
        return pendingFetch;
    }
    getSumiAMDLoader(extension, rpcProtocol) {
        const _exports = {};
        const _module = { exports: _exports };
        const _require = (request) => {
            const interceptor = this.requireInterceptorService.getRequireInterceptor(request);
            return interceptor === null || interceptor === void 0 ? void 0 : interceptor.load({
                injector: this.injector,
                extension,
                rpcProtocol,
            });
        };
        return { _module, _exports, _require };
    }
    async loadBrowserModule(browserPath, extension, defaultExports) {
        const loadTimer = this.reporterService.time(ide_core_common_1.REPORT_NAME.LOAD_EXTENSION_MAIN);
        const pendingFetch = await this.doFetch(decodeURIComponent(browserPath));
        loadTimer.timeEnd(extension.id);
        const { _module, _exports, _require } = this.getSumiAMDLoader(extension, this.nodeExtensionService.protocol);
        const initFn = new Function('module', 'exports', 'require', (await pendingFetch.text()) + `\n//# sourceURL=${extension.id}`);
        initFn(_module, _exports, _require);
        return {
            moduleExports: defaultExports ? _module.exports.default : _module.exports,
        };
    }
    // view config related starts
    normalizeDeprecatedViewsConfig(moduleExports, extension, proxiedHead) {
        if (this.appConfig.useExperimentalShadowDom) {
            return Object.keys(moduleExports)
                .filter((key) => moduleExports[key] && Array.isArray(moduleExports[key].component))
                .reduce((pre, cur) => {
                pre[cur] = {
                    view: moduleExports[cur].component.map(({ panel, id, ...other }) => ({
                        ...other,
                        id,
                        component: (props) => (0, shadowRoot_1.getShadowRoot)(panel, extension, props, id, proxiedHead, this.appConfig.componentCDNType, this.appConfig.extensionBrowserStyleSheet),
                    })),
                };
                return pre;
            }, {});
        }
        else {
            const views = moduleExports.default ? moduleExports.default : moduleExports;
            return Object.keys(views)
                .filter((key) => views[key] && Array.isArray(views[key].component))
                .reduce((config, location) => {
                config[location] = {
                    view: views[location].component.map(({ panel, ...other }) => ({
                        ...other,
                        component: panel,
                    })),
                };
                return config;
            }, {});
        }
    }
    /**
     * 对于使用 kaitian.js 方式注册的 UI ，使用 default 导出
     * @example
     * ```ts
     * export default {
     *    left: {...},
     *    right: {...}
     * }
     * ```
     * 使用 browserViews Contributes 注册的 UI，不使用 default 导出，因为这种入口只导出组件，不包含 UI 相关配置
     * @example
     * ```ts
     * export const Component = {...};
     * export const ComponentB = {...};
     * ```
     */
    async loadBrowserModuleUseInterceptor(browserPath, extension, defaultExports) {
        const loadTimer = this.reporterService.time(ide_core_common_1.REPORT_NAME.LOAD_EXTENSION_MAIN);
        const pendingFetch = await this.doFetch(decodeURIComponent(browserPath));
        loadTimer.timeEnd(extension.id);
        const { _module, _exports, _require } = this.getSumiAMDLoader(extension, this.nodeExtensionService.protocol);
        const stylesCollection = [];
        const proxiedHead = document.createElement('head');
        const proxiedDocument = (0, proxies_1.createProxiedDocument)(proxiedHead);
        const proxiedWindow = (0, proxies_1.createProxiedWindow)(proxiedDocument, proxiedHead);
        const initFn = new Function('module', 'exports', 'require', 'styles', 'document', 'window', (await pendingFetch.text()) + `\n//# sourceURL=${extension.id}`);
        initFn(_module, _exports, _require, stylesCollection, proxiedDocument, proxiedWindow);
        return {
            moduleExports: defaultExports ? _module.exports.default : _module.exports,
            proxiedHead,
        };
    }
};
exports.ViewExtProcessService = ViewExtProcessService;
/**
 * TODO: 支持底部面板多视图展示
 * should replace view component
 */
ViewExtProcessService.tabBarLocation = ['left', 'right'];
tslib_1.__decorate([
    (0, di_1.Autowired)(di_1.INJECTOR_TOKEN),
    tslib_1.__metadata("design:type", di_1.Injector)
], ViewExtProcessService.prototype, "injector", void 0);
tslib_1.__decorate([
    (0, di_1.Autowired)(ide_core_common_1.ILogger),
    tslib_1.__metadata("design:type", Object)
], ViewExtProcessService.prototype, "logger", void 0);
tslib_1.__decorate([
    (0, di_1.Autowired)(ide_core_browser_1.AppConfig),
    tslib_1.__metadata("design:type", Object)
], ViewExtProcessService.prototype, "appConfig", void 0);
tslib_1.__decorate([
    (0, di_1.Autowired)(ide_core_browser_1.IToolbarPopoverRegistry),
    tslib_1.__metadata("design:type", Object)
], ViewExtProcessService.prototype, "toolbarPopoverRegistry", void 0);
tslib_1.__decorate([
    (0, di_1.Autowired)(extension_service_1.AbstractWorkerExtProcessService),
    tslib_1.__metadata("design:type", extension_service_1.AbstractWorkerExtProcessService)
], ViewExtProcessService.prototype, "workerExtensionService", void 0);
tslib_1.__decorate([
    (0, di_1.Autowired)(extension_service_1.AbstractNodeExtProcessService),
    tslib_1.__metadata("design:type", extension_service_1.AbstractNodeExtProcessService)
], ViewExtProcessService.prototype, "nodeExtensionService", void 0);
tslib_1.__decorate([
    (0, di_1.Autowired)(),
    tslib_1.__metadata("design:type", static_resource_1.StaticResourceService)
], ViewExtProcessService.prototype, "staticResourceService", void 0);
tslib_1.__decorate([
    (0, di_1.Autowired)(common_1.RequireInterceptorContribution),
    tslib_1.__metadata("design:type", Object)
], ViewExtProcessService.prototype, "requireInterceptorContributionProvider", void 0);
tslib_1.__decorate([
    (0, di_1.Autowired)(common_1.IRequireInterceptorService),
    tslib_1.__metadata("design:type", Object)
], ViewExtProcessService.prototype, "requireInterceptorService", void 0);
tslib_1.__decorate([
    (0, di_1.Autowired)(ide_core_common_1.IReporterService),
    tslib_1.__metadata("design:type", Object)
], ViewExtProcessService.prototype, "reporterService", void 0);
tslib_1.__decorate([
    (0, di_1.Autowired)(ide_scm_1.SCMService),
    tslib_1.__metadata("design:type", ide_scm_1.SCMService)
], ViewExtProcessService.prototype, "scmService", void 0);
exports.ViewExtProcessService = ViewExtProcessService = ViewExtProcessService_1 = tslib_1.__decorate([
    (0, di_1.Injectable)()
], ViewExtProcessService);
//# sourceMappingURL=extension-view.service.js.map