/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { mapFind } from '../../../../base/common/arrays.js';
import { BugIndicatingError, onUnexpectedExternalError } from '../../../../base/common/errors.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { autorunHandleChanges, derived, observableSignal, observableValue, transaction } from '../../../../base/common/observable.js';
import { isDefined } from '../../../../base/common/types.js';
import { EditOperation } from '../../../common/core/editOperation.js';
import { Position } from '../../../common/core/position.js';
import { Range } from '../../../common/core/range.js';
import { InlineCompletionTriggerKind } from '../../../common/languages.js';
import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';
import { GhostText } from './ghostText.js';
import { addPositions, lengthOfText } from './utils.js';
import { InlineCompletionsSource } from './inlineCompletionsSource.js';
import { SnippetController2 } from '../../snippet/browser/snippetController2.js';
import { ICommandService } from '../../../../platform/commands/common/commands.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
export var VersionIdChangeReason;
(function (VersionIdChangeReason) {
    VersionIdChangeReason[VersionIdChangeReason["Undo"] = 0] = "Undo";
    VersionIdChangeReason[VersionIdChangeReason["Redo"] = 1] = "Redo";
    VersionIdChangeReason[VersionIdChangeReason["AcceptWord"] = 2] = "AcceptWord";
    VersionIdChangeReason[VersionIdChangeReason["Other"] = 3] = "Other";
})(VersionIdChangeReason || (VersionIdChangeReason = {}));
let InlineCompletionsModel = class InlineCompletionsModel extends Disposable {
    get isAcceptingPartialWord() { return this._isAcceptingPartialWord; }
    get isNavigatingCurrentInlineCompletion() { return this._isNavigatingCurrentInlineCompletion; }
    constructor(textModel, selectedSuggestItem, cursorPosition, textModelVersionId, _debounceValue, _suggestPreviewEnabled, _suggestPreviewMode, _inlineSuggestMode, _enabled, _instantiationService, _commandService, _languageConfigurationService) {
        super();
        this.textModel = textModel;
        this.selectedSuggestItem = selectedSuggestItem;
        this.cursorPosition = cursorPosition;
        this.textModelVersionId = textModelVersionId;
        this._debounceValue = _debounceValue;
        this._suggestPreviewEnabled = _suggestPreviewEnabled;
        this._suggestPreviewMode = _suggestPreviewMode;
        this._inlineSuggestMode = _inlineSuggestMode;
        this._enabled = _enabled;
        this._instantiationService = _instantiationService;
        this._commandService = _commandService;
        this._languageConfigurationService = _languageConfigurationService;
        this._source = this._register(this._instantiationService.createInstance(InlineCompletionsSource, this.textModel, this._debounceValue));
        this._isActive = observableValue('isActive', false);
        this._isAcceptingPartialWord = false;
        this._isNavigatingCurrentInlineCompletion = false;
        this._filteredInlineCompletionItems = derived('filteredInlineCompletionItems', (reader) => {
            const c = this._source.inlineCompletions.read(reader);
            if (!c) {
                return [];
            }
            const versionId = this.textModelVersionId.read(reader);
            const inlineCompletions = c.getInlineCompletions(versionId);
            const model = this.textModel;
            const cursorPosition = model.validatePosition(this.cursorPosition.read(reader));
            const filteredCompletions = inlineCompletions.filter(c => c.isVisible(model, cursorPosition));
            return filteredCompletions;
        });
        // We use a semantic id to track the selection even if the cache changes.
        this._currentInlineCompletionId = undefined;
        this._selectedCompletionIdChanged = observableSignal('selectedCompletionIdChanged');
        this.currentInlineCompletionIndex = derived('currentCachedCompletionIndex', (reader) => {
            this._selectedCompletionIdChanged.read(reader);
            const filteredCompletions = this._filteredInlineCompletionItems.read(reader);
            if (!this._currentInlineCompletionId || filteredCompletions.length === 0) {
                return 0;
            }
            const idx = filteredCompletions.findIndex(v => v.semanticId === this._currentInlineCompletionId);
            if (idx === -1) {
                // Reset the selection so that the selection does not jump back when it appears again
                this._currentInlineCompletionId = undefined;
                return 0;
            }
            return idx;
        });
        this.currentInlineCompletion = derived('currentCachedCompletion', (reader) => {
            const filteredCompletions = this._filteredInlineCompletionItems.read(reader);
            const idx = this.currentInlineCompletionIndex.read(reader);
            return filteredCompletions[idx];
        });
        this.lastTriggerKind = this._source.inlineCompletions.map(v => v === null || v === void 0 ? void 0 : v.request.context.triggerKind);
        this.inlineCompletionsCount = derived('currentInlineCompletionsCount', reader => {
            if (this.lastTriggerKind.read(reader) === InlineCompletionTriggerKind.Explicit) {
                return this._filteredInlineCompletionItems.read(reader).length;
            }
            else {
                return undefined;
            }
        });
        this.ghostText = derived('ghostText', (reader) => {
            const versionId = this.textModelVersionId.read(reader);
            const model = this.textModel;
            const suggestItem = this.selectedSuggestItem.read(reader);
            if (suggestItem) {
                const suggestWidgetInlineCompletions = this._source.suggestWidgetInlineCompletions.read(reader);
                const candidateInlineCompletion = suggestWidgetInlineCompletions
                    ? suggestWidgetInlineCompletions.getInlineCompletions(versionId)
                    : [this.currentInlineCompletion.read(reader)].filter(isDefined);
                const suggestCompletion = suggestItem.toSingleTextEdit().removeCommonPrefix(model);
                const augmentedCompletion = mapFind(candidateInlineCompletion, c => {
                    let r = c.toSingleTextEdit();
                    r = r.removeCommonPrefix(model, Range.fromPositions(r.range.getStartPosition(), suggestItem.range.getEndPosition()));
                    return r.augments(suggestCompletion) ? r : undefined;
                });
                const isSuggestionPreviewEnabled = this._suggestPreviewEnabled.read(reader);
                if (!isSuggestionPreviewEnabled && !augmentedCompletion) {
                    return undefined;
                }
                const edit = augmentedCompletion !== null && augmentedCompletion !== void 0 ? augmentedCompletion : suggestCompletion;
                const editPreviewLength = augmentedCompletion ? augmentedCompletion.text.length - suggestCompletion.text.length : 0;
                const mode = this._suggestPreviewMode.read(reader);
                const cursor = this.cursorPosition.read(reader);
                const newGhostText = edit.computeGhostText(model, mode, cursor, editPreviewLength);
                // Show an invisible ghost text to reserve space
                return newGhostText !== null && newGhostText !== void 0 ? newGhostText : new GhostText(edit.range.endLineNumber, [], 0);
            }
            else {
                if (!this._isActive.read(reader)) {
                    return undefined;
                }
                const item = this.currentInlineCompletion.read(reader);
                if (!item) {
                    return undefined;
                }
                const replacement = item.toSingleTextEdit();
                const mode = this._inlineSuggestMode.read(reader);
                const cursor = this.cursorPosition.read(reader);
                return replacement.computeGhostText(model, mode, cursor);
            }
        });
        let preserveCurrentCompletion = false;
        const preserveCurrentCompletionReasons = new Set([
            VersionIdChangeReason.Redo,
            VersionIdChangeReason.Undo,
            VersionIdChangeReason.AcceptWord,
        ]);
        this._register(autorunHandleChanges('update', {
            handleChange: ctx => {
                if (ctx.didChange(this.textModelVersionId) && preserveCurrentCompletionReasons.has(ctx.change)) {
                    preserveCurrentCompletion = true;
                }
                return true;
            }
        }, (reader) => {
            if ((this._enabled.read(reader) && this.selectedSuggestItem.read(reader)) || this._isActive.read(reader)) {
                this._update(reader, InlineCompletionTriggerKind.Automatic, preserveCurrentCompletion);
            }
            preserveCurrentCompletion = false;
        }));
    }
    _update(reader, triggerKind, preserveCurrentCompletion = false) {
        var _a, _b;
        return __awaiter(this, void 0, void 0, function* () {
            preserveCurrentCompletion = preserveCurrentCompletion || ((_b = (_a = this.currentInlineCompletion.get()) === null || _a === void 0 ? void 0 : _a.inlineCompletion.source.inlineCompletions.enableForwardStability) !== null && _b !== void 0 ? _b : false);
            const suggestItem = this.selectedSuggestItem.read(reader);
            const cursorPosition = this.cursorPosition.read(reader);
            this.textModelVersionId.read(reader);
            const suggestWidgetInlineCompletions = this._source.suggestWidgetInlineCompletions.get();
            if (suggestWidgetInlineCompletions && !suggestItem) {
                const inlineCompletions = this._source.inlineCompletions.get();
                if (inlineCompletions && suggestWidgetInlineCompletions.request.versionId > inlineCompletions.request.versionId) {
                    this._source.inlineCompletions.set(suggestWidgetInlineCompletions.clone(), undefined);
                }
                this._source.clearSuggestWidgetInlineCompletions();
            }
            yield this._source.update(cursorPosition, { triggerKind, selectedSuggestionInfo: suggestItem === null || suggestItem === void 0 ? void 0 : suggestItem.toSelectedSuggestionInfo() }, preserveCurrentCompletion ? this.currentInlineCompletion.get() : undefined);
        });
    }
    trigger(tx) {
        this._isActive.set(true, tx);
    }
    stop(tx) {
        if (!tx) {
            transaction(tx => this.stop(tx));
            return;
        }
        this._isActive.set(false, tx);
        this._source.clear(tx);
    }
    next() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.triggerExplicitly();
            this._isNavigatingCurrentInlineCompletion = true;
            try {
                const completions = this._filteredInlineCompletionItems.get() || [];
                if (completions.length > 0) {
                    const newIdx = (this.currentInlineCompletionIndex.get() + 1) % completions.length;
                    this._currentInlineCompletionId = completions[newIdx].semanticId;
                }
                else {
                    this._currentInlineCompletionId = undefined;
                }
                this._selectedCompletionIdChanged.trigger(undefined);
            }
            finally {
                this._isNavigatingCurrentInlineCompletion = false;
            }
        });
    }
    previous() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.triggerExplicitly();
            this._isNavigatingCurrentInlineCompletion = true;
            try {
                const completions = this._filteredInlineCompletionItems.get() || [];
                if (completions.length > 0) {
                    const newIdx = (this.currentInlineCompletionIndex.get() + completions.length - 1) % completions.length;
                    this._currentInlineCompletionId = completions[newIdx].semanticId;
                }
                else {
                    this._currentInlineCompletionId = undefined;
                }
                this._selectedCompletionIdChanged.trigger(undefined);
            }
            finally {
                this._isNavigatingCurrentInlineCompletion = false;
            }
        });
    }
    triggerExplicitly() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this._update(undefined, InlineCompletionTriggerKind.Explicit);
        });
    }
    accept(editor) {
        var _a, _b;
        if (editor.getModel() !== this.textModel) {
            throw new BugIndicatingError();
        }
        const ghostText = this.ghostText.get();
        const completion = (_a = this.currentInlineCompletion.get()) === null || _a === void 0 ? void 0 : _a.toInlineCompletion();
        if (!ghostText || !completion) {
            return;
        }
        editor.pushUndoStop();
        if (completion.snippetInfo) {
            editor.executeEdits('inlineSuggestion.accept', [
                EditOperation.replaceMove(completion.range, ''),
                ...completion.additionalTextEdits
            ]);
            editor.setPosition(completion.snippetInfo.range.getStartPosition());
            (_b = SnippetController2.get(editor)) === null || _b === void 0 ? void 0 : _b.insert(completion.snippetInfo.snippet, { undoStopBefore: false });
        }
        else {
            editor.executeEdits('inlineSuggestion.accept', [
                EditOperation.replaceMove(completion.range, completion.insertText),
                ...completion.additionalTextEdits
            ]);
        }
        if (completion.command) {
            this._commandService
                .executeCommand(completion.command.id, ...(completion.command.arguments || []))
                .finally(() => {
                transaction(tx => {
                    this._source.clear(tx);
                });
            })
                .then(undefined, onUnexpectedExternalError);
        }
        else {
            transaction(tx => {
                this._source.clear(tx);
            });
        }
    }
    acceptNextWord(editor) {
        var _a;
        if (editor.getModel() !== this.textModel) {
            throw new BugIndicatingError();
        }
        const ghostText = this.ghostText.get();
        const completion = (_a = this.currentInlineCompletion.get()) === null || _a === void 0 ? void 0 : _a.toInlineCompletion();
        if (!ghostText || !completion) {
            return;
        }
        if (completion.snippetInfo || completion.filterText !== completion.insertText) {
            // not in WYSIWYG mode, partial commit might change completion, thus it is not supported
            this.accept(editor);
            return;
        }
        if (ghostText.parts.length === 0) {
            return;
        }
        const firstPart = ghostText.parts[0];
        const position = new Position(ghostText.lineNumber, firstPart.column);
        const line = firstPart.lines.join('\n');
        const langId = this.textModel.getLanguageIdAtPosition(ghostText.lineNumber, 1);
        const config = this._languageConfigurationService.getLanguageConfiguration(langId);
        const wordRegExp = new RegExp(config.wordDefinition.source, config.wordDefinition.flags.replace('g', ''));
        const m1 = line.match(wordRegExp);
        let acceptUntilIndexExclusive = 0;
        if (m1 && m1.index !== undefined) {
            if (m1.index === 0) {
                acceptUntilIndexExclusive = m1[0].length;
            }
            else {
                acceptUntilIndexExclusive = m1.index;
            }
        }
        else {
            acceptUntilIndexExclusive = line.length;
        }
        const wsRegExp = /\s+/g;
        const m2 = wsRegExp.exec(line);
        if (m2 && m2.index !== undefined) {
            if (m2.index + m2[0].length < acceptUntilIndexExclusive) {
                acceptUntilIndexExclusive = m2.index + m2[0].length;
            }
        }
        if (acceptUntilIndexExclusive === line.length && ghostText.parts.length === 1) {
            this.accept(editor);
            return;
        }
        const partialText = line.substring(0, acceptUntilIndexExclusive);
        this._isAcceptingPartialWord = true;
        try {
            editor.pushUndoStop();
            editor.executeEdits('inlineSuggestion.accept', [
                EditOperation.replace(Range.fromPositions(position), partialText),
            ]);
            const length = lengthOfText(partialText);
            editor.setPosition(addPositions(position, length));
        }
        finally {
            this._isAcceptingPartialWord = false;
        }
        if (completion.source.provider.handlePartialAccept) {
            const acceptedRange = Range.fromPositions(completion.range.getStartPosition(), addPositions(position, lengthOfText(partialText)));
            // This assumes that the inline completion and the model use the same EOL style.
            const text = editor.getModel().getValueInRange(acceptedRange, 1 /* EndOfLinePreference.LF */);
            completion.source.provider.handlePartialAccept(completion.source.inlineCompletions, completion.sourceInlineCompletion, text.length);
        }
    }
};
InlineCompletionsModel = __decorate([
    __param(9, IInstantiationService),
    __param(10, ICommandService),
    __param(11, ILanguageConfigurationService)
], InlineCompletionsModel);
export { InlineCompletionsModel };
