"use strict";
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultDiagnosticSettings = exports.CachedContextProviderRegistry = exports.MutableContextProviderRegistry = exports.CoreContextProviderRegistry = exports.DefaultContextProvidersContainer = exports.ICompletionsDefaultContextProviders = exports.ICompletionsContextProviderRegistryService = void 0;
exports.telemetrizeContextItems = telemetrizeContextItems;
exports.matchContextItems = matchContextItems;
exports.useContextProviderAPI = useContextProviderAPI;
exports.getDefaultDiagnosticSettings = getDefaultDiagnosticSettings;
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
const configurationService_1 = require("../../../../../../platform/configuration/common/configurationService");
const languageContextProviderService_1 = require("../../../../../../platform/languageContextProvider/common/languageContextProviderService");
const nullExperimentationService_1 = require("../../../../../../platform/telemetry/common/nullExperimentationService");
const services_1 = require("../../../../../../util/common/services");
const errors_1 = require("../../../../../../util/vs/base/common/errors");
const instantiation_1 = require("../../../../../../util/vs/platform/instantiation/common/instantiation");
const config_1 = require("../config");
const featuresService_1 = require("../experiments/featuresService");
const cache_1 = require("../helpers/cache");
const logger_1 = require("../logger");
const runtimeMode_1 = require("../util/runtimeMode");
const asyncUtils_1 = require("./asyncUtils");
const contextProviderRegistryCpp_1 = require("./contextProviderRegistryCpp");
const contextProviderRegistryCSharp_1 = require("./contextProviderRegistryCSharp");
const contextProviderRegistryMultiLanguage_1 = require("./contextProviderRegistryMultiLanguage");
const contextProviderRegistryTs_1 = require("./contextProviderRegistryTs");
const contextItemSchemas_1 = require("./contextProviders/contextItemSchemas");
const contextProviderStatistics_1 = require("./contextProviderStatistics");
exports.ICompletionsContextProviderRegistryService = (0, services_1.createServiceIdentifier)('ICompletionsContextProviderRegistryService');
exports.ICompletionsDefaultContextProviders = (0, services_1.createServiceIdentifier)('ICompletionsDefaultContextProviders');
class DefaultContextProvidersContainer {
    constructor() {
        this.ids = [];
    }
    add(id) {
        this.ids.push(id);
    }
    getIds() {
        return this.ids;
    }
}
exports.DefaultContextProvidersContainer = DefaultContextProvidersContainer;
let CoreContextProviderRegistry = class CoreContextProviderRegistry {
    constructor(match, registryService, runtimeMode, instantiationService, logTarget, contextProviderStatistics) {
        this.match = match;
        this.registryService = registryService;
        this.runtimeMode = runtimeMode;
        this.instantiationService = instantiationService;
        this.logTarget = logTarget;
        this.contextProviderStatistics = contextProviderStatistics;
    }
    registerContextProvider(_provider) {
        throw new Error(`Should not be call. Use ILanguageContextProviderService`);
    }
    unregisterContextProvider(_providerId) {
        throw new Error(`Should not be call. Use ILanguageContextProviderService`);
    }
    get providers() {
        return this.registryService.getAllProviders([languageContextProviderService_1.ProviderTarget.Completions]).slice();
    }
    /**
     * Resolves all context providers for the given context.
     * Items returned will need to be filtered by schema.
     */
    async resolveAllProviders(completionId, opportunityId, documentContext, telemetryData, completionCancellationToken, data) {
        if (completionCancellationToken?.isCancellationRequested) {
            logger_1.logger.debug(this.logTarget, `Resolving context providers cancelled`);
            return [];
        }
        // Pass experiments here if needed.
        const activeExperiments = new Map();
        this.instantiationService.invokeFunction(contextProviderRegistryCSharp_1.fillInCSharpActiveExperiments, activeExperiments, telemetryData);
        const resolvedContextItems = [];
        const _providers = this.providers;
        if (_providers.length === 0) {
            return resolvedContextItems;
        }
        const providersWithMatchScore = await this.matchProviders(_providers, documentContext, telemetryData);
        const matchedProviders = providersWithMatchScore.filter(p => p[1] > 0);
        const unmatchedProviders = providersWithMatchScore.filter(p => p[1] <= 0);
        // For the unmatched providers, we still want to create a context item, but with an empty data array.
        unmatchedProviders.forEach(([provider, score]) => {
            const item = {
                providerId: provider.id,
                matchScore: score,
                resolution: 'none',
                resolutionTimeMs: 0,
                data: [],
            };
            resolvedContextItems.push(item);
        });
        if (matchedProviders.length === 0) {
            return resolvedContextItems;
        }
        if (completionCancellationToken?.isCancellationRequested) {
            logger_1.logger.debug(this.logTarget, `Resolving context providers cancelled`);
            return [];
        }
        // Fill in the active experiments for the matched providers.
        this.instantiationService.invokeFunction(contextProviderRegistryCpp_1.fillInCppVSCodeActiveExperiments, matchedProviders.map(p => p[0].id), activeExperiments, telemetryData);
        this.instantiationService.invokeFunction(contextProviderRegistryMultiLanguage_1.fillInMultiLanguageActiveExperiments, matchedProviders.map(p => p[0].id), activeExperiments, telemetryData);
        this.instantiationService.invokeFunction(contextProviderRegistryTs_1.fillInTsActiveExperiments, matchedProviders.map(p => p[0].id), activeExperiments, telemetryData);
        const providerCancellationTokenSource = new vscode_languageserver_protocol_1.CancellationTokenSource();
        if (completionCancellationToken) {
            const disposable = completionCancellationToken.onCancellationRequested(_ => {
                providerCancellationTokenSource.cancel();
                disposable.dispose();
            });
        }
        // Overriding this config with a value of 0 will create an infinite timeout (useful for debugging)
        const timeBudget = this.runtimeMode.isDebugEnabled() && !this.runtimeMode.isRunningInSimulation()
            ? 0
            : this.instantiationService.invokeFunction(getContextProviderTimeBudget, documentContext.languageId, telemetryData);
        const timeoutEnd = timeBudget > 0 ? Date.now() + timeBudget : Number.MAX_SAFE_INTEGER;
        let timeoutId;
        if (timeBudget > 0) {
            timeoutId = setTimeout(() => {
                providerCancellationTokenSource.cancel();
                providerCancellationTokenSource.dispose();
            }, timeBudget);
        }
        const resolutionMap = new Map();
        const request = {
            completionId,
            opportunityId,
            documentContext,
            activeExperiments,
            timeBudget,
            timeoutEnd,
            data,
        };
        for (const [provider] of matchedProviders) {
            const stats = this.contextProviderStatistics
                .getPreviousStatisticsForCompletion(completionId)
                ?.get(provider.id);
            if (stats) {
                request.previousUsageStatistics = stats;
            }
            const pendingContextItem = provider.resolver.resolve(request, providerCancellationTokenSource.token);
            resolutionMap.set(provider.id, pendingContextItem);
        }
        const statistics = this.contextProviderStatistics.getStatisticsForCompletion(completionId);
        statistics.setOpportunityId(opportunityId);
        const results = await (0, asyncUtils_1.resolveAll)(resolutionMap, providerCancellationTokenSource.token);
        // Once done, clear the timeout so that we don't cancel the request once it has finished.
        if (timeoutId) {
            clearTimeout(timeoutId);
        }
        for (const [provider, score] of matchedProviders) {
            const result = results.get(provider.id);
            if (result) {
                if (result.status === 'error') {
                    if (!(0, errors_1.isCancellationError)(result.reason)) {
                        logger_1.logger.error(this.logTarget, `Error resolving context from ${provider.id}: `, result.reason);
                    }
                    resolvedContextItems.push({
                        providerId: provider.id,
                        matchScore: score,
                        resolution: result.status,
                        resolutionTimeMs: result.resolutionTime,
                        data: [],
                    });
                }
                else {
                    const mergedItems = [...(result.value ?? [])];
                    if (result.status === 'none' || result.status === 'partial') {
                        logger_1.logger.info(this.logTarget, `Context provider ${provider.id} exceeded time budget of ${timeBudget}ms`);
                        if (provider.resolver.resolveOnTimeout) {
                            try {
                                const fallbackItems = provider.resolver.resolveOnTimeout(request);
                                if ((0, asyncUtils_1.isArrayOfT)(fallbackItems)) {
                                    mergedItems.push(...fallbackItems);
                                }
                                else if (fallbackItems) {
                                    mergedItems.push(fallbackItems);
                                }
                                if (mergedItems.length > 0) {
                                    result.status = 'partial';
                                }
                            }
                            catch (error) {
                                logger_1.logger.error(this.logTarget, `Error in fallback logic for context provider ${provider.id}: `, error);
                            }
                        }
                    }
                    const [supportedItems, invalidItems] = (0, contextItemSchemas_1.filterSupportedContextItems)(mergedItems);
                    if (invalidItems) {
                        logger_1.logger.error(this.logTarget, `Dropped ${invalidItems} context items from ${provider.id} due to invalid schema`);
                    }
                    const filteredItemsWithId = this.instantiationService.invokeFunction(contextItemSchemas_1.addOrValidateContextItemsIDs, supportedItems);
                    const resolvedContextItem = {
                        providerId: provider.id,
                        matchScore: score,
                        resolution: result.status,
                        resolutionTimeMs: result.resolutionTime,
                        data: filteredItemsWithId,
                    };
                    resolvedContextItems.push(resolvedContextItem);
                }
                statistics.setLastResolution(provider.id, result.status);
            }
            else {
                // This can't happen
                logger_1.logger.error(this.logTarget, `Context provider ${provider.id} not found in results`);
            }
        }
        // Sort the results by match score, so that the highest match score is first.
        return resolvedContextItems.sort((a, b) => b.matchScore - a.matchScore);
    }
    async matchProviders(providers, documentContext, telemetryData) {
        const activeContextProviders = this.instantiationService.invokeFunction(getActiveContextProviders, documentContext.languageId, telemetryData);
        const enableAllProviders = activeContextProviders.length === 1 && activeContextProviders[0] === '*';
        const providersWithScore = await Promise.all(providers.map(async (provider) => {
            if (!enableAllProviders && !activeContextProviders.includes(provider.id)) {
                return [provider, 0];
            }
            const matchScore = await this.match(this.instantiationService, provider.selector, documentContext);
            return [provider, matchScore];
        }));
        return providersWithScore;
    }
};
exports.CoreContextProviderRegistry = CoreContextProviderRegistry;
exports.CoreContextProviderRegistry = CoreContextProviderRegistry = __decorate([
    __param(1, languageContextProviderService_1.ILanguageContextProviderService),
    __param(2, runtimeMode_1.ICompletionsRuntimeModeService),
    __param(3, instantiation_1.IInstantiationService),
    __param(4, logger_1.ICompletionsLogTargetService),
    __param(5, contextProviderStatistics_1.ICompletionsContextProviderService)
], CoreContextProviderRegistry);
let MutableContextProviderRegistry = class MutableContextProviderRegistry extends CoreContextProviderRegistry {
    constructor(match, registryService, runtimeMode, instantiationService, logTarget, contextProviderStatistics) {
        super(match, registryService, runtimeMode, instantiationService, logTarget, contextProviderStatistics);
        this._providers = [];
    }
    registerContextProvider(provider) {
        if (provider.id.includes(',') || provider.id.includes('*')) {
            throw new Error(`A context provider id cannot contain a comma or an asterisk. The id ${provider.id} is invalid.`);
        }
        if (this._providers.find(p => p.id === provider.id)) {
            throw new Error(`A context provider with id ${provider.id} has already been registered`);
        }
        this._providers.push(provider);
    }
    unregisterContextProvider(providerId) {
        this._providers = this._providers.filter(p => p.id !== providerId);
    }
    get providers() {
        return this._providers.slice().concat(super.providers);
    }
};
exports.MutableContextProviderRegistry = MutableContextProviderRegistry;
exports.MutableContextProviderRegistry = MutableContextProviderRegistry = __decorate([
    __param(1, languageContextProviderService_1.ILanguageContextProviderService),
    __param(2, runtimeMode_1.ICompletionsRuntimeModeService),
    __param(3, instantiation_1.IInstantiationService),
    __param(4, logger_1.ICompletionsLogTargetService),
    __param(5, contextProviderStatistics_1.ICompletionsContextProviderService)
], MutableContextProviderRegistry);
let CachedContextProviderRegistry = class CachedContextProviderRegistry {
    constructor(registry, match, instantiationService) {
        // We don't need to cache many items, since initially we will only hold the cache for
        // the duration of a single completion request.
        this._cachedContextItems = new cache_1.LRUCacheMap(5);
        this.delegate = instantiationService.createInstance(registry, match);
    }
    registerContextProvider(provider) {
        this.delegate.registerContextProvider(provider);
    }
    unregisterContextProvider(providerId) {
        this.delegate.unregisterContextProvider(providerId);
    }
    get providers() {
        return this.delegate.providers;
    }
    async resolveAllProviders(completionId, opportunityId, documentContext, telemetryData, completionToken, data) {
        const cachedItems = this._cachedContextItems.get(completionId);
        if (completionId && cachedItems && cachedItems.length > 0) {
            return cachedItems;
        }
        const resolvedContextItems = await this.delegate.resolveAllProviders(completionId, opportunityId, documentContext, telemetryData, completionToken, data);
        if (resolvedContextItems.length > 0 && completionId) {
            this._cachedContextItems.set(completionId, resolvedContextItems);
        }
        return resolvedContextItems;
    }
};
exports.CachedContextProviderRegistry = CachedContextProviderRegistry;
exports.CachedContextProviderRegistry = CachedContextProviderRegistry = __decorate([
    __param(2, instantiation_1.IInstantiationService)
], CachedContextProviderRegistry);
function telemetrizeContextItems(contextProvider, completionId, resolvedContextItems) {
    const contextProviderStatistics = contextProvider.getStatisticsForCompletion(completionId);
    const contextProviderTelemetry = resolvedContextItems.map(p => {
        const { providerId, resolution, resolutionTimeMs, matchScore, data } = p;
        const providerStatistics = contextProviderStatistics.get(providerId);
        let usage = providerStatistics?.usage ?? 'none';
        // Unmatched providers are special: we still want to telemetrize them, but we don't
        // rely on the statistics since those will refer to the last time it was matched!
        if (matchScore <= 0 || resolution === 'none' || resolution === 'error') {
            usage = 'none';
        }
        const contextProviderTelemetry = {
            providerId,
            resolution,
            resolutionTimeMs,
            usage,
            usageDetails: providerStatistics?.usageDetails,
            matched: matchScore > 0,
            numResolvedItems: data.length,
        };
        const numUsedItems = providerStatistics?.usageDetails !== undefined
            ? providerStatistics?.usageDetails.filter(i => i.usage === 'full' || i.usage === 'partial' || i.usage === 'partial_content_excluded').length
            : undefined;
        const numPartiallyUsedItems = providerStatistics?.usageDetails !== undefined
            ? providerStatistics?.usageDetails.filter(i => i.usage === 'partial' || i.usage === 'partial_content_excluded').length
            : undefined;
        // TODO: Inline this above once promptlib has been removed
        if (numUsedItems !== undefined) {
            contextProviderTelemetry.numUsedItems = numUsedItems;
        }
        if (numPartiallyUsedItems !== undefined) {
            contextProviderTelemetry.numPartiallyUsedItems = numPartiallyUsedItems;
        }
        return contextProviderTelemetry;
    });
    return contextProviderTelemetry;
}
function matchContextItems(resolvedContextItem) {
    return resolvedContextItem.matchScore > 0 && resolvedContextItem.resolution !== 'error';
}
function getActiveContextProviders(accessor, languageId, telemetryData) {
    const expContextProviders = getExpContextProviders(accessor, languageId, telemetryData);
    const configContextProviders = (0, config_1.getConfig)(accessor, config_1.ConfigKey.ContextProviders) ?? [];
    if ((expContextProviders.length === 1 && expContextProviders[0] === '*') ||
        (configContextProviders.length === 1 && configContextProviders[0] === '*')) {
        return ['*'];
    }
    // Merge the two arrays and deduplicate
    const defaultContextProviders = accessor.get(exports.ICompletionsDefaultContextProviders).getIds();
    return Array.from(new Set([...defaultContextProviders, ...expContextProviders, ...configContextProviders]));
}
/**
 * This only returns the context providers that are enabled by EXP.
 * Use `getActiveContextProviders` to get the context providers that are enabled by both EXP and config.
 */
function getExpContextProviders(accessor, languageId, telemetryData) {
    if (accessor.get(runtimeMode_1.ICompletionsRuntimeModeService).isDebugEnabled()) {
        return ['*'];
    }
    const featuresService = accessor.get(featuresService_1.ICompletionsFeaturesService);
    const result = featuresService.contextProviders(telemetryData);
    const langSpecific = featuresService.getContextProviderExpSettings(languageId);
    if (langSpecific !== undefined) {
        for (const id of langSpecific.ids) {
            if (!result.includes(id)) {
                result.push(id);
            }
        }
    }
    return result;
}
function useContextProviderAPI(accessor, languageId, telemetryData) {
    return getActiveContextProviders(accessor, languageId, telemetryData).length > 0;
}
function getContextProviderTimeBudget(accessor, languageId, telemetryData) {
    const configTimeout = (0, config_1.getConfig)(accessor, config_1.ConfigKey.ContextProviderTimeBudget);
    if (configTimeout !== undefined && typeof configTimeout === 'number') {
        return configTimeout;
    }
    return accessor.get(featuresService_1.ICompletionsFeaturesService).contextProviderTimeBudget(languageId, telemetryData);
}
var DefaultDiagnosticSettings;
(function (DefaultDiagnosticSettings) {
    function from(value) {
        if (!value) {
            return undefined;
        }
        try {
            const parsed = JSON.parse(value);
            if (parsed.warnings === undefined && parsed.maxDiagnostics === undefined && parsed.maxLineDistance === undefined) {
                return undefined;
            }
            const warnings = getWarnings(parsed);
            const maxLineDistance = typeof parsed.maxLineDistance === 'number' && parsed.maxLineDistance >= 0
                ? parsed.maxLineDistance
                : 10;
            const maxDiagnostics = typeof parsed.maxDiagnostics === 'number' && parsed.maxDiagnostics > 0
                ? parsed.maxDiagnostics
                : 5;
            return {
                warnings,
                maxLineDistance,
                maxDiagnostics
            };
        }
        catch {
            return undefined;
        }
    }
    DefaultDiagnosticSettings.from = from;
    function getWarnings(value) {
        const warnings = value?.warnings;
        if (warnings === 'yes' || warnings === 'no' || warnings === 'yesIfNoErrors') {
            return warnings;
        }
        return 'no';
    }
})(DefaultDiagnosticSettings || (exports.DefaultDiagnosticSettings = DefaultDiagnosticSettings = {}));
function getDefaultDiagnosticSettings(accessor) {
    const configurationService = accessor.get(configurationService_1.IConfigurationService);
    const experimentationService = accessor.get(nullExperimentationService_1.IExperimentationService);
    const value = configurationService.getExperimentBasedConfig(configurationService_1.ConfigKey.TeamInternal.InlineCompletionsDefaultDiagnosticsOptions, experimentationService);
    if (typeof value !== 'string') {
        return undefined;
    }
    return DefaultDiagnosticSettings.from(value);
}
//# sourceMappingURL=contextProviderRegistry.js.map