"use strict";
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.ForceMultiLine = exports.GhostTextComputer = void 0;
exports.getGhostText = getGhostText;
const logService_1 = require("../../../../../../platform/log/common/logService");
const telemetry_1 = require("../../../../../../platform/telemetry/common/telemetry");
const crypto_1 = require("../../../../../../util/common/crypto");
const uuid_1 = require("../../../../../../util/vs/base/common/uuid");
const instantiation_1 = require("../../../../../../util/vs/platform/instantiation/common/instantiation");
const inlineSuggestion_1 = require("../../../../../xtab/common/inlineSuggestion");
const tokenization_1 = require("../../../prompt/src/tokenization");
const src_1 = require("../../../types/src");
const completionNotifier_1 = require("../completionNotifier");
const config_1 = require("../config");
const featuresService_1 = require("../experiments/featuresService");
const logger_1 = require("../logger");
const networking_1 = require("../networking");
const config_2 = require("../openai/config");
const progress_1 = require("../progress");
const contextProviderBridge_1 = require("../prompt/components/contextProviderBridge");
const contextProviderStatistics_1 = require("../prompt/contextProviderStatistics");
const parseBlock_1 = require("../prompt/parseBlock");
const prompt_1 = require("../prompt/prompt");
const repository_1 = require("../prompt/repository");
const suggestions_1 = require("../suggestions/suggestions");
const telemetry_2 = require("../telemetry");
const textDocument_1 = require("../textDocument");
const async_1 = require("../util/async");
const asyncCompletions_1 = require("./asyncCompletions");
const blockTrimmer_1 = require("./blockTrimmer");
const completionsCache_1 = require("./completionsCache");
const completionsFromNetwork_1 = require("./completionsFromNetwork");
const current_1 = require("./current");
const ghostTextStrategy_1 = require("./ghostTextStrategy");
const resultType_1 = require("./resultType");
const telemetry_3 = require("./telemetry");
const defaultOptions = {
    isCycling: false,
    promptOnly: false,
    isSpeculative: false,
};
function getRemainingDebounceMs(accessor, opts, telemetry) {
    const featuresService = accessor.get(featuresService_1.ICompletionsFeaturesService);
    const debounce = (0, config_1.getConfig)(accessor, config_1.ConfigKey.CompletionsDebounce) ??
        featuresService.completionsDebounce(telemetry) ??
        opts.debounceMs;
    if (debounce === undefined) {
        return 0;
    }
    const elapsed = (0, telemetry_2.now)() - telemetry.issuedTime;
    return Math.max(0, debounce - elapsed);
}
function isCompletionRequestCancelled(currentGhostText, requestId, cancellationToken) {
    return cancellationToken?.isCancellationRequested || requestId !== currentGhostText.currentRequestId;
}
let GhostTextComputer = class GhostTextComputer {
    constructor(instantiationService, telemetryService, notifierService, contextProviderBridge, currentGhostText, contextproviderStatistics, asyncCompletionManager, completionsFeaturesService, logTarget, statusReporter, logService) {
        this.instantiationService = instantiationService;
        this.telemetryService = telemetryService;
        this.notifierService = notifierService;
        this.contextProviderBridge = contextProviderBridge;
        this.currentGhostText = currentGhostText;
        this.contextproviderStatistics = contextproviderStatistics;
        this.asyncCompletionManager = asyncCompletionManager;
        this.completionsFeaturesService = completionsFeaturesService;
        this.logTarget = logTarget;
        this.statusReporter = statusReporter;
        this.logService = logService;
    }
    async getGhostText(completionState, token, options, logContext, telemetryBuilder, parentLogger) {
        const id = (0, uuid_1.generateUuid)();
        const logger = parentLogger.createSubLogger(['GhostTextComputer#getGhostText']);
        this.currentGhostText.currentRequestId = id;
        const telemetryData = await this.instantiationService.invokeFunction(createTelemetryWithExp, completionState.textDocument, id, options);
        // A CLS consumer has an LSP bug where it erroneously makes method requests before `initialize` has returned, which
        // means we can't use `initialize` to actually initialize anything expensive.  This the primary user of the
        // tokenizer, so settle for initializing here instead.  We don't use waitForTokenizers() because in the event of a
        // tokenizer load failure, that would spam handleException() on every request.
        await tokenization_1.initializeTokenizers.catch(() => { });
        try {
            this.contextProviderBridge.schedule(completionState, id, options?.opportunityId ?? '', telemetryData, token, options);
            this.notifierService.notifyRequest(completionState, id, telemetryData, token, options);
            const result = await this.getGhostTextWithoutAbortHandling(completionState, id, telemetryData, token, options, logContext, telemetryBuilder, logger);
            const statistics = this.contextproviderStatistics.getStatisticsForCompletion(id);
            const opportunityId = options?.opportunityId ?? 'unknown';
            for (const [providerId, statistic] of statistics.getAllUsageStatistics()) {
                /* __GDPR__
                    "context-provider.completion-stats" : {
                        "owner": "dirkb",
                        "comment": "Telemetry for copilot inline completion context",
                        "requestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The request correlation id" },
                        "opportunityId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The opportunity id" },
                        "providerId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The context provider id" },
                        "resolution": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The resolution of the context" },
                        "usage": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "How the context was used" },
                        "usageDetails": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Additional details about the usage as a JSON string" }
                    }
                */
                this.telemetryService.sendMSFTTelemetryEvent('context-provider.completion-stats', {
                    requestId: id,
                    opportunityId,
                    providerId,
                    resolution: statistic.resolution,
                    usage: statistic.usage,
                    usageDetails: JSON.stringify(statistic.usageDetails),
                }, {});
            }
            return result;
        }
        catch (e) {
            // The cancellation token may be called after the request is done but while we still process data.
            // The underlying implementation catches abort errors for specific scenarios but we still have uncovered paths.
            // To avoid returning an error to the editor, this acts as an fault barrier here.
            if ((0, networking_1.isAbortError)(e)) {
                return {
                    type: 'canceled',
                    reason: 'aborted at unknown location',
                    telemetryData: (0, telemetry_3.mkCanceledResultTelemetry)(telemetryData, {
                        cancelledNetworkRequest: true,
                    }),
                };
            }
            throw e;
        }
    }
    async getGhostTextWithoutAbortHandling(completionState, ourRequestId, preIssuedTelemetryDataWithExp, cancellationToken, options, logContext, telemetryBuilder, parentLogger) {
        const logger = parentLogger.createSubLogger(['GhostTextComputer#getGhostTextWithoutAbortHandling']);
        let start = preIssuedTelemetryDataWithExp.issuedTime; // Start before getting exp assignments
        const performanceMetrics = [];
        /** Internal helper to record performance measurements. Mutates performanceMetrics and start. */
        function recordPerformance(name) {
            const next = (0, telemetry_2.now)();
            performanceMetrics.push([name, next - start]);
            start = next;
        }
        recordPerformance('telemetry');
        if (isCompletionRequestCancelled(this.currentGhostText, ourRequestId, cancellationToken)) {
            return {
                type: 'abortedBeforeIssued',
                reason: 'cancelled before extractPrompt',
                telemetryData: (0, telemetry_3.mkBasicResultTelemetry)(preIssuedTelemetryDataWithExp),
            };
        }
        const inlineSuggestion = isInlineSuggestion(completionState.textDocument, completionState.position);
        if (inlineSuggestion === undefined) {
            logger.debug('Completions do not trigger in the middle of the line');
            return {
                type: 'abortedBeforeIssued',
                reason: 'Invalid middle of the line',
                telemetryData: (0, telemetry_3.mkBasicResultTelemetry)(preIssuedTelemetryDataWithExp),
            };
        }
        const engineInfo = this.instantiationService.invokeFunction(config_2.getEngineRequestInfo, preIssuedTelemetryDataWithExp);
        const ghostTextOptions = { ...defaultOptions, ...options, tokenizer: engineInfo.tokenizer };
        const prompt = await this.instantiationService.invokeFunction(prompt_1.extractPrompt, ourRequestId, completionState, preIssuedTelemetryDataWithExp, undefined, ghostTextOptions);
        recordPerformance('prompt');
        logContext.setPrompt(prompt_1.PromptResponse.toString(prompt));
        if (prompt.type === 'copilotContentExclusion') {
            logger.debug('Copilot not available, due to content exclusion');
            return {
                type: 'abortedBeforeIssued',
                reason: 'Copilot not available due to content exclusion',
                telemetryData: (0, telemetry_3.mkBasicResultTelemetry)(preIssuedTelemetryDataWithExp),
            };
        }
        if (prompt.type === 'contextTooShort') {
            logger.debug('Breaking, not enough context');
            return {
                type: 'abortedBeforeIssued',
                reason: 'Not enough context',
                telemetryData: (0, telemetry_3.mkBasicResultTelemetry)(preIssuedTelemetryDataWithExp),
            };
        }
        if (prompt.type === 'promptError') {
            logger.debug('Error while building the prompt');
            return {
                type: 'abortedBeforeIssued',
                reason: 'Error while building the prompt',
                telemetryData: (0, telemetry_3.mkBasicResultTelemetry)(preIssuedTelemetryDataWithExp),
            };
        }
        if (ghostTextOptions.promptOnly) {
            return { type: 'promptOnly', reason: 'Breaking, promptOnly set to true', prompt: prompt };
        }
        if (prompt.type === 'promptCancelled') {
            logger.debug('Cancelled during extractPrompt');
            return {
                type: 'abortedBeforeIssued',
                reason: 'Cancelled during extractPrompt',
                telemetryData: (0, telemetry_3.mkBasicResultTelemetry)(preIssuedTelemetryDataWithExp),
            };
        }
        if (prompt.type === 'promptTimeout') {
            logger.debug('Timeout during extractPrompt');
            return {
                type: 'abortedBeforeIssued',
                reason: 'Timeout',
                telemetryData: (0, telemetry_3.mkBasicResultTelemetry)(preIssuedTelemetryDataWithExp),
            };
        }
        if (prompt.prompt.prefix.length === 0 && prompt.prompt.suffix.length === 0) {
            logger.debug('Error empty prompt');
            return {
                type: 'abortedBeforeIssued',
                reason: 'Empty prompt',
                telemetryData: (0, telemetry_3.mkBasicResultTelemetry)(preIssuedTelemetryDataWithExp),
            };
        }
        const debounce = this.instantiationService.invokeFunction(getRemainingDebounceMs, ghostTextOptions, preIssuedTelemetryDataWithExp);
        if (debounce > 0) {
            logger.debug(`Debouncing ghost text request for ${debounce}ms`);
            await (0, async_1.delay)(debounce);
            if (isCompletionRequestCancelled(this.currentGhostText, ourRequestId, cancellationToken)) {
                return {
                    type: 'abortedBeforeIssued',
                    reason: 'cancelled after debounce',
                    telemetryData: (0, telemetry_3.mkBasicResultTelemetry)(preIssuedTelemetryDataWithExp),
                };
            }
        }
        return this.statusReporter.withProgress(async () => {
            const [prefix] = (0, prompt_1.trimLastLine)(completionState.textDocument.getText(textDocument_1.LocationFactory.range(textDocument_1.LocationFactory.position(0, 0), completionState.position)));
            logger.trace(`Starting ghost text computation, prefix length: ${prefix.length}`);
            const hasAcceptedCurrentCompletion = this.currentGhostText.hasAcceptedCurrentCompletion(prefix, prompt.prompt.suffix);
            logger.trace(`hasAcceptedCurrentCompletion: ${hasAcceptedCurrentCompletion}`);
            const originalPrompt = prompt.prompt;
            const ghostTextStrategy = await this.instantiationService.invokeFunction(ghostTextStrategy_1.getGhostTextStrategy, completionState, prefix, prompt, inlineSuggestion, hasAcceptedCurrentCompletion, preIssuedTelemetryDataWithExp);
            recordPerformance('strategy');
            logger.trace(`Ghost text strategy: blockMode=${ghostTextStrategy.blockMode}, requestMultiline=${ghostTextStrategy.requestMultiline}, stop=${ghostTextStrategy.stop}, maxTokens=${ghostTextStrategy.maxTokens}`);
            let choices = this.instantiationService.invokeFunction(getLocalInlineSuggestion, prefix, originalPrompt, ghostTextStrategy.requestMultiline);
            logger.trace(`Local cache lookup: ${choices ? `found ${choices[0].length} choices` : 'no cached choices'}`);
            recordPerformance('cache');
            const repoInfo = this.instantiationService.invokeFunction(repository_1.extractRepoInfoInBackground, completionState.textDocument.uri);
            const requestContext = {
                blockMode: ghostTextStrategy.blockMode,
                languageId: completionState.textDocument.detectedLanguageId,
                repoInfo: repoInfo,
                engineModelId: engineInfo.modelId,
                ourRequestId,
                prefix,
                prompt: prompt.prompt,
                multiline: ghostTextStrategy.requestMultiline,
                indentation: (0, parseBlock_1.contextIndentation)(completionState.textDocument, completionState.position),
                isCycling: ghostTextOptions.isCycling,
                headers: engineInfo.headers,
                stop: ghostTextStrategy.stop,
                maxTokens: ghostTextStrategy.maxTokens,
                afterAccept: hasAcceptedCurrentCompletion,
            };
            // Add headers to identify async completions and speculative requests
            requestContext.headers = {
                ...requestContext.headers,
                'X-Copilot-Async': 'true',
                'X-Copilot-Speculative': ghostTextOptions.isSpeculative ? 'true' : 'false',
            };
            // this will be used as basis for the choice telemetry data
            const telemetryData = this.instantiationService.invokeFunction(telemetryIssued, completionState.textDocument, requestContext, completionState.position, prompt, preIssuedTelemetryDataWithExp, engineInfo, ghostTextOptions);
            // Wait before requesting more completions if there is a candidate
            // completion request in flight. Does not wait for cycling requests or
            // if there is a cached completion.
            if (choices === undefined &&
                !ghostTextOptions.isCycling &&
                this.asyncCompletionManager.shouldWaitForAsyncCompletions(prefix, prompt.prompt)) {
                logger.trace('No cached choices, waiting for async completions from in-flight request');
                const choice = await this.asyncCompletionManager.getFirstMatchingRequestWithTimeout(ourRequestId, prefix, prompt.prompt, ghostTextOptions.isSpeculative, telemetryData);
                recordPerformance('asyncWait');
                if (choice) {
                    logger.trace('Received choice from async completion');
                    const forceSingleLine = !ghostTextStrategy.requestMultiline;
                    const trimmedChoice = (0, completionsFromNetwork_1.makeGhostAPIChoice)(choice[0], { forceSingleLine });
                    choices = [[trimmedChoice], resultType_1.ResultType.Async];
                }
                else {
                    logger.trace('No matching async completion found within timeout');
                }
                if (isCompletionRequestCancelled(this.currentGhostText, ourRequestId, cancellationToken)) {
                    logger.debug('Cancelled before requesting a new completion');
                    return {
                        type: 'abortedBeforeIssued',
                        reason: 'Cancelled after waiting for async completion',
                        telemetryData: (0, telemetry_3.mkBasicResultTelemetry)(telemetryData),
                    };
                }
            }
            else {
                logger.trace('Skipping wait for async completions');
            }
            const isMoreMultiline = ghostTextStrategy.blockMode === config_1.BlockMode.MoreMultiline &&
                blockTrimmer_1.BlockTrimmer.isSupported(completionState.textDocument.detectedLanguageId);
            if (choices !== undefined) {
                logger.trace(`Post-processing ${choices[0].length} cached choices, isMoreMultiline=${isMoreMultiline}`);
                // Post-process any cached choices before deciding whether to issue a network request
                choices[0] = choices[0]
                    .map(c => this.instantiationService.invokeFunction(suggestions_1.postProcessChoiceInContext, completionState.textDocument, completionState.position, c, isMoreMultiline, logger))
                    .filter(c => c !== undefined);
            }
            if (choices && (choices[1] === resultType_1.ResultType.Cache || choices[1] === resultType_1.ResultType.TypingAsSuggested)) {
                telemetryBuilder.setIsFromCache();
                logContext.markAsFromCache();
            }
            if (choices !== undefined && choices[0].length === 0) {
                logger.trace(`Found empty inline suggestions locally via ${(0, telemetry_3.resultTypeToString)(choices[1])}`);
                return {
                    type: 'empty',
                    reason: 'cached results empty after post-processing',
                    telemetryData: (0, telemetry_3.mkBasicResultTelemetry)(telemetryData),
                };
            }
            if (choices !== undefined &&
                choices[0].length > 0 &&
                // If it's a cycling request, need to show multiple choices
                (!ghostTextOptions.isCycling || choices[0].length > 1)) {
                logger.trace(`Found inline suggestions locally via ${(0, telemetry_3.resultTypeToString)(choices[1])}`);
            }
            else {
                // No local choices, go to network
                logger.trace(`Going to network, isCycling=${ghostTextOptions.isCycling}`);
                const completionsFromNetwork = this.instantiationService.createInstance(completionsFromNetwork_1.CompletionsFromNetwork);
                if (ghostTextOptions.isCycling) {
                    logger.trace('Fetching all completions for cycling request');
                    const networkChoices = await completionsFromNetwork.getAllCompletionsFromNetwork(requestContext, telemetryData, cancellationToken, ghostTextStrategy.finishedCb, telemetryBuilder);
                    // TODO: if we already had some choices cached from the initial non-cycling request,
                    // and then the cycling request returns no results for some reason, we need to still
                    // return the original choices to the editor to avoid the ghost text disappearing completely.
                    // However this should be telemetrised according to the result of the cycling request itself,
                    // i.e. failure/empty (or maybe canceled).
                    //
                    // Right now this is awkward to orchestrate in the code and we don't handle it, incorrectly
                    // returning `ghostText.produced` instead. Cycling is a manual action and hence uncommon,
                    // so this shouldn't cause much inaccuracy, but we still should fix this.
                    if (networkChoices.type === 'success') {
                        logger.trace(`Cycling network request returned ${networkChoices.value[0].length} choices`);
                        const resultChoices = choices?.[0] ?? [];
                        networkChoices.value[0].forEach(c => {
                            // Collect only unique displayTexts
                            if (resultChoices.findIndex(v => v.completionText.trim() === c.completionText.trim()) !== -1) {
                                return;
                            }
                            resultChoices.push(c);
                        });
                        logger.trace(`After deduplication: ${resultChoices.length} unique choices`);
                        choices = [resultChoices, resultType_1.ResultType.Cycling];
                    }
                    else {
                        if (choices === undefined) {
                            return networkChoices;
                        }
                    }
                }
                else {
                    logger.trace('Initiating network request for completions');
                    // Wrap an observer around the finished callback to update the
                    // async manager as the request streams in.
                    const finishedCb = (text, delta) => {
                        this.asyncCompletionManager.updateCompletion(ourRequestId, text);
                        return ghostTextStrategy.finishedCb(text, delta);
                    };
                    const asyncCancellationTokenSource = new src_1.CancellationTokenSource();
                    const requestPromise = completionsFromNetwork.getCompletionsFromNetwork(requestContext, telemetryData, asyncCancellationTokenSource.token, finishedCb, telemetryBuilder);
                    void this.asyncCompletionManager.queueCompletionRequest(ourRequestId, prefix, prompt.prompt, asyncCancellationTokenSource, requestPromise);
                    const c = await this.asyncCompletionManager.getFirstMatchingRequest(ourRequestId, prefix, prompt.prompt, ghostTextOptions.isSpeculative);
                    if (c === undefined) {
                        logger.trace('Network request returned no results');
                        return {
                            type: 'empty',
                            reason: 'received no results from async completions',
                            telemetryData: (0, telemetry_3.mkBasicResultTelemetry)(telemetryData),
                        };
                    }
                    logger.trace('Received completion from network request');
                    choices = [[c[0]], resultType_1.ResultType.Async];
                }
                recordPerformance('network');
            }
            if (choices === undefined) {
                return {
                    type: 'failed',
                    reason: 'internal error: choices should be defined after network call',
                    telemetryData: (0, telemetry_3.mkBasicResultTelemetry)(telemetryData),
                };
            }
            const [choicesArray, resultType] = choices;
            logger.trace(`Final choices: ${choicesArray.length} from ${(0, telemetry_3.resultTypeToString)(resultType)}`);
            const postProcessedChoicesArray = choicesArray
                .map(c => this.instantiationService.invokeFunction(suggestions_1.postProcessChoiceInContext, completionState.textDocument, completionState.position, c, isMoreMultiline, logger))
                .filter(c => c !== undefined);
            logger.trace(`Post-processed to ${postProcessedChoicesArray.length} choices`);
            // Delay response if needed. Note, this must come before the
            // telemetryWithAddData call since the time_to_produce_ms is computed
            // there
            const completionsDelay = this.instantiationService.invokeFunction((config_1.getConfig), config_1.ConfigKey.CompletionsDelay) ??
                this.completionsFeaturesService.completionsDelay(preIssuedTelemetryDataWithExp);
            const elapsed = (0, telemetry_2.now)() - preIssuedTelemetryDataWithExp.issuedTime;
            const remainingDelay = Math.max(completionsDelay - elapsed, 0);
            if (resultType !== resultType_1.ResultType.TypingAsSuggested && !ghostTextOptions.isCycling && remainingDelay > 0) {
                logger.debug(`Waiting ${remainingDelay}ms before returning completion`);
                await (0, async_1.delay)(remainingDelay);
                if (isCompletionRequestCancelled(this.currentGhostText, ourRequestId, cancellationToken)) {
                    logger.debug('Cancelled after completions delay');
                    return {
                        type: 'canceled',
                        reason: 'after completions delay',
                        telemetryData: (0, telemetry_3.mkCanceledResultTelemetry)(telemetryData),
                    };
                }
            }
            const results = [];
            for (const choice of postProcessedChoicesArray) {
                // Do this to get a new object for each choice
                const choiceTelemetryData = telemetryWithAddData(completionState.textDocument, requestContext, choice, telemetryData);
                const suffixCoverage = inlineSuggestion
                    ? (0, suggestions_1.checkSuffix)(completionState.textDocument, completionState.position, choice)
                    : 0;
                // We want to use `newTrailingWs` as the trailing whitespace
                const ghostCompletion = adjustLeadingWhitespace(choice.choiceIndex, choice.completionText, prompt.trailingWs);
                const res = {
                    completion: ghostCompletion,
                    telemetry: choiceTelemetryData,
                    isMiddleOfTheLine: inlineSuggestion,
                    suffixCoverage,
                    copilotAnnotations: choice.copilotAnnotations,
                    clientCompletionId: choice.clientCompletionId,
                };
                results.push(res);
            }
            // Lift clientCompletionId out of the result in order to include it in the telemetry payload computed by mkBasicResultTelemetry.
            telemetryData.properties.clientCompletionId = results[0]?.clientCompletionId;
            // If reading from the cache or async, capture the look back offset used
            telemetryData.measurements.foundOffset = results?.[0]?.telemetry?.measurements?.foundOffset ?? -1;
            logger.debug(`Produced ${results.length} results from ${(0, telemetry_3.resultTypeToString)(resultType)} at ${telemetryData.measurements.foundOffset} offset`);
            if (isCompletionRequestCancelled(this.currentGhostText, ourRequestId, cancellationToken)) {
                return {
                    type: 'canceled',
                    reason: 'after post processing completions',
                    telemetryData: (0, telemetry_3.mkCanceledResultTelemetry)(telemetryData),
                };
            }
            if (!ghostTextOptions.isSpeculative) {
                logger.trace('Updating current ghost text as request is not speculative');
                // Update the current ghost text with the new response before returning for the "typing as suggested" UX
                this.currentGhostText.setGhostText(prefix, prompt.prompt.suffix, postProcessedChoicesArray, resultType);
            }
            recordPerformance('complete');
            logger.trace(`Ghost text computation complete, returning ${results.length} results`);
            return {
                type: 'success',
                value: [results, resultType],
                telemetryData: (0, telemetry_3.mkBasicResultTelemetry)(telemetryData),
                telemetryBlob: telemetryData,
                resultType,
                performanceMetrics,
            };
        });
    }
};
exports.GhostTextComputer = GhostTextComputer;
exports.GhostTextComputer = GhostTextComputer = __decorate([
    __param(0, instantiation_1.IInstantiationService),
    __param(1, telemetry_1.ITelemetryService),
    __param(2, completionNotifier_1.ICompletionsNotifierService),
    __param(3, contextProviderBridge_1.ICompletionsContextProviderBridgeService),
    __param(4, current_1.ICompletionsCurrentGhostText),
    __param(5, contextProviderStatistics_1.ICompletionsContextProviderService),
    __param(6, asyncCompletions_1.ICompletionsAsyncManagerService),
    __param(7, featuresService_1.ICompletionsFeaturesService),
    __param(8, logger_1.ICompletionsLogTargetService),
    __param(9, progress_1.ICompletionsStatusReporter),
    __param(10, logService_1.ILogService)
], GhostTextComputer);
async function getGhostText(accessor, completionState, token, options, logContext, telemetryBuilder, logger) {
    const instaService = accessor.get(instantiation_1.IInstantiationService);
    const ghostTextComputer = instaService.createInstance(GhostTextComputer);
    return ghostTextComputer.getGhostText(completionState, token, options, logContext, telemetryBuilder, logger);
}
/**
 * Attempt to get InlineSuggestion locally, in one of two ways:
 *  1. If the user is typing the letters already displayed as inline suggestion.
 *  2. If we have a previously cached inline suggestion for this prompt and requestMultiline.
 */
function getLocalInlineSuggestion(accessor, prefix, prompt, requestMultiline) {
    const currentGhostText = accessor.get(current_1.ICompletionsCurrentGhostText);
    const choicesTyping = currentGhostText.getCompletionsForUserTyping(prefix, prompt.suffix);
    const choicesCache = getCompletionsFromCache(accessor, prefix, prompt.suffix, requestMultiline);
    if (choicesTyping && choicesTyping.length > 0) {
        // Append cached choices to choicesTyping, if any. Ensure typing choices
        // are first so that the shown completion doesn't disappear.
        // Filter duplicates by completionText
        const choicesCacheDeduped = (choicesCache ?? []).filter(c => !choicesTyping.some(t => t.completionText === c.completionText));
        return [choicesTyping.concat(choicesCacheDeduped), resultType_1.ResultType.TypingAsSuggested];
    }
    if (choicesCache && choicesCache.length > 0) {
        return [choicesCache, resultType_1.ResultType.Cache];
    }
}
/** Checks if the position is valid inline suggestion position. Returns `undefined` if it's position where ghost text shouldn't be displayed */
function isInlineSuggestion(document, position) {
    const line = document.lineAt(position);
    const textAfterCursor = line.text.substring(position.character);
    return (0, inlineSuggestion_1.isInlineSuggestionFromTextAfterCursor)(textAfterCursor);
}
// This enables tests to control multi line behavior
class ForceMultiLine {
    static { this.default = new ForceMultiLine(); }
    constructor(requestMultilineOverride = false) {
        this.requestMultilineOverride = requestMultilineOverride;
    }
}
exports.ForceMultiLine = ForceMultiLine;
function adjustLeadingWhitespace(index, text, ws) {
    if (ws.length > 0) {
        if (text.startsWith(ws)) {
            // Remove common prefix so that it can display in the correct position
            return {
                completionIndex: index,
                completionText: text,
                displayText: text.substring(ws.length),
                displayNeedsWsOffset: false,
            };
        }
        else {
            // The idea here is that we do want the display to be as close to the final position as possible
            const textLeftWs = text.substring(0, text.length - text.trimStart().length);
            if (ws.startsWith(textLeftWs)) {
                // NOTE: It's possible that `ws` is a bit too over-indented. Example:
                // def foo(n):
                //     if n > 0:
                //         print(f"n is positive: {n}")
                //         [cursor is here after new line]
                //
                // completion: "    else:"
                return {
                    completionIndex: index,
                    completionText: text,
                    displayText: text.trimStart(),
                    displayNeedsWsOffset: true,
                };
            }
            else {
                // We don't know any better so just send `text` back
                return { completionIndex: index, completionText: text, displayText: text, displayNeedsWsOffset: false };
            }
        }
    }
    else {
        // If we do not know leading whitespace or if it is an empty string, just return input text
        return { completionIndex: index, completionText: text, displayText: text, displayNeedsWsOffset: false };
    }
}
/**
 * Returns all completions from the cache for given document prefix. Walks back
 * from the current prefix to search for completions with a prefix that
 * partially matches the current prefix and completion text that matches the
 * remaining current prefix.
 */
function getCompletionsFromCache(accessor, prefix, suffix, multiline) {
    const logger = accessor.get(logService_1.ILogService).createSubLogger(['getCompletionsFromCache']);
    const choices = accessor.get(completionsCache_1.ICompletionsCacheService).findAll(prefix, suffix);
    if (choices.length === 0) {
        logger.debug('Found no completions in cache');
        return [];
    }
    logger.debug(`Found ${choices.length} completions in cache`);
    return choices.map(choice => (0, completionsFromNetwork_1.makeGhostAPIChoice)(choice, { forceSingleLine: !multiline }));
}
/** Create a TelemetryWithExp instance for a ghost text request. */
async function createTelemetryWithExp(accessor, document, headerRequestId, options) {
    const featuresService = accessor.get(featuresService_1.ICompletionsFeaturesService);
    const properties = { headerRequestId };
    if (options?.opportunityId) {
        properties.opportunityId = options.opportunityId;
    }
    if (options?.selectedCompletionInfo?.text) {
        properties.completionsActive = 'true';
    }
    if (options?.isSpeculative) {
        properties.reason = 'speculative';
    }
    const telemetryData = telemetry_2.TelemetryData.createAndMarkAsIssued(properties);
    const telemetryWithExp = await featuresService.updateExPValuesAndAssignments({ uri: document.uri, languageId: document.detectedLanguageId }, telemetryData);
    return telemetryWithExp;
}
/** Return a copy of the choice's telemetry data with extra information added */
function telemetryWithAddData(document, requestContext, choice, issuedTelemetryData) {
    const requestId = choice.requestId;
    const properties = {
        choiceIndex: choice.choiceIndex.toString(),
        clientCompletionId: choice.clientCompletionId,
    };
    if (choice.generatedChoiceIndex !== undefined) {
        properties.originalChoiceIndex = properties.choiceIndex;
        properties.choiceIndex = (10_000 * (choice.generatedChoiceIndex + 1) + choice.choiceIndex).toString();
    }
    const measurements = {
        compCharLen: choice.completionText.length,
        numLines: choice.completionText.trim().split('\n').length,
    };
    // Add assessments
    if (choice.meanLogProb) {
        measurements.meanLogProb = choice.meanLogProb;
    }
    if (choice.meanAlternativeLogProb) {
        measurements.meanAlternativeLogProb = choice.meanAlternativeLogProb;
    }
    const extendedTelemetry = choice.telemetryData.extendedBy(properties, measurements);
    extendedTelemetry.issuedTime = issuedTelemetryData.issuedTime;
    extendedTelemetry.measurements.timeToProduceMs = performance.now() - issuedTelemetryData.issuedTime;
    addDocumentTelemetry(extendedTelemetry, document);
    extendedTelemetry.extendWithRequestId(requestId);
    return extendedTelemetry;
}
/** Create new telemetry data based on baseTelemetryData and send `ghostText.issued` event  */
function telemetryIssued(accessor, document, requestContext, position, prompt, baseTelemetryData, requestInfo, ghostTextOptions) {
    // base ghostText telemetry data
    const properties = {
        languageId: document.detectedLanguageId,
    };
    properties.afterAccept = requestContext.afterAccept.toString();
    properties.isSpeculative = ghostTextOptions.isSpeculative.toString();
    const telemetryData = baseTelemetryData.extendedBy(properties);
    addDocumentTelemetry(telemetryData, document);
    // Add repository information
    const repoInfo = requestContext.repoInfo;
    telemetryData.properties.gitRepoInformation =
        repoInfo === undefined ? 'unavailable' : repoInfo === repository_1.ComputationStatus.PENDING ? 'pending' : 'available';
    if (repoInfo !== undefined && repoInfo !== repository_1.ComputationStatus.PENDING) {
        telemetryData.properties.gitRepoUrl = repoInfo.url;
        telemetryData.properties.gitRepoHost = repoInfo.hostname;
        if (repoInfo.repoId?.type === 'github') {
            telemetryData.properties.gitRepoOwner = repoInfo.repoId.org;
            telemetryData.properties.gitRepoName = repoInfo.repoId.repo;
        }
        else if (repoInfo.repoId?.type === 'ado') {
            telemetryData.properties.gitRepoOwner = repoInfo.repoId.project;
            telemetryData.properties.gitRepoName = repoInfo.repoId.repo;
        }
        else {
            // TODO: We don't have generic owner and repo for other providers
        }
        telemetryData.properties.gitRepoPath = repoInfo.pathname;
    }
    telemetryData.properties.engineName = requestInfo.modelId;
    telemetryData.properties.engineChoiceSource = requestInfo.engineChoiceSource;
    // Add requestMultiline information
    telemetryData.properties.isMultiline = JSON.stringify(requestContext.multiline);
    telemetryData.properties.isCycling = JSON.stringify(requestContext.isCycling);
    // calculated values for the issued event
    const currentLine = document.lineAt(position.line);
    const lineBeforeCursor = document.getText(textDocument_1.LocationFactory.range(currentLine.range.start, position));
    const restOfLine = document.getText(textDocument_1.LocationFactory.range(position, currentLine.range.end));
    const typeFileHashCode = Array.from(prompt.neighborSource.entries()).map(typeFiles => [
        typeFiles[0],
        typeFiles[1].map(f => (0, crypto_1.createSha256Hash)(f).toString()), // file name is sensitive. We just keep SHA256 of the file name.
    ]);
    // Properties that we only want to include in the issued event
    const extendedProperties = {
        beforeCursorWhitespace: JSON.stringify(lineBeforeCursor.trim() === ''),
        afterCursorWhitespace: JSON.stringify(restOfLine.trim() === ''),
        neighborSource: JSON.stringify(typeFileHashCode),
        blockMode: requestContext.blockMode,
    };
    const extendedMeasurements = {
        ...(0, telemetry_2.telemetrizePromptLength)(prompt.prompt),
        promptEndPos: document.offsetAt(position),
        promptComputeTimeMs: prompt.computeTimeMs,
    };
    if (prompt.metadata) {
        extendedProperties.promptMetadata = JSON.stringify(prompt.metadata);
    }
    if (prompt.contextProvidersTelemetry) {
        extendedProperties.contextProviders = JSON.stringify(prompt.contextProvidersTelemetry);
    }
    const telemetryDataToSend = telemetryData.extendedBy(extendedProperties, extendedMeasurements);
    // telemetrize the issued event
    (0, telemetry_2.telemetry)(accessor, 'ghostText.issued', telemetryDataToSend);
    return telemetryData;
}
function addDocumentTelemetry(telemetry, document) {
    telemetry.measurements.documentLength = document.getText().length;
    telemetry.measurements.documentLineCount = document.lineCount;
}
//# sourceMappingURL=ghostText.js.map