"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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClippedDocument = exports.N_LINES_AS_CONTEXT = exports.N_LINES_BELOW = exports.N_LINES_ABOVE = exports.PromptPieces = void 0;
exports.getUserPrompt = getUserPrompt;
exports.truncateCode = truncateCode;
exports.expandRangeToPageRange = expandRangeToPageRange;
exports.clipPreservingRange = clipPreservingRange;
exports.createTaggedCurrentFileContentUsingPagedClipping = createTaggedCurrentFileContentUsingPagedClipping;
exports.constructTaggedFile = constructTaggedFile;
const xtabPromptOptions = __importStar(require("../../../platform/inlineEdits/common/dataTypes/xtabPromptOptions"));
const xtabPromptOptions_1 = require("../../../platform/inlineEdits/common/dataTypes/xtabPromptOptions");
const languageContextService_1 = require("../../../platform/languageServer/common/languageContextService");
const result_1 = require("../../../util/common/result");
const arrays_1 = require("../../../util/vs/base/common/arrays");
const assert_1 = require("../../../util/vs/base/common/assert");
const stringEdit_1 = require("../../../util/vs/editor/common/core/edits/stringEdit");
const offsetRange_1 = require("../../../util/vs/editor/common/core/ranges/offsetRange");
const diffHistoryForPrompt_1 = require("./diffHistoryForPrompt");
const promptCraftingUtils_1 = require("./promptCraftingUtils");
const recentFilesForPrompt_1 = require("./recentFilesForPrompt");
const tags_1 = require("./tags");
class PromptPieces {
    constructor(currentDocument, editWindowLinesRange, areaAroundEditWindowLinesRange, activeDoc, xtabHistory, taggedCurrentDocLines, areaAroundCodeToEdit, langCtx, aggressivenessLevel, lintErrors, computeTokens, opts, neighborSnippets) {
        this.currentDocument = currentDocument;
        this.editWindowLinesRange = editWindowLinesRange;
        this.areaAroundEditWindowLinesRange = areaAroundEditWindowLinesRange;
        this.activeDoc = activeDoc;
        this.xtabHistory = xtabHistory;
        this.taggedCurrentDocLines = taggedCurrentDocLines;
        this.areaAroundCodeToEdit = areaAroundCodeToEdit;
        this.langCtx = langCtx;
        this.aggressivenessLevel = aggressivenessLevel;
        this.lintErrors = lintErrors;
        this.computeTokens = computeTokens;
        this.opts = opts;
        this.neighborSnippets = neighborSnippets;
    }
}
exports.PromptPieces = PromptPieces;
function getUserPrompt(promptPieces) {
    const { activeDoc, xtabHistory, taggedCurrentDocLines, areaAroundCodeToEdit, langCtx, aggressivenessLevel, lintErrors, computeTokens, opts, neighborSnippets } = promptPieces;
    const currentFileContent = taggedCurrentDocLines.join('\n');
    let recentlyViewedCodeSnippets;
    let docsInPrompt;
    let neighborSnippetsResult;
    let editDiffHistory;
    let nDiffsInPrompt;
    let diffTokensInPrompt;
    if (opts.globalBudget !== undefined) {
        const cascade = runGlobalBudgetCascade(activeDoc, xtabHistory, langCtx, computeTokens, opts, neighborSnippets, opts.globalBudget);
        recentlyViewedCodeSnippets = cascade.codeSnippets;
        docsInPrompt = cascade.documents;
        neighborSnippetsResult = cascade.neighborSnippetsResult;
        editDiffHistory = cascade.editDiffHistory;
        nDiffsInPrompt = cascade.nDiffsInPrompt;
        diffTokensInPrompt = cascade.diffTokensInPrompt;
    }
    else {
        const r = (0, recentFilesForPrompt_1.getRecentCodeSnippets)(activeDoc, xtabHistory, langCtx, computeTokens, opts, neighborSnippets);
        recentlyViewedCodeSnippets = r.codeSnippets;
        docsInPrompt = r.documents;
        neighborSnippetsResult = r.neighborSnippetsResult;
        docsInPrompt.add(activeDoc.id); // Add active document to the set of documents in prompt
        const diff = (0, diffHistoryForPrompt_1.getEditDiffHistory)(activeDoc, xtabHistory, docsInPrompt, computeTokens, opts.diffHistory);
        editDiffHistory = diff.promptPiece;
        nDiffsInPrompt = diff.nDiffs;
        diffTokensInPrompt = diff.totalTokens;
    }
    const relatedInformation = getRelatedInformation(langCtx);
    const currentFilePath = (0, promptCraftingUtils_1.toUniquePath)(activeDoc.id, activeDoc.workspaceRoot?.path);
    const postScript = promptPieces.opts.includePostScript ? getPostScript(opts.promptingStrategy, currentFilePath, aggressivenessLevel) : '';
    const lintsWithNewLinePadding = opts.lintOptions ? `\n${lintErrors.getFormattedLintErrors(opts.lintOptions)}\n` : '';
    const basePrompt = `${tags_1.PromptTags.RECENT_FILES.start}
${recentlyViewedCodeSnippets}
${tags_1.PromptTags.RECENT_FILES.end}

${tags_1.PromptTags.CURRENT_FILE.start}
current_file_path: ${currentFilePath}
${currentFileContent}
${tags_1.PromptTags.CURRENT_FILE.end}
${lintsWithNewLinePadding}
${tags_1.PromptTags.EDIT_HISTORY.start}
${editDiffHistory}
${tags_1.PromptTags.EDIT_HISTORY.end}`;
    let mainPrompt;
    switch (opts.promptingStrategy) {
        case xtabPromptOptions_1.PromptingStrategy.PatchBased01:
            mainPrompt = basePrompt;
            break;
        case xtabPromptOptions_1.PromptingStrategy.PatchBased02: {
            const currentDocument = promptPieces.currentDocument;
            const cursorLine = currentDocument.lineWithCursor();
            const cursorLineWithTag = cursorLine.substring(0, currentDocument.cursorPosition.column - 1) + tags_1.PromptTags.CURSOR + cursorLine.substring(currentDocument.cursorPosition.column - 1);
            const lineNumberZeroBased = currentDocument.cursorPosition.lineNumber - 1;
            let lineNumbering;
            switch (opts.currentFile.includeLineNumbers) {
                case xtabPromptOptions.IncludeLineNumbersOption.WithSpaceAfter:
                    lineNumbering = `${lineNumberZeroBased}| `;
                    break;
                case xtabPromptOptions.IncludeLineNumbersOption.WithoutSpace:
                    lineNumbering = `${lineNumberZeroBased}|`;
                    break;
                case xtabPromptOptions.IncludeLineNumbersOption.None:
                    lineNumbering = '';
                    break;
                default:
                    (0, assert_1.assertNever)(opts.currentFile.includeLineNumbers);
            }
            const lineWithCursorSnippet = [
                tags_1.PromptTags.CURSOR_LOCATION.start,
                `${lineNumbering}${cursorLineWithTag}`,
                tags_1.PromptTags.CURSOR_LOCATION.end
            ].join('\n');
            mainPrompt = basePrompt + `\n\n${lineWithCursorSnippet}`;
            break;
        }
        default:
            mainPrompt = basePrompt + `\n\n${areaAroundCodeToEdit}`;
            break;
    }
    const includeBackticks = opts.promptingStrategy !== xtabPromptOptions_1.PromptingStrategy.Nes41Miniv3 &&
        opts.promptingStrategy !== xtabPromptOptions_1.PromptingStrategy.Codexv21NesUnified &&
        opts.promptingStrategy !== xtabPromptOptions_1.PromptingStrategy.PatchBased01 &&
        opts.promptingStrategy !== xtabPromptOptions_1.PromptingStrategy.PatchBased02;
    const packagedPrompt = includeBackticks ? wrapInBackticks(mainPrompt) : mainPrompt;
    const packagedPromptWithRelatedInfo = addRelatedInformation(relatedInformation, packagedPrompt, opts.languageContext.traitPosition);
    const prompt = packagedPromptWithRelatedInfo + postScript;
    const trimmedPrompt = prompt.trim();
    return { prompt: trimmedPrompt, nDiffsInPrompt, diffTokensInPrompt, neighborSnippetsResult };
}
/**
 * Single-pool budget cascade across prompt parts, modeled after completions-core's
 * `CascadingPromptFactory`. For each part in `globalBudget.order`, allocate
 * `surplus + totalTokens * shares[part]` and run that sub-builder with the override.
 * Unspent budget cascades to the next part.
 *
 * Sub-builders are invoked using existing helpers so behavior of each individual
 * part is unchanged. `currentFile` and `lintOptions` are intentionally excluded
 * and continue using their own per-part caps.
 *
 * Each sub-builder reports `tokensConsumed` using the same internal accounting
 * it uses to make budget decisions (paged-clipping line cost, raw-snippet cost
 * for appenders, diff-entry cost for history). The cascade uses that reported
 * value to compute `surplus`, which keeps the cascade aligned with how each
 * part actually charges against its budget.
 */
function runGlobalBudgetCascade(activeDoc, xtabHistory, langCtx, computeTokens, opts, neighborSnippets, globalBudget) {
    validateGlobalBudget(globalBudget);
    const recentlyViewedSnippets = [];
    const langCtxSnippets = [];
    const neighborOutSnippets = [];
    const docsInPrompt = new Set();
    docsInPrompt.add(activeDoc.id);
    let neighborSnippetsResult;
    let editDiffHistory = '';
    let nDiffsInPrompt = 0;
    let diffTokensInPrompt = 0;
    const preparedRecent = (0, recentFilesForPrompt_1.prepareRecentCodeSnippets)(activeDoc, xtabHistory, opts);
    let surplus = 0;
    for (const part of globalBudget.order) {
        const share = globalBudget.shares[part] ?? 0;
        const budget = Math.max(0, Math.floor(surplus + globalBudget.totalTokens * share));
        let tokensConsumed = 0;
        switch (part) {
            case 'recentlyViewedDocuments': {
                const overridden = {
                    ...opts,
                    recentlyViewedDocuments: { ...opts.recentlyViewedDocuments, maxTokens: budget },
                };
                const r = (0, recentFilesForPrompt_1.buildCodeSnippetsUsingPagedClipping)(preparedRecent, computeTokens, overridden);
                recentlyViewedSnippets.push(...r.snippets);
                r.docsInPrompt.forEach(d => docsInPrompt.add(d));
                tokensConsumed = r.tokensConsumed;
                break;
            }
            case 'languageContext': {
                if (langCtx) {
                    tokensConsumed = (0, recentFilesForPrompt_1.appendLanguageContextSnippets)(langCtx, langCtxSnippets, budget, computeTokens, opts.recentlyViewedDocuments.includeLineNumbers);
                }
                break;
            }
            case 'neighborFiles': {
                if (opts.neighborFiles.enabled && neighborSnippets && neighborSnippets.length > 0) {
                    neighborSnippetsResult = (0, recentFilesForPrompt_1.appendNeighborFileSnippets)(neighborSnippets, neighborOutSnippets, docsInPrompt, budget, computeTokens, opts.recentlyViewedDocuments.includeLineNumbers);
                    tokensConsumed = neighborSnippetsResult.tokensConsumed;
                }
                break;
            }
            case 'diffHistory': {
                const overriddenDiff = { ...opts.diffHistory, maxTokens: budget };
                const r = (0, diffHistoryForPrompt_1.getEditDiffHistory)(activeDoc, xtabHistory, docsInPrompt, computeTokens, overriddenDiff);
                editDiffHistory = r.promptPiece;
                nDiffsInPrompt = r.nDiffs;
                diffTokensInPrompt = r.totalTokens;
                tokensConsumed = r.totalTokens;
                break;
            }
            default:
                (0, assert_1.assertNever)(part);
        }
        surplus = Math.max(0, budget - tokensConsumed);
    }
    const codeSnippets = [...recentlyViewedSnippets, ...langCtxSnippets, ...neighborOutSnippets].join('\n\n');
    return {
        codeSnippets,
        documents: docsInPrompt,
        neighborSnippetsResult,
        editDiffHistory,
        nDiffsInPrompt,
        diffTokensInPrompt,
    };
}
/**
 * Validate {@link GlobalBudgetOptions} since it is runtime-configurable
 * (e.g. via experiments). Catches misconfigurations that would otherwise
 * cause silent, hard-to-debug behavior:
 *  - duplicate parts in `order` (would render the same part twice)
 *  - missing share for any part in `order`
 *  - shares not summing to ~1 across `order` (would over/under-allocate)
 *  - `neighborFiles` ordered before `recentlyViewedDocuments` (the former
 *    consults `docsInPrompt` populated by the latter)
 */
function validateGlobalBudget(globalBudget) {
    const seen = new Set();
    for (const part of globalBudget.order) {
        if (seen.has(part)) {
            throw new Error(`globalBudget.order contains duplicate part '${part}'`);
        }
        seen.add(part);
        if (typeof globalBudget.shares[part] !== 'number') {
            throw new Error(`globalBudget.shares is missing entry for '${part}'`);
        }
    }
    const recentIdx = globalBudget.order.indexOf('recentlyViewedDocuments');
    const neighborIdx = globalBudget.order.indexOf('neighborFiles');
    if (recentIdx !== -1 && neighborIdx !== -1 && neighborIdx < recentIdx) {
        throw new Error(`globalBudget.order must place 'recentlyViewedDocuments' before 'neighborFiles'`);
    }
    const sharesSum = globalBudget.order.reduce((sum, part) => sum + globalBudget.shares[part], 0);
    const epsilon = 1e-3;
    if (Math.abs(sharesSum - 1) > epsilon) {
        throw new Error(`globalBudget.shares across order must sum to ~1, got ${sharesSum}`);
    }
}
function wrapInBackticks(content) {
    return `\`\`\`\n${content}\n\`\`\``;
}
function addRelatedInformation(relatedInformation, prompt, position) {
    if (position === 'before') {
        return appendWithNewLineIfNeeded(relatedInformation, prompt, 2);
    }
    return appendWithNewLineIfNeeded(prompt, relatedInformation, 2);
}
function appendWithNewLineIfNeeded(base, toAppend, minNewLines) {
    // Count existing newlines at the end of base and start of toAppend
    let existingNewLines = 0;
    for (let i = base.length - 1; i >= 0 && base[i] === '\n'; i--) {
        existingNewLines++;
    }
    for (let i = 0; i < toAppend.length && toAppend[i] === '\n'; i++) {
        existingNewLines++;
    }
    // Add newlines to reach the minimum required
    const newLinesToAdd = Math.max(0, minNewLines - existingNewLines);
    return (base + '\n'.repeat(newLinesToAdd) + toAppend).trim();
}
function getPostScript(strategy, currentFilePath, aggressivenessLevel) {
    let postScript;
    switch (strategy) {
        case xtabPromptOptions_1.PromptingStrategy.PatchBased01:
        case xtabPromptOptions_1.PromptingStrategy.Codexv21NesUnified:
            break;
        case xtabPromptOptions_1.PromptingStrategy.PatchBased02:
            postScript = `The developer was working on a section of code within the \`current_file_content\` - carefully note their \`cursor_location\` marked with \`<|cursor|>\`. Using the given \`recently_viewed_code_snippets\`, \`current_file_content\`, \`edit_diff_history\`, and \`cursor_location\`, please continue the developer's work. Output a modified diff format with a sequence of intuitive next changes, where each patch must start with \`<filename>:<line number>\`. Order changes by priority and flow; for instance, edits adjacent to the user's cursor should always be prioritized, followed by lines near the cursor, followed by lines farther away. If there are no good edit candidates, output the empty string "". Avoid undoing or reverting the developer's last change unless there are obvious typos or errors. Adhere meticulously to the diff format.`;
            break;
        case xtabPromptOptions_1.PromptingStrategy.UnifiedModel:
            postScript = `The developer was working on a section of code within the tags \`code_to_edit\` in the file located at \`${currentFilePath}\`. Using the given \`recently_viewed_code_snippets\`, \`current_file_content\`, \`edit_diff_history\`, \`area_around_code_to_edit\`, and the cursor position marked as \`${tags_1.PromptTags.CURSOR}\`, please continue the developer's work. Update the \`code_to_edit\` section by predicting and completing the changes they would have made next. Start your response with <EDIT>, <INSERT>, or <NO_CHANGE>. If you are making an edit, start with <EDIT> and then provide the rewritten code window followed by </EDIT>. If you are inserting new code, start with <INSERT> and then provide only the new code that will be inserted at the cursor position followed by </INSERT>. If no changes are necessary, reply only with <NO_CHANGE>. Avoid undoing or reverting the developer's last change unless there are obvious typos or errors.`;
            break;
        case xtabPromptOptions_1.PromptingStrategy.Nes41Miniv3:
            postScript = `The developer was working on a section of code within the tags <|code_to_edit|> in the file located at \`${currentFilePath}\`. Using the given \`recently_viewed_code_snippets\`, \`current_file_content\`, \`edit_diff_history\`, \`area_around_code_to_edit\`, and the cursor position marked as \`<|cursor|>\`, please continue the developer's work. Update the <|code_to_edit|> section by predicting and completing the changes they would have made next. Start your response with <EDIT> or <NO_CHANGE>. If you are making an edit, start with <EDIT> and then provide the rewritten code window followed by </EDIT>. If no changes are necessary, reply only with <NO_CHANGE>. Avoid undoing or reverting the developer's last change unless there are obvious typos or errors.`;
            break;
        case xtabPromptOptions_1.PromptingStrategy.Xtab275EditIntentShort:
        case xtabPromptOptions_1.PromptingStrategy.Xtab275EditIntent:
        case xtabPromptOptions_1.PromptingStrategy.Xtab275:
            postScript = `The developer was working on a section of code within the tags \`code_to_edit\` in the file located at \`${currentFilePath}\`. Using the given \`recently_viewed_code_snippets\`, \`current_file_content\`, \`edit_diff_history\`, \`area_around_code_to_edit\`, and the cursor position marked as \`${tags_1.PromptTags.CURSOR}\`, please continue the developer's work. Update the \`code_to_edit\` section by predicting and completing the changes they would have made next. Provide the revised code that was between the \`${tags_1.PromptTags.EDIT_WINDOW.start}\` and \`${tags_1.PromptTags.EDIT_WINDOW.end}\` tags, but do not include the tags themselves. Avoid undoing or reverting the developer's last change unless there are obvious typos or errors. Don't include the line numbers or the form #| in your response. Do not skip any lines. Do not be lazy.`;
            break;
        case xtabPromptOptions_1.PromptingStrategy.XtabAggressiveness:
            postScript = `<|aggressive|>${aggressivenessLevel}<|/aggressive|>`;
            break;
        case xtabPromptOptions_1.PromptingStrategy.Xtab275Aggressiveness:
            postScript = `The developer was working on a section of code within the tags \`code_to_edit\` in the file located at \`${currentFilePath}\`. Using the given \`recently_viewed_code_snippets\`, \`current_file_content\`, \`edit_diff_history\`, \`area_around_code_to_edit\`, and the cursor position marked as \`${tags_1.PromptTags.CURSOR}\`, please continue the developer's work. Update the \`code_to_edit\` section by predicting and completing the changes they would have made next. Provide the revised code that was between the \`${tags_1.PromptTags.EDIT_WINDOW.start}\` and \`${tags_1.PromptTags.EDIT_WINDOW.end}\` tags, but do not include the tags themselves. Avoid undoing or reverting the developer's last change unless there are obvious typos or errors. Don't include the line numbers or the form #| in your response. Do not skip any lines. Do not be lazy.\n<|aggressive|>${aggressivenessLevel}<|/aggressive|>`;
            break;
        case xtabPromptOptions_1.PromptingStrategy.PatchBased:
            postScript = `Output a modified diff style format with the changes you want. Each change patch must start with \`<filename>:<line number>\` and then include some non empty "anchor lines" preceded by \`-\` and the new lines meant to replace them preceded by \`+\`. Put your changes in the order that makes the most sense, for example edits inside the code_to_edit region and near the user's <|cursor|> should always be prioritized. Output "<NO_EDIT>" if you don't have a good edit candidate.`;
            break;
        case xtabPromptOptions_1.PromptingStrategy.SimplifiedSystemPrompt:
        case xtabPromptOptions_1.PromptingStrategy.CopilotNesXtab:
        case undefined:
            postScript = `The developer was working on a section of code within the tags \`code_to_edit\` in the file located at \`${currentFilePath}\`. \
Using the given \`recently_viewed_code_snippets\`, \`current_file_content\`, \`edit_diff_history\`, \`area_around_code_to_edit\`, and the cursor \
position marked as \`${tags_1.PromptTags.CURSOR}\`, please continue the developer's work. Update the \`code_to_edit\` section by predicting and completing the changes \
they would have made next. Provide the revised code that was between the \`${tags_1.PromptTags.EDIT_WINDOW.start}\` and \`${tags_1.PromptTags.EDIT_WINDOW.end}\` tags with the following format, but do not include the tags themselves.
\`\`\`
// Your revised code goes here
\`\`\``;
            break;
        default:
            (0, assert_1.assertNever)(strategy);
    }
    const formattedPostScript = postScript === undefined ? '' : `\n\n${postScript}`;
    return formattedPostScript;
}
function getRelatedInformation(langCtx) {
    if (langCtx === undefined) {
        return '';
    }
    const traits = langCtx.items
        .filter(ctx => ctx.context.kind === languageContextService_1.ContextKind.Trait)
        .map(t => t.context);
    if (traits.length === 0) {
        return '';
    }
    const relatedInformation = [];
    for (const trait of traits) {
        relatedInformation.push(`${trait.name}: ${trait.value}`);
    }
    return `Consider this related information:\n${relatedInformation.join('\n')}`;
}
function truncateCode(lines, fromBeginning, maxTokens) {
    if (!lines.length) {
        return [0, 0];
    }
    const allowedLength = maxTokens * 4;
    let totalLength = 0;
    let i = fromBeginning ? lines.length - 1 : 0;
    while (totalLength < allowedLength) {
        totalLength += lines[i].length + 1; // +1 for \n
        if (fromBeginning) {
            i--;
            if (i < 0) {
                break;
            }
        }
        else {
            i++;
            if (i >= lines.length) {
                break;
            }
        }
    }
    if (fromBeginning) {
        return [i + 1, lines.length];
    }
    else {
        return [0, i];
    }
}
exports.N_LINES_ABOVE = 2;
exports.N_LINES_BELOW = 5;
exports.N_LINES_AS_CONTEXT = 15;
function expandRangeToPageRange(currentDocLines, areaAroundEditWindowLinesRange, pageSize, maxTokens, computeTokens, prioritizeAboveCursor) {
    const totalNOfPages = Math.ceil(currentDocLines.length / pageSize);
    function computeTokensForPage(kthPage) {
        const start = kthPage * pageSize;
        const end = Math.min(start + pageSize, currentDocLines.length);
        const page = currentDocLines.slice(start, end);
        return (0, promptCraftingUtils_1.countTokensForLines)(page, computeTokens);
    }
    // [0, pageSize) -> 0, [pageSize, 2*pageSize) -> 1, ...
    // eg 5 -> 0, 63 -> 6
    let firstPageIdx = Math.floor(areaAroundEditWindowLinesRange.start / pageSize);
    let lastPageIdxIncl = Math.floor((areaAroundEditWindowLinesRange.endExclusive - 1) / pageSize);
    const availableTokenBudget = maxTokens - (0, arrays_1.range)(firstPageIdx, lastPageIdxIncl + 1).reduce((sum, idx) => sum + computeTokensForPage(idx), 0);
    if (availableTokenBudget < 0) {
        return { firstPageIdx, lastPageIdxIncl, budgetLeft: availableTokenBudget };
    }
    let tokenBudget = availableTokenBudget;
    // TODO: this's specifically implemented with some code duplication to not accidentally change existing behavior
    if (!prioritizeAboveCursor) { // both above and below get the half of budget
        const halfOfAvailableTokenBudget = Math.floor(availableTokenBudget / 2);
        tokenBudget = halfOfAvailableTokenBudget; // split by 2 to give both above and below areaAroundCode same budget
        for (let i = firstPageIdx - 1; i >= 0 && tokenBudget > 0; --i) {
            const tokenCountForPage = computeTokensForPage(i);
            const newTokenBudget = tokenBudget - tokenCountForPage;
            if (newTokenBudget < 0) {
                break;
            }
            firstPageIdx = i;
            tokenBudget = newTokenBudget;
        }
        tokenBudget = halfOfAvailableTokenBudget;
        for (let i = lastPageIdxIncl + 1; i < totalNOfPages && tokenBudget > 0; ++i) {
            const tokenCountForPage = computeTokensForPage(i);
            const newTokenBudget = tokenBudget - tokenCountForPage;
            if (newTokenBudget < 0) {
                break;
            }
            lastPageIdxIncl = i;
            tokenBudget = newTokenBudget;
        }
    }
    else { // code above consumes as much as it can and the leftover budget is given to code below
        tokenBudget = availableTokenBudget;
        for (let i = firstPageIdx - 1; i >= 0 && tokenBudget > 0; --i) {
            const tokenCountForPage = computeTokensForPage(i);
            const newTokenBudget = tokenBudget - tokenCountForPage;
            if (newTokenBudget < 0) {
                break;
            }
            firstPageIdx = i;
            tokenBudget = newTokenBudget;
        }
        for (let i = lastPageIdxIncl + 1; i < totalNOfPages && tokenBudget > 0; ++i) {
            const tokenCountForPage = computeTokensForPage(i);
            const newTokenBudget = tokenBudget - tokenCountForPage;
            if (newTokenBudget < 0) {
                break;
            }
            lastPageIdxIncl = i;
            tokenBudget = newTokenBudget;
        }
    }
    return { firstPageIdx, lastPageIdxIncl, budgetLeft: tokenBudget };
}
function clipPreservingRange(docLines, rangeToPreserve, computeTokens, pageSize, opts) {
    // subtract budget consumed by rangeToPreserve
    const linesToPreserve = docLines.slice(rangeToPreserve.start, rangeToPreserve.endExclusive);
    const availableTokenBudget = opts.maxTokens - (0, promptCraftingUtils_1.countTokensForLines)(linesToPreserve, computeTokens);
    if (availableTokenBudget < 0) {
        return result_1.Result.error('outOfBudget');
    }
    const { firstPageIdx, lastPageIdxIncl } = expandRangeToPageRange(docLines, rangeToPreserve, pageSize, availableTokenBudget, computeTokens, opts.prioritizeAboveCursor);
    const linesOffsetStart = firstPageIdx * pageSize;
    const linesOffsetEndExcl = (lastPageIdxIncl + 1) * pageSize;
    return result_1.Result.ok(new offsetRange_1.OffsetRange(linesOffsetStart, linesOffsetEndExcl));
}
class ClippedDocument {
    constructor(
    /** The lines of the document that were kept after clipping. */
    lines, 
    /** The line range in the original document that corresponds to the kept lines. */
    keptRange) {
        this.lines = lines;
        this.keptRange = keptRange;
    }
}
exports.ClippedDocument = ClippedDocument;
function createTaggedCurrentFileContentUsingPagedClipping(currentDocLines, areaAroundCodeToEdit, areaAroundEditWindowLinesRange, computeTokens, pageSize, opts) {
    const r = clipPreservingRange(currentDocLines, areaAroundEditWindowLinesRange, computeTokens, pageSize, opts);
    if (r.isError()) {
        return r;
    }
    const rangeToKeep = r.val;
    const taggedCurrentFileContent = [
        ...currentDocLines.slice(rangeToKeep.start, areaAroundEditWindowLinesRange.start),
        ...areaAroundCodeToEdit,
        ...currentDocLines.slice(areaAroundEditWindowLinesRange.endExclusive, rangeToKeep.endExclusive),
    ];
    const keptRange = new offsetRange_1.OffsetRange(rangeToKeep.start, rangeToKeep.start + taggedCurrentFileContent.length);
    return result_1.Result.ok(new ClippedDocument(taggedCurrentFileContent, keptRange));
}
function addLineNumbers(lines, option) {
    switch (option) {
        case xtabPromptOptions.IncludeLineNumbersOption.WithSpaceAfter:
            return lines.map((line, idx) => `${idx}| ${line}`);
        case xtabPromptOptions.IncludeLineNumbersOption.WithoutSpace:
            return lines.map((line, idx) => `${idx}|${line}`);
        case xtabPromptOptions.IncludeLineNumbersOption.None:
            return [...lines];
        default:
            (0, assert_1.assertNever)(option);
    }
}
function constructTaggedFile(currentDocument, editWindowLinesRange, areaAroundEditWindowLinesRange, promptOptions, computeTokens, opts) {
    // Content with cursor tag - always created for areaAroundCodeToEdit
    const contentWithCursorAsLinesOriginal = (() => {
        const addCursorTagEdit = stringEdit_1.StringEdit.single(stringEdit_1.StringReplacement.insert(currentDocument.cursorOffset, tags_1.PromptTags.CURSOR));
        const contentWithCursor = addCursorTagEdit.applyOnText(currentDocument.content);
        return contentWithCursor.getLines();
    })();
    const contentWithCursorAsLines = addLineNumbers(contentWithCursorAsLinesOriginal, opts.includeLineNumbers.areaAroundCodeToEdit);
    const editWindowWithCursorAsLines = contentWithCursorAsLines.slice(editWindowLinesRange.start, editWindowLinesRange.endExclusive);
    const areaAroundCodeToEdit = [
        tags_1.PromptTags.AREA_AROUND.start,
        ...contentWithCursorAsLines.slice(areaAroundEditWindowLinesRange.start, editWindowLinesRange.start),
        tags_1.PromptTags.EDIT_WINDOW.start,
        ...editWindowWithCursorAsLines,
        tags_1.PromptTags.EDIT_WINDOW.end,
        ...contentWithCursorAsLines.slice(editWindowLinesRange.endExclusive, areaAroundEditWindowLinesRange.endExclusive),
        tags_1.PromptTags.AREA_AROUND.end
    ];
    // For current file content, optionally include cursor tag based on includeCursorTag option
    const currentFileContentSourceLines = promptOptions.currentFile.includeCursorTag
        ? contentWithCursorAsLinesOriginal
        : currentDocument.lines;
    const currentFileContentWithCursorLines = addLineNumbers(currentFileContentSourceLines, opts.includeLineNumbers.currentFileContent);
    const currentFileContentLines = addLineNumbers(currentDocument.lines, opts.includeLineNumbers.currentFileContent);
    let areaAroundCodeToEditForCurrentFile;
    if (promptOptions.currentFile.includeTags && opts.includeLineNumbers.currentFileContent === opts.includeLineNumbers.areaAroundCodeToEdit) {
        areaAroundCodeToEditForCurrentFile = areaAroundCodeToEdit;
    }
    else {
        // Use currentFileContentWithCursorLines for edit window too
        const editWindowLines = currentFileContentWithCursorLines.slice(editWindowLinesRange.start, editWindowLinesRange.endExclusive);
        areaAroundCodeToEditForCurrentFile = [
            ...currentFileContentWithCursorLines.slice(areaAroundEditWindowLinesRange.start, editWindowLinesRange.start),
            ...editWindowLines,
            ...currentFileContentWithCursorLines.slice(editWindowLinesRange.endExclusive, areaAroundEditWindowLinesRange.endExclusive),
        ];
    }
    const taggedCurrentFileContentResult = createTaggedCurrentFileContentUsingPagedClipping(currentFileContentLines, areaAroundCodeToEditForCurrentFile, areaAroundEditWindowLinesRange, computeTokens, promptOptions.pagedClipping.pageSize, promptOptions.currentFile);
    return taggedCurrentFileContentResult.map(clippedTaggedCurrentDoc => ({
        clippedTaggedCurrentDoc,
        areaAroundCodeToEdit: areaAroundCodeToEdit.join('\n'),
    }));
}
//# sourceMappingURL=promptCrafting.js.map