import { __decorate, __param } from '../../../../../../../external/tslib/tslib.es6.js';
import { localizeWithPath, localize2WithPath } from '../../../../nls.js';
import { combinedDisposable } from '../../../../base/common/lifecycle.js';
import { URI } from '../../../../base/common/uri.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import '../../../../../../../override/vs/platform/dialogs/common/dialogs.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { IStorageService } from '../../../../platform/storage/common/storage.js';
import { IURLService } from '../../../../platform/url/common/url.js';
import { IHostService } from '../../host/browser/host.js';
import { IExtensionService } from '../common/extensions.js';
import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';
import { registerWorkbenchContribution2 } from '../../../common/contributions.js';
import { registerAction2, Action2, MenuId } from '../../../../platform/actions/common/actions.js';
import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js';
import { IsWebContext } from '../../../../platform/contextkey/common/contextkeys.js';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
import { IProductService } from '../../../../platform/product/common/productService.js';
import { disposableWindowInterval } from '../../../../base/browser/dom.js';
import { mainWindow } from '../../../../base/browser/window.js';
import { ICommandService } from '../../../../platform/commands/common/commands.js';
import { isCancellationError } from '../../../../base/common/errors.js';
import { INotificationService } from '../../../../platform/notification/common/notification.js';
import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
var ExtensionUrlBootstrapHandler_1;
const FIVE_MINUTES = 5 * 60 * 1000;
const THIRTY_SECONDS = 30 * 1000;
const URL_TO_HANDLE = 'extensionUrlHandler.urlToHandle';
const USER_TRUSTED_EXTENSIONS_CONFIGURATION_KEY = 'extensions.confirmedUriHandlerExtensionIds';
const USER_TRUSTED_EXTENSIONS_STORAGE_KEY = 'extensionUrlHandler.confirmedExtensions';
function isExtensionId(value) {
    return /^[a-z0-9][a-z0-9\-]*\.[a-z0-9][a-z0-9\-]*$/i.test(value);
}
class UserTrustedExtensionIdStorage {
    get extensions() {
        const userTrustedExtensionIdsJson = this.storageService.get(USER_TRUSTED_EXTENSIONS_STORAGE_KEY, 0 , '[]');
        try {
            return JSON.parse(userTrustedExtensionIdsJson);
        }
        catch {
            return [];
        }
    }
    constructor(storageService) {
        this.storageService = storageService;
    }
    has(id) {
        return this.extensions.indexOf(id) > -1;
    }
    add(id) {
        this.set([...this.extensions, id]);
    }
    set(ids) {
        this.storageService.store(USER_TRUSTED_EXTENSIONS_STORAGE_KEY, JSON.stringify(ids), 0 , 1 );
    }
}
const IExtensionUrlHandler = ( createDecorator('extensionUrlHandler'));
let ExtensionUrlHandler = class ExtensionUrlHandler {
    constructor(urlService, extensionService, dialogService, commandService, hostService, storageService, configurationService, telemetryService, notificationService, productService, workbenchEnvironmentService) {
        this.extensionService = extensionService;
        this.dialogService = dialogService;
        this.commandService = commandService;
        this.hostService = hostService;
        this.storageService = storageService;
        this.configurationService = configurationService;
        this.telemetryService = telemetryService;
        this.notificationService = notificationService;
        this.productService = productService;
        this.workbenchEnvironmentService = workbenchEnvironmentService;
        this.extensionHandlers = ( new Map());
        this.uriBuffer = ( new Map());
        this.userTrustedExtensionsStorage = ( new UserTrustedExtensionIdStorage(storageService));
        const interval = disposableWindowInterval(mainWindow, () => this.garbageCollect(), THIRTY_SECONDS);
        const urlToHandleValue = this.storageService.get(URL_TO_HANDLE, 1 );
        if (urlToHandleValue) {
            this.storageService.remove(URL_TO_HANDLE, 1 );
            this.handleURL(URI.revive(JSON.parse(urlToHandleValue)), { trusted: true });
        }
        this.disposable = combinedDisposable(urlService.registerHandler(this), interval);
        const cache = ExtensionUrlBootstrapHandler.cache;
        setTimeout(() => cache.forEach(([uri, option]) => this.handleURL(uri, option)));
    }
    async handleURL(uri, options) {
        if (!isExtensionId(uri.authority)) {
            return false;
        }
        const extensionId = uri.authority;
        this.telemetryService.publicLog2('uri_invoked/start', { extensionId });
        const initialHandler = this.extensionHandlers.get(ExtensionIdentifier.toKey(extensionId));
        let extensionDisplayName;
        if (!initialHandler) {
            const extension = await this.extensionService.getExtension(extensionId);
            if (!extension) {
                await this.handleUnhandledURL(uri, extensionId, options);
                return true;
            }
            else {
                extensionDisplayName = extension.displayName ?? '';
            }
        }
        else {
            extensionDisplayName = initialHandler.extensionDisplayName;
        }
        const trusted = options?.trusted
            || this.productService.trustedExtensionProtocolHandlers?.includes(extensionId)
            || this.didUserTrustExtension(ExtensionIdentifier.toKey(extensionId));
        if (!trusted) {
            let uriString = ( uri.toString(false));
            if (uriString.length > 40) {
                uriString = `${uriString.substring(0, 30)}...${uriString.substring(uriString.length - 5)}`;
            }
            const result = await this.dialogService.confirm({
                message: ( localizeWithPath(
                    'vs/workbench/services/extensions/browser/extensionUrlHandler',
                    'confirmUrl',
                    "Allow '{0}' extension to open this URI?",
                    extensionDisplayName
                )),
                checkbox: {
                    label: ( localizeWithPath(
                        'vs/workbench/services/extensions/browser/extensionUrlHandler',
                        'rememberConfirmUrl',
                        "Do not ask me again for this extension"
                    )),
                },
                detail: uriString,
                primaryButton: ( localizeWithPath(
                    'vs/workbench/services/extensions/browser/extensionUrlHandler',
                    { key: 'open', comment: ['&& denotes a mnemonic'] },
                    "&&Open"
                ))
            });
            if (!result.confirmed) {
                this.telemetryService.publicLog2('uri_invoked/cancel', { extensionId });
                return true;
            }
            if (result.checkboxChecked) {
                this.userTrustedExtensionsStorage.add(ExtensionIdentifier.toKey(extensionId));
            }
        }
        const handler = this.extensionHandlers.get(ExtensionIdentifier.toKey(extensionId));
        if (handler) {
            if (!initialHandler) {
                return await this.handleURLByExtension(extensionId, handler, uri, options);
            }
            return false;
        }
        const timestamp = ( new Date()).getTime();
        let uris = this.uriBuffer.get(ExtensionIdentifier.toKey(extensionId));
        if (!uris) {
            uris = [];
            this.uriBuffer.set(ExtensionIdentifier.toKey(extensionId), uris);
        }
        uris.push({ timestamp, uri });
        await this.extensionService.activateByEvent(`onUri:${ExtensionIdentifier.toKey(extensionId)}`, 1 );
        return true;
    }
    registerExtensionHandler(extensionId, handler) {
        this.extensionHandlers.set(ExtensionIdentifier.toKey(extensionId), handler);
        const uris = this.uriBuffer.get(ExtensionIdentifier.toKey(extensionId)) || [];
        for (const { uri } of uris) {
            this.handleURLByExtension(extensionId, handler, uri);
        }
        this.uriBuffer.delete(ExtensionIdentifier.toKey(extensionId));
    }
    unregisterExtensionHandler(extensionId) {
        this.extensionHandlers.delete(ExtensionIdentifier.toKey(extensionId));
    }
    async handleURLByExtension(extensionId, handler, uri, options) {
        this.telemetryService.publicLog2('uri_invoked/end', { extensionId: ExtensionIdentifier.toKey(extensionId) });
        return await handler.handleURL(uri, options);
    }
    async handleUnhandledURL(uri, extensionId, options) {
        this.telemetryService.publicLog2('uri_invoked/install_extension/start', { extensionId });
        try {
            await this.commandService.executeCommand('workbench.extensions.installExtension', extensionId, {
                justification: {
                    reason: `${( localizeWithPath(
                        'vs/workbench/services/extensions/browser/extensionUrlHandler',
                        'installDetail',
                        "This extension wants to open a URI:"
                    ))}\n${( uri.toString())}`,
                    action: ( localizeWithPath(
                        'vs/workbench/services/extensions/browser/extensionUrlHandler',
                        'openUri',
                        "Open URI"
                    ))
                },
                enable: true
            });
            this.telemetryService.publicLog2('uri_invoked/install_extension/accept', { extensionId });
        }
        catch (error) {
            if (isCancellationError(error)) {
                this.telemetryService.publicLog2('uri_invoked/install_extension/cancel', { extensionId });
            }
            else {
                this.telemetryService.publicLog2('uri_invoked/install_extension/error', { extensionId });
                this.notificationService.error(error);
            }
            return;
        }
        const extension = await this.extensionService.getExtension(extensionId);
        if (extension) {
            await this.handleURL(uri, { ...options, trusted: true });
        }
        else {
            this.telemetryService.publicLog2('uri_invoked/install_extension/reload', { extensionId, isRemote: !!this.workbenchEnvironmentService.remoteAuthority });
            const result = await this.dialogService.confirm({
                message: ( localizeWithPath(
                    'vs/workbench/services/extensions/browser/extensionUrlHandler',
                    'reloadAndHandle',
                    "Extension '{0}' is not loaded. Would you like to reload the window to load the extension and open the URL?",
                    extensionId
                )),
                primaryButton: ( localizeWithPath(
                    'vs/workbench/services/extensions/browser/extensionUrlHandler',
                    { key: 'reloadAndOpen', comment: ['&& denotes a mnemonic'] },
                    "&&Reload Window and Open"
                ))
            });
            if (!result.confirmed) {
                return;
            }
            this.storageService.store(URL_TO_HANDLE, JSON.stringify(uri.toJSON()), 1 , 1 );
            await this.hostService.reload();
        }
    }
    garbageCollect() {
        const now = ( new Date()).getTime();
        const uriBuffer = ( new Map());
        this.uriBuffer.forEach((uris, extensionId) => {
            uris = uris.filter(({ timestamp }) => now - timestamp < FIVE_MINUTES);
            if (uris.length > 0) {
                uriBuffer.set(extensionId, uris);
            }
        });
        this.uriBuffer = uriBuffer;
    }
    didUserTrustExtension(id) {
        if (( this.userTrustedExtensionsStorage.has(id))) {
            return true;
        }
        return this.getConfirmedTrustedExtensionIdsFromConfiguration().indexOf(id) > -1;
    }
    getConfirmedTrustedExtensionIdsFromConfiguration() {
        const trustedExtensionIds = this.configurationService.getValue(USER_TRUSTED_EXTENSIONS_CONFIGURATION_KEY);
        if (!Array.isArray(trustedExtensionIds)) {
            return [];
        }
        return trustedExtensionIds;
    }
    dispose() {
        this.disposable.dispose();
        this.extensionHandlers.clear();
        this.uriBuffer.clear();
    }
};
ExtensionUrlHandler = ( __decorate([
    ( __param(0, IURLService)),
    ( __param(1, IExtensionService)),
    ( __param(2, IDialogService)),
    ( __param(3, ICommandService)),
    ( __param(4, IHostService)),
    ( __param(5, IStorageService)),
    ( __param(6, IConfigurationService)),
    ( __param(7, ITelemetryService)),
    ( __param(8, INotificationService)),
    ( __param(9, IProductService)),
    ( __param(10, IWorkbenchEnvironmentService))
], ExtensionUrlHandler));
let ExtensionUrlBootstrapHandler = class ExtensionUrlBootstrapHandler {
    static { ExtensionUrlBootstrapHandler_1 = this; }
    static { this.ID = 'workbench.contrib.extensionUrlBootstrapHandler'; }
    static { this._cache = []; }
    static get cache() {
        ExtensionUrlBootstrapHandler_1.disposable.dispose();
        const result = ExtensionUrlBootstrapHandler_1._cache;
        ExtensionUrlBootstrapHandler_1._cache = [];
        return result;
    }
    constructor(urlService) {
        ExtensionUrlBootstrapHandler_1.disposable = urlService.registerHandler(this);
    }
    async handleURL(uri, options) {
        if (!isExtensionId(uri.authority)) {
            return false;
        }
        ExtensionUrlBootstrapHandler_1._cache.push([uri, options]);
        return true;
    }
};
ExtensionUrlBootstrapHandler = ExtensionUrlBootstrapHandler_1 = ( __decorate([
    ( __param(0, IURLService))
], ExtensionUrlBootstrapHandler));
registerWorkbenchContribution2(ExtensionUrlBootstrapHandler.ID, ExtensionUrlBootstrapHandler, 2 );
class ManageAuthorizedExtensionURIsAction extends Action2 {
    constructor() {
        super({
            id: 'workbench.extensions.action.manageAuthorizedExtensionURIs',
            title: ( localize2WithPath(
                'vs/workbench/services/extensions/browser/extensionUrlHandler',
                'manage',
                'Manage Authorized Extension URIs...'
            )),
            category: ( localize2WithPath(
                'vs/workbench/services/extensions/browser/extensionUrlHandler',
                'extensions',
                'Extensions'
            )),
            menu: {
                id: MenuId.CommandPalette,
                when: ( IsWebContext.toNegated())
            }
        });
    }
    async run(accessor) {
        const storageService = accessor.get(IStorageService);
        const quickInputService = accessor.get(IQuickInputService);
        const storage = ( new UserTrustedExtensionIdStorage(storageService));
        const items = ( storage.extensions.map(label => ({ label, picked: true })));
        if (items.length === 0) {
            await quickInputService.pick([{ label: ( localizeWithPath(
                'vs/workbench/services/extensions/browser/extensionUrlHandler',
                'no',
                'There are currently no authorized extension URIs.'
            )) }]);
            return;
        }
        const result = await quickInputService.pick(items, { canPickMany: true });
        if (!result) {
            return;
        }
        storage.set(( result.map(item => item.label)));
    }
}
registerAction2(ManageAuthorizedExtensionURIsAction);
export { ExtensionUrlHandler, IExtensionUrlHandler };
