"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.getCurrentLine = getCurrentLine;
exports.isModelLineCompatible = isModelLineCompatible;
const position_1 = require("../../../util/vs/editor/common/core/position");
const positionToOffset_1 = require("../../../util/vs/editor/common/core/text/positionToOffset");
/**
 * Resolves the current content of a line after applying an intermediate
 * user edit to the original document.
 *
 * The line's 0-based index in the original document may no longer be
 * valid after the user inserts or deletes lines above it. This function
 * maps the line's character offset through the edit to find the correct
 * line in the resulting document.
 *
 * @param originalDoc A transformer for the original document text (reused to
 *                    avoid recomputing line offsets).
 * @param docLineIdx 0-based line index in the original document.
 * @returns The line content, or `undefined` if the line index is out of
 *          bounds or the original position falls inside a replacement range
 *          (making the mapping ambiguous).
 */
function getCurrentLine(originalDoc, docLineIdx, intermediateEdit, precomputed) {
    const lineNumber = docLineIdx + 1; // 1-based
    const lineCount = originalDoc.textLength.lineCount + 1;
    if (lineNumber < 1 || lineNumber > lineCount) {
        return undefined;
    }
    const lineStartOffset = originalDoc.getOffset(new position_1.Position(lineNumber, 1));
    // Walk through the edit's replacements (sorted, non-overlapping) and
    // accumulate the character-offset delta for replacements entirely before
    // the line start.
    let delta = 0;
    for (const replacement of intermediateEdit.replacements) {
        if (replacement.replaceRange.endExclusive <= lineStartOffset) {
            delta += replacement.newText.length - replacement.replaceRange.length;
        }
        else if (replacement.replaceRange.start < lineStartOffset) {
            // The line start falls inside a replacement — ambiguous.
            return undefined;
        }
        else {
            break;
        }
    }
    const mappedOffset = lineStartOffset + delta;
    const currentDoc = precomputed?.currentDoc ?? intermediateEdit.apply(originalDoc.text);
    const currentTransformer = precomputed?.currentTransformer ?? new positionToOffset_1.PositionOffsetTransformer(currentDoc);
    // Map the offset back to a position in the current document, then extract
    // the full line content.
    const currentPos = currentTransformer.getPosition(mappedOffset);
    const lineStart = currentTransformer.getOffset(new position_1.Position(currentPos.lineNumber, 1));
    const lineLen = currentTransformer.getLineLength(currentPos.lineNumber);
    return currentDoc.substring(lineStart, lineStart + lineLen);
}
/**
 * Computes the minimal edit between two versions of the same line by stripping
 * the longest common prefix and suffix.
 *
 * Example: `diffLine("function fi", "function fib")`
 *   → `{ startOffset: 11, endOffset: 11, replaced: "", inserted: "b" }`
 */
function diffLine(before, after) {
    let prefixLen = 0;
    while (prefixLen < before.length && prefixLen < after.length
        && before[prefixLen] === after[prefixLen]) {
        prefixLen++;
    }
    let suffixLen = 0;
    while (suffixLen < before.length - prefixLen && suffixLen < after.length - prefixLen
        && before[before.length - 1 - suffixLen] === after[after.length - 1 - suffixLen]) {
        suffixLen++;
    }
    return {
        startOffset: prefixLen,
        endOffset: before.length - suffixLen,
        replaced: before.substring(prefixLen, before.length - suffixLen),
        inserted: after.substring(prefixLen, after.length - suffixLen),
    };
}
/**
 * Checks whether the model's line output is compatible with what the user
 * has typed since the request started.
 *
 * Algorithm:
 * 1. Diff original → current to find what the user typed.
 * 2. Diff original → model to find what the model changed.
 * 3. If the user's edit range falls within the model's edit range, check whether
 *    the model's new text is a continuation of the user's typing.
 *
 * @example
 *   original: `function fi`, current: `function fib`, model: `function fibonacci(n): number`
 *   → user typed "b", model inserted "bonacci(n): number"
 *   → model text starts with "b" → compatible ✓
 *
 * @example
 *   original: `function fi`, current: `function fix`, model: `function fibonacci(n): number`
 *   → user typed "x", model inserted "bonacci(n): number"
 *   → model text does not start with "x" → incompatible ✗
 */
function isModelLineCompatible(originalLine, currentLine, modelLine) {
    const userEdit = diffLine(originalLine, currentLine);
    const modelEdit = diffLine(originalLine, modelLine);
    // No actual user change — trivially compatible.
    if (userEdit.replaced.length === 0 && userEdit.inserted.length === 0) {
        return true;
    }
    // The user's edit range must fall within the model's edit range.
    // If the user edited a region the model didn't touch, we can't determine
    // compatibility from the line alone.
    const userEditWithinModelEdit = userEdit.startOffset >= modelEdit.startOffset
        && userEdit.endOffset <= modelEdit.endOffset;
    if (!userEditWithinModelEdit) {
        return false;
    }
    return isUserEditCompatibleWithModelEdit(userEdit, modelEdit, currentLine, modelLine);
}
const AUTO_CLOSE_PAIRS = new Set(['()', '[]', '{}', '<>', '""', `''`, '``']);
/**
 * Checks whether the user's edit is compatible with the model's edit.
 *
 * For pure insertions, compatibility is determined by whether the model is
 * continuing the user's inserted text.
 *
 * For deletions and replacements, avoid treating an empty inserted string as
 * universally compatible. In those cases, only accept when the resulting line
 * already matches the model, or when the model is editing the exact same range
 * and replacing the exact same original text with a compatible continuation.
 */
function isUserEditCompatibleWithModelEdit(userEdit, modelEdit, currentLine, modelLine) {
    if (userEdit.replaced.length > 0) {
        if (currentLine === modelLine) {
            return true;
        }
        return userEdit.startOffset === modelEdit.startOffset
            && userEdit.endOffset === modelEdit.endOffset
            && userEdit.replaced === modelEdit.replaced
            && userEdit.inserted.length > 0
            && isUserTypingCompatibleWithModelText(userEdit.inserted, modelEdit.inserted);
    }
    return isUserTypingCompatibleWithModelText(userEdit.inserted, modelEdit.inserted);
}
/**
 * Checks whether the user's typed text is compatible with the model's new text.
 *
 * Rules:
 * 1. The model's new text must **start with** the user's typed text — the model
 *    is continuing what the user began.
 * 2. If the user's text is a known auto-close pair (e.g. `()`, `{}`), accept
 *    if both characters appear in order in the model's text (subsequence match).
 */
function isUserTypingCompatibleWithModelText(userTypedText, modelNewText) {
    if (modelNewText.startsWith(userTypedText)) {
        return true;
    }
    // Subsequence check for auto-close pairs: if the user typed e.g. "()" and the
    // model's text is "(x, y)", the pair characters appear in order.
    if (AUTO_CLOSE_PAIRS.has(userTypedText)) {
        return isSubsequenceOf(userTypedText, modelNewText);
    }
    return false;
}
/**
 * Returns true if every character of `subsequence` appears in `str` in order.
 */
function isSubsequenceOf(subsequence, str) {
    let si = 0;
    for (let ti = 0; ti < subsequence.length; ti++) {
        const idx = str.indexOf(subsequence[ti], si);
        if (idx === -1) {
            return false;
        }
        si = idx + 1;
    }
    return true;
}
//# sourceMappingURL=cursorLineDivergence.js.map