"use strict";
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getGhostTextStrategy = getGhostTextStrategy;
const instantiation_1 = require("../../../../../../util/vs/platform/instantiation/common/instantiation");
const parse_1 = require("../../../prompt/src/parse");
const config_1 = require("../config");
const featuresService_1 = require("../experiments/featuresService");
const parseBlock_1 = require("../prompt/parseBlock");
const telemetry_1 = require("../telemetry");
const textDocument_1 = require("../textDocument");
const blockTrimmer_1 = require("./blockTrimmer");
const cacheUtils_1 = require("./cacheUtils");
const completionsCache_1 = require("./completionsCache");
const configBlockMode_1 = require("./configBlockMode");
const multilineModel_1 = require("./multilineModel");
const streamedCompletionSplitter_1 = require("./streamedCompletionSplitter");
// p50 line length is 19 characters (p95 is 73)
// average token length is around 4 characters
// the below values have quite a bit of buffer while bringing the limit in significantly from 500
const maxSinglelineTokens = 20;
function takeNLines(n) {
    return (text) => {
        // If the text is longer than n lines, return the offset.
        // Checks for n+1 lines because of the leading newline.
        const lines = text?.split('\n') ?? [];
        if (lines.length > n + 1) {
            return lines.slice(0, n + 1).join('\n').length;
        }
    };
}
async function getGhostTextStrategy(accessor, completionState, prefix, prompt, inlineSuggestion, hasAcceptedCurrentCompletion, preIssuedTelemetryData) {
    const instantiationService = accessor.get(instantiation_1.IInstantiationService);
    const featuresService = accessor.get(featuresService_1.ICompletionsFeaturesService);
    const blockModeConfig = accessor.get(configBlockMode_1.ICompletionsBlockModeConfig);
    const multilineAfterAcceptLines = featuresService.multilineAfterAcceptLines(preIssuedTelemetryData);
    const blockMode = blockModeConfig.forLanguage(completionState.textDocument.detectedLanguageId, preIssuedTelemetryData);
    switch (blockMode) {
        case config_1.BlockMode.Server:
            // Override the server-side trimming after accepting a completion
            if (hasAcceptedCurrentCompletion) {
                return {
                    blockMode: config_1.BlockMode.Parsing,
                    requestMultiline: true,
                    finishedCb: takeNLines(multilineAfterAcceptLines),
                    stop: ['\n\n'],
                    maxTokens: maxSinglelineTokens * multilineAfterAcceptLines,
                };
            }
            return {
                blockMode: config_1.BlockMode.Server,
                requestMultiline: true,
                finishedCb: _ => undefined,
            };
        case config_1.BlockMode.Parsing:
        case config_1.BlockMode.ParsingAndServer:
        case config_1.BlockMode.MoreMultiline:
        default: {
            // we shouldn't drop through to here, but in case we do, be explicit about the behaviour
            let requestMultiline;
            try {
                requestMultiline = await instantiationService.invokeFunction(shouldRequestMultiline, blockMode, completionState.textDocument, completionState.position, inlineSuggestion, hasAcceptedCurrentCompletion, prompt);
            }
            catch (err) {
                // Fallback to non-multiline
                requestMultiline = { requestMultiline: false };
            }
            if (!hasAcceptedCurrentCompletion &&
                requestMultiline.requestMultiline &&
                featuresService.singleLineUnlessAccepted(preIssuedTelemetryData)) {
                requestMultiline.requestMultiline = false;
            }
            if (requestMultiline.requestMultiline) {
                // Note that `trailingWs` contains *any* trailing whitespace from the prompt, but the prompt itself
                // is only trimmed if the entire last line is whitespace.  We have to account for that here when we
                // check whether the block body is finished.
                let adjustedPosition;
                if (prompt.trailingWs.length > 0 && !prompt.prompt.prefix.endsWith(prompt.trailingWs)) {
                    // Prompt was adjusted, so adjust the position to match
                    adjustedPosition = textDocument_1.LocationFactory.position(completionState.position.line, Math.max(completionState.position.character - prompt.trailingWs.length, 0));
                }
                else {
                    // Otherwise, just use the original position
                    adjustedPosition = completionState.position;
                }
                return {
                    blockMode: blockMode,
                    requestMultiline: true,
                    ...instantiationService.invokeFunction(buildFinishedCallback, blockMode, completionState.textDocument, adjustedPosition, requestMultiline.blockPosition, prefix, true, prompt.prompt, preIssuedTelemetryData),
                };
            }
            // Override single-line to multiline after accepting a completion
            if (hasAcceptedCurrentCompletion) {
                const result = {
                    blockMode: config_1.BlockMode.Parsing,
                    requestMultiline: true,
                    finishedCb: takeNLines(multilineAfterAcceptLines),
                    stop: ['\n\n'],
                    maxTokens: maxSinglelineTokens * multilineAfterAcceptLines,
                };
                if (blockMode === config_1.BlockMode.MoreMultiline) {
                    result.blockMode = config_1.BlockMode.MoreMultiline;
                }
                return result;
            }
            // not multiline
            return {
                blockMode: blockMode,
                requestMultiline: false,
                ...instantiationService.invokeFunction(buildFinishedCallback, blockMode, completionState.textDocument, completionState.position, requestMultiline.blockPosition, prefix, false, prompt.prompt, preIssuedTelemetryData),
            };
        }
    }
}
function buildFinishedCallback(accessor, blockMode, document, position, positionType, prefix, multiline, prompt, telemetryData) {
    const featuresService = accessor.get(featuresService_1.ICompletionsFeaturesService);
    const instantiationService = accessor.get(instantiation_1.IInstantiationService);
    if (multiline && blockMode === config_1.BlockMode.MoreMultiline && blockTrimmer_1.BlockTrimmer.isSupported(document.detectedLanguageId)) {
        const lookAhead = positionType === blockTrimmer_1.BlockPositionType.EmptyBlock || positionType === blockTrimmer_1.BlockPositionType.BlockEnd
            ? featuresService.longLookaheadSize(telemetryData)
            : featuresService.shortLookaheadSize(telemetryData);
        const completionsCacheService = accessor.get(completionsCache_1.ICompletionsCacheService);
        const finishedCb = instantiationService.createInstance(streamedCompletionSplitter_1.StreamedCompletionSplitter, prefix, document.detectedLanguageId, false, lookAhead, (extraPrefix, item) => {
            const cacheContext = {
                prefix: prefix + extraPrefix,
                prompt: { ...prompt, prefix: prompt.prefix + extraPrefix },
            };
            (0, cacheUtils_1.appendToCache)(completionsCacheService, cacheContext, item);
        }).getFinishedCallback();
        return {
            finishedCb,
            maxTokens: featuresService.maxMultilineTokens(telemetryData),
        };
    }
    return { finishedCb: multiline ? (0, parseBlock_1.parsingBlockFinished)(document, position) : _ => undefined };
}
async function shouldRequestMultiline(accessor, blockMode, document, position, inlineSuggestion, afterAccept, prompt) {
    // Parsing long files for multiline completions is slow, so we only do
    // it for files with less than 8000 lines
    if (document.lineCount >= 8000) {
        (0, telemetry_1.telemetry)(accessor, 'ghostText.longFileMultilineSkip', telemetry_1.TelemetryData.createAndMarkAsIssued({
            languageId: document.detectedLanguageId,
            lineCount: String(document.lineCount),
            currentLine: String(position.line),
        }));
    }
    else {
        if (blockMode === config_1.BlockMode.MoreMultiline && blockTrimmer_1.BlockTrimmer.isSupported(document.detectedLanguageId)) {
            if (!afterAccept) {
                return { requestMultiline: false };
            }
            const blockPosition = await (0, blockTrimmer_1.getBlockPositionType)(document, position);
            return { requestMultiline: true, blockPosition };
        }
        const targetLanguagesNewLine = ['typescript', 'typescriptreact'];
        if (targetLanguagesNewLine.includes(document.detectedLanguageId)) {
            const newLine = isNewLine(position, document);
            if (newLine) {
                return { requestMultiline: true };
            }
        }
        let requestMultiline = false;
        if (!inlineSuggestion && (0, parse_1.isSupportedLanguageId)(document.detectedLanguageId)) {
            // Can only check block-level nodes of languages we support
            requestMultiline = await (0, parseBlock_1.isEmptyBlockStartUtil)(document, position);
        }
        else if (inlineSuggestion && (0, parse_1.isSupportedLanguageId)(document.detectedLanguageId)) {
            //If we are inline, check if we would suggest multiline for current position or if we would suggest a multiline completion if we were at the end of the line
            requestMultiline =
                (await (0, parseBlock_1.isEmptyBlockStartUtil)(document, position)) ||
                    (await (0, parseBlock_1.isEmptyBlockStartUtil)(document, document.lineAt(position).range.end));
        }
        // If requestMultiline is false, for specific languages check multiline score
        if (!requestMultiline) {
            const requestMultiModelThreshold = 0.5;
            const targetLanguagesModel = ['javascript', 'javascriptreact', 'python'];
            if (targetLanguagesModel.includes(document.detectedLanguageId)) {
                // Call multiline model if not multiline and EXP flag is set.
                const multiModelScore = (0, multilineModel_1.requestMultilineScore)(prompt.prompt, document.detectedLanguageId);
                requestMultiline = multiModelScore > requestMultiModelThreshold;
            }
        }
        return { requestMultiline };
    }
    return { requestMultiline: false };
}
/** Checks if position is the beginning of an empty line (including indentation) */
function isNewLine(selectionPosition, doc) {
    const line = doc.lineAt(selectionPosition);
    const lineTrimmed = line.text.trim();
    return lineTrimmed.length === 0;
}
//# sourceMappingURL=ghostTextStrategy.js.map