/*---------------------------------------------------------------------------------------------
 *  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 { FuzzyScore } from '../../../../base/common/filters.js';
import { RefCountedDisposable } from '../../../../base/common/lifecycle.js';
import { registerEditorContribution } from '../../../browser/editorExtensions.js';
import { ICodeEditorService } from '../../../browser/services/codeEditorService.js';
import { Range } from '../../../common/core/range.js';
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
import { CompletionItemInsertTextRule } from '../../../common/standalone/standaloneEnums.js';
import { CompletionModel, LineContext } from './completionModel.js';
import { provideSuggestionItems, QuickSuggestionsOptions } from './suggest.js';
import { WordDistance } from './wordDistance.js';
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
class InlineCompletionResults extends RefCountedDisposable {
    constructor(model, line, word, completionModel, completions) {
        super(completions.disposable);
        this.model = model;
        this.line = line;
        this.word = word;
        this.completionModel = completionModel;
    }
    canBeReused(model, line, word) {
        return this.model === model // same model
            && this.line === line && this.word.startColumn === word.startColumn && this.word.endColumn < word.endColumn // same word
            && this.completionModel.incomplete.size === 0; // no incomplete results
    }
    get items() {
        var _a;
        const result = [];
        for (const item of this.completionModel.items) {
            if (item.score === FuzzyScore.Default) {
                // skip items that have no overlap
                continue;
            }
            const range = new Range(item.editStart.lineNumber, item.editStart.column, item.editInsertEnd.lineNumber, item.editInsertEnd.column + this.completionModel.lineContext.characterCountDelta // end PLUS character delta
            );
            const insertText = item.completion.insertTextRules && (item.completion.insertTextRules & CompletionItemInsertTextRule.InsertAsSnippet)
                ? { snippet: item.completion.insertText }
                : item.completion.insertText;
            result.push({
                range,
                filterText: (_a = item.filterTextLow) !== null && _a !== void 0 ? _a : item.labelLow,
                insertText,
                command: item.completion.command,
                additionalTextEdits: item.completion.additionalTextEdits
            });
        }
        return result;
    }
}
let SuggestInlineCompletions = class SuggestInlineCompletions {
    constructor(_getEditorOption, _languageFeatureService, _clipboardService) {
        this._getEditorOption = _getEditorOption;
        this._languageFeatureService = _languageFeatureService;
        this._clipboardService = _clipboardService;
    }
    provideInlineCompletions(model, position, context, token) {
        var _a;
        return __awaiter(this, void 0, void 0, function* () {
            if (context.selectedSuggestionInfo) {
                return;
            }
            const config = this._getEditorOption(79 /* EditorOption.quickSuggestions */, model);
            if (QuickSuggestionsOptions.isAllOff(config)) {
                // quick suggest is off (for this model/language)
                return;
            }
            model.tokenizeIfCheap(position.lineNumber);
            const lineTokens = model.getLineTokens(position.lineNumber);
            const tokenType = lineTokens.getStandardTokenType(lineTokens.findTokenIndexAtOffset(Math.max(position.column - 1 - 1, 0)));
            if (QuickSuggestionsOptions.valueFor(config, tokenType) !== 'inline') {
                // quick suggest is off (for this token)
                return undefined;
            }
            const wordInfo = model.getWordAtPosition(position);
            if (!wordInfo || wordInfo.word.length === 0 || wordInfo.endColumn !== position.column) {
                // not without true prefix, not inside word
                return;
            }
            let result;
            const leadingLineContents = model.getValueInRange(new Range(position.lineNumber, 1, position.lineNumber, position.column));
            if ((_a = this._lastResult) === null || _a === void 0 ? void 0 : _a.canBeReused(model, position.lineNumber, wordInfo)) {
                // reuse a previous result iff possible, only a refilter is needed
                // TODO@jrieken this can be improved further and only incomplete results can be updated
                // console.log(`REUSE with ${wordInfo.word}`);
                const newLineContext = new LineContext(leadingLineContents, position.column - this._lastResult.word.endColumn);
                this._lastResult.completionModel.lineContext = newLineContext;
                this._lastResult.acquire();
                result = this._lastResult;
            }
            else {
                // refesh model is required
                const completions = yield provideSuggestionItems(this._languageFeatureService.completionProvider, model, position, undefined, undefined, token);
                let clipboardText;
                if (completions.needsClipboard) {
                    clipboardText = yield this._clipboardService.readText();
                }
                const completionModel = new CompletionModel(completions.items, position.column, new LineContext(leadingLineContents, 0), WordDistance.None, this._getEditorOption(106 /* EditorOption.suggest */, model), this._getEditorOption(101 /* EditorOption.snippetSuggestions */, model), clipboardText);
                result = new InlineCompletionResults(model, position.lineNumber, wordInfo, completionModel, completions);
            }
            this._lastResult = result;
            return result;
        });
    }
    freeInlineCompletions(result) {
        result.release();
    }
};
SuggestInlineCompletions = __decorate([
    __param(1, ILanguageFeaturesService),
    __param(2, IClipboardService)
], SuggestInlineCompletions);
let EditorContribution = class EditorContribution {
    constructor(_editor, languageFeatureService, editorService, instaService) {
        // HACK - way to contribute something only once
        if (++EditorContribution._counter === 1) {
            const provider = instaService.createInstance(SuggestInlineCompletions, (id, model) => {
                var _a;
                // HACK - reuse the editor options world outside from a "normal" contribution
                const editor = (_a = editorService.listCodeEditors().find(editor => editor.getModel() === model)) !== null && _a !== void 0 ? _a : _editor;
                return editor.getOption(id);
            });
            EditorContribution._disposable = languageFeatureService.inlineCompletionsProvider.register('*', provider);
        }
    }
    dispose() {
        var _a;
        if (--EditorContribution._counter === 0) {
            (_a = EditorContribution._disposable) === null || _a === void 0 ? void 0 : _a.dispose();
            EditorContribution._disposable = undefined;
        }
    }
};
EditorContribution._counter = 0;
EditorContribution = __decorate([
    __param(1, ILanguageFeaturesService),
    __param(2, ICodeEditorService),
    __param(3, IInstantiationService)
], EditorContribution);
registerEditorContribution('suggest.inlineCompletionsProvider', EditorContribution);
