
import { __decorate, __param } from '../../../../../../../../external/tslib/tslib.es6.js';
import { CancellationTokenSource } from '../../../../../base/common/cancellation.js';
import { equalsIfDefined, itemEquals } from '../../../../../base/common/equals.js';
import { matchesSubString } from '../../../../../base/common/filters.js';
import { Disposable, MutableDisposable } from '../../../../../base/common/lifecycle.js';
import { disposableObservableValue, observableValue, transaction } from '../../../../../base/common/observableInternal/base.js';
import '../../../../../base/common/event.js';
import '../../../../../base/common/observableInternal/autorun.js';
import { derivedOpts } from '../../../../../base/common/observableInternal/derived.js';
import { observableFromEvent } from '../../../../../base/common/observableInternal/utils.js';
import { ICommandService } from '../../../../../platform/commands/common/commands.service.js';
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.service.js';
import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.service.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { ILogService } from '../../../../../platform/log/common/log.service.js';
import { observableConfigValue } from '../../../../../platform/observable/common/platformObservableUtils.js';
import { Range } from '../../../../common/core/range.js';
import { SingleTextEdit } from '../../../../common/core/textEdit.js';
import { TextLength } from '../../../../common/core/textLength.js';
import { InlineCompletionTriggerKind } from '../../../../common/languages.js';
import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js';
import { EndOfLinePreference } from '../../../../common/model.js';
import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js';
import { provideInlineCompletions } from './provideInlineCompletions.js';
import { singleTextRemoveCommonPrefix } from './singleTextEditHelpers.js';

var InlineCompletionsSource_1;
let InlineCompletionsSource = class InlineCompletionsSource extends Disposable {
    static { InlineCompletionsSource_1 = this; }
    static { this._requestId = 0; }
    constructor(_textModel, _versionId, _debounceValue, _languageFeaturesService, _languageConfigurationService, _logService, _configurationService, _instantiationService) {
        super();
        this._textModel = _textModel;
        this._versionId = _versionId;
        this._debounceValue = _debounceValue;
        this._languageFeaturesService = _languageFeaturesService;
        this._languageConfigurationService = _languageConfigurationService;
        this._logService = _logService;
        this._configurationService = _configurationService;
        this._instantiationService = _instantiationService;
        this._updateOperation = this._register(( new MutableDisposable()));
        this.inlineCompletions = disposableObservableValue('inlineCompletions', undefined);
        this.suggestWidgetInlineCompletions = disposableObservableValue('suggestWidgetInlineCompletions', undefined);
        this._loggingEnabled = observableConfigValue('editor.inlineSuggest.logFetch', false, this._configurationService).recomputeInitiallyAndOnChange(this._store);
        this._structuredFetchLogger = this._register(this._instantiationService.createInstance(StructuredLogger.cast(), 'editor.inlineSuggest.logFetch.commandId'));
        this.loading = observableValue(this, false);
        this._register(this._textModel.onDidChangeContent(() => {
            this._updateOperation.clear();
        }));
    }
    _log(entry) {
        if (this._loggingEnabled.get()) {
            this._logService.info(formatRecordableLogEntry(entry));
        }
        this._structuredFetchLogger.log(entry);
    }
    fetch(position, context, activeInlineCompletion) {
        const request = ( new UpdateRequest(position, context, this._textModel.getVersionId()));
        const target = context.selectedSuggestionInfo ? this.suggestWidgetInlineCompletions : this.inlineCompletions;
        if (this._updateOperation.value?.request.satisfies(request)) {
            return this._updateOperation.value.promise;
        }
        else if (target.get()?.request.satisfies(request)) {
            return Promise.resolve(true);
        }
        this.loading.set(true, undefined);
        const updateOngoing = !!this._updateOperation.value;
        this._updateOperation.clear();
        const source = ( new CancellationTokenSource());
        const promise = (async () => {
            const shouldDebounce = updateOngoing || context.triggerKind === InlineCompletionTriggerKind.Automatic;
            if (shouldDebounce) {
                await wait(this._debounceValue.get(this._textModel), source.token);
            }
            if (source.token.isCancellationRequested || this._store.isDisposed || this._textModel.getVersionId() !== request.versionId) {
                return false;
            }
            const requestId = InlineCompletionsSource_1._requestId++;
            if (this._loggingEnabled.get() || this._structuredFetchLogger.isEnabled.get()) {
                this._log({ sourceId: 'InlineCompletions.fetch', kind: 'start', requestId, modelUri: ( this._textModel.uri.toString()), modelVersion: this._textModel.getVersionId(), context: { triggerKind: context.triggerKind }, time: Date.now() });
            }
            const startTime = ( new Date());
            let updatedCompletions = undefined;
            let error = undefined;
            try {
                updatedCompletions = await provideInlineCompletions(this._languageFeaturesService.inlineCompletionsProvider, position, this._textModel, context, source.token, this._languageConfigurationService);
            }
            catch (e) {
                error = e;
                throw e;
            }
            finally {
                if (this._loggingEnabled.get() || this._structuredFetchLogger.isEnabled.get()) {
                    if (source.token.isCancellationRequested || this._store.isDisposed || this._textModel.getVersionId() !== request.versionId) {
                        error = 'canceled';
                    }
                    const result = updatedCompletions?.completions.map(c => ({
                        range: ( c.range.toString()),
                        text: c.insertText,
                        isInlineEdit: !!c.sourceInlineCompletion.isInlineEdit,
                        source: c.source.provider.groupId,
                    }));
                    this._log({ sourceId: 'InlineCompletions.fetch', kind: 'end', requestId, durationMs: (Date.now() - startTime.getTime()), error, result, time: Date.now() });
                }
            }
            if (source.token.isCancellationRequested || this._store.isDisposed || this._textModel.getVersionId() !== request.versionId) {
                updatedCompletions.dispose();
                return false;
            }
            const endTime = ( new Date());
            this._debounceValue.update(this._textModel, endTime.getTime() - startTime.getTime());
            const completions = ( new UpToDateInlineCompletions(updatedCompletions, request, this._textModel, this._versionId));
            if (activeInlineCompletion) {
                const asInlineCompletion = activeInlineCompletion.toInlineCompletion(undefined);
                if (activeInlineCompletion.canBeReused(this._textModel, position) && !( updatedCompletions.has(asInlineCompletion))) {
                    completions.prepend(activeInlineCompletion.inlineCompletion, asInlineCompletion.range, true);
                }
            }
            this._updateOperation.clear();
            transaction(tx => {
                target.set(completions, tx);
                this.loading.set(false, tx);
            });
            return true;
        })();
        const updateOperation = ( new UpdateOperation(request, source, promise));
        this._updateOperation.value = updateOperation;
        return promise;
    }
    clear(tx) {
        this._updateOperation.clear();
        this.inlineCompletions.set(undefined, tx);
        this.suggestWidgetInlineCompletions.set(undefined, tx);
    }
    clearSuggestWidgetInlineCompletions(tx) {
        if (this._updateOperation.value?.request.context.selectedSuggestionInfo) {
            this._updateOperation.clear();
        }
        this.suggestWidgetInlineCompletions.set(undefined, tx);
    }
    cancelUpdate() {
        this._updateOperation.clear();
    }
};
InlineCompletionsSource = InlineCompletionsSource_1 = ( __decorate([
    ( __param(3, ILanguageFeaturesService)),
    ( __param(4, ILanguageConfigurationService)),
    ( __param(5, ILogService)),
    ( __param(6, IConfigurationService)),
    ( __param(7, IInstantiationService))
], InlineCompletionsSource));
function wait(ms, cancellationToken) {
    return ( new Promise(resolve => {
        let d = undefined;
        const handle = setTimeout(() => {
            if (d) {
                d.dispose();
            }
            resolve();
        }, ms);
        if (cancellationToken) {
            d = cancellationToken.onCancellationRequested(() => {
                clearTimeout(handle);
                if (d) {
                    d.dispose();
                }
                resolve();
            });
        }
    }));
}
class UpdateRequest {
    constructor(position, context, versionId) {
        this.position = position;
        this.context = context;
        this.versionId = versionId;
    }
    satisfies(other) {
        return this.position.equals(other.position)
            && equalsIfDefined(this.context.selectedSuggestionInfo, other.context.selectedSuggestionInfo, itemEquals())
            && (other.context.triggerKind === InlineCompletionTriggerKind.Automatic
                || this.context.triggerKind === InlineCompletionTriggerKind.Explicit)
            && this.versionId === other.versionId;
    }
    get isExplicitRequest() {
        return this.context.triggerKind === InlineCompletionTriggerKind.Explicit;
    }
}
class UpdateOperation {
    constructor(request, cancellationTokenSource, promise) {
        this.request = request;
        this.cancellationTokenSource = cancellationTokenSource;
        this.promise = promise;
    }
    dispose() {
        this.cancellationTokenSource.cancel();
    }
}
class UpToDateInlineCompletions {
    get inlineCompletions() { return this._inlineCompletions; }
    constructor(inlineCompletionProviderResult, request, _textModel, _versionId) {
        this.inlineCompletionProviderResult = inlineCompletionProviderResult;
        this.request = request;
        this._textModel = _textModel;
        this._versionId = _versionId;
        this._refCount = 1;
        this._prependedInlineCompletionItems = [];
        const ids = _textModel.deltaDecorations([], ( inlineCompletionProviderResult.completions.map(i => ({
            range: i.range,
            options: {
                description: 'inline-completion-tracking-range'
            },
        }))));
        this._inlineCompletions = ( inlineCompletionProviderResult.completions.map((i, index) => ( new InlineCompletionWithUpdatedRange(i, ids[index], this._textModel, this._versionId, this.request))));
    }
    clone() {
        this._refCount++;
        return this;
    }
    dispose() {
        this._refCount--;
        if (this._refCount === 0) {
            setTimeout(() => {
                if (!this._textModel.isDisposed()) {
                    this._textModel.deltaDecorations(( this._inlineCompletions.map(i => i.decorationId)), []);
                }
            }, 0);
            this.inlineCompletionProviderResult.dispose();
            for (const i of this._prependedInlineCompletionItems) {
                i.source.removeRef();
            }
        }
    }
    prepend(inlineCompletion, range, addRefToSource) {
        if (addRefToSource) {
            inlineCompletion.source.addRef();
        }
        const id = this._textModel.deltaDecorations([], [{
                range,
                options: {
                    description: 'inline-completion-tracking-range'
                },
            }])[0];
        this._inlineCompletions.unshift(( new InlineCompletionWithUpdatedRange(inlineCompletion, id, this._textModel, this._versionId, this.request)));
        this._prependedInlineCompletionItems.push(inlineCompletion);
    }
}
class InlineCompletionWithUpdatedRange {
    get forwardStable() {
        return this.inlineCompletion.source.inlineCompletions.enableForwardStability ?? false;
    }
    constructor(inlineCompletion, decorationId, _textModel, _modelVersion, request) {
        this.inlineCompletion = inlineCompletion;
        this.decorationId = decorationId;
        this._textModel = _textModel;
        this._modelVersion = _modelVersion;
        this.request = request;
        this.semanticId = JSON.stringify([
            this.inlineCompletion.filterText,
            this.inlineCompletion.insertText,
            ( this.inlineCompletion.range.getStartPosition().toString())
        ]);
        this._updatedRange = derivedOpts({ owner: this, equalsFn: Range.equalsRange }, reader => {
            this._modelVersion.read(reader);
            return this._textModel.getDecorationRange(this.decorationId);
        });
    }
    toInlineCompletion(reader) {
        return this.inlineCompletion.withRange(this._updatedRange.read(reader) ?? emptyRange);
    }
    toSingleTextEdit(reader) {
        return ( new SingleTextEdit(
            this._updatedRange.read(reader) ?? emptyRange,
            this.inlineCompletion.insertText
        ));
    }
    isVisible(model, cursorPosition, reader) {
        const minimizedReplacement = singleTextRemoveCommonPrefix(this._toFilterTextReplacement(reader), model);
        const updatedRange = this._updatedRange.read(reader);
        if (!updatedRange
            || !this.inlineCompletion.range.getStartPosition().equals(updatedRange.getStartPosition())
            || cursorPosition.lineNumber !== minimizedReplacement.range.startLineNumber
            || minimizedReplacement.isEmpty
        ) {
            return false;
        }
        const originalValue = model.getValueInRange(minimizedReplacement.range, EndOfLinePreference.LF);
        const filterText = minimizedReplacement.text;
        const cursorPosIndex = Math.max(0, cursorPosition.column - minimizedReplacement.range.startColumn);
        let filterTextBefore = filterText.substring(0, cursorPosIndex);
        let filterTextAfter = filterText.substring(cursorPosIndex);
        let originalValueBefore = originalValue.substring(0, cursorPosIndex);
        let originalValueAfter = originalValue.substring(cursorPosIndex);
        const originalValueIndent = model.getLineIndentColumn(minimizedReplacement.range.startLineNumber);
        if (minimizedReplacement.range.startColumn <= originalValueIndent) {
            originalValueBefore = originalValueBefore.trimStart();
            if (originalValueBefore.length === 0) {
                originalValueAfter = originalValueAfter.trimStart();
            }
            filterTextBefore = filterTextBefore.trimStart();
            if (filterTextBefore.length === 0) {
                filterTextAfter = filterTextAfter.trimStart();
            }
        }
        return filterTextBefore.startsWith(originalValueBefore)
            && !!matchesSubString(originalValueAfter, filterTextAfter);
    }
    canBeReused(model, position) {
        const updatedRange = this._updatedRange.read(undefined);
        const result = !!updatedRange
            && updatedRange.containsPosition(position)
            && this.isVisible(model, position, undefined)
            && TextLength.ofRange(updatedRange).isGreaterThanOrEqualTo(TextLength.ofRange(this.inlineCompletion.range));
        return result;
    }
    _toFilterTextReplacement(reader) {
        return ( new SingleTextEdit(
            this._updatedRange.read(reader) ?? emptyRange,
            this.inlineCompletion.filterText
        ));
    }
}
const emptyRange = ( new Range(1, 1, 1, 1));
function formatRecordableLogEntry(entry) {
    return entry.sourceId + ' @@ ' + JSON.stringify({ ...entry, sourceId: undefined });
}
let StructuredLogger = class StructuredLogger extends Disposable {
    static cast() {
        return this;
    }
    constructor(_contextKey, _contextKeyService, _commandService) {
        super();
        this._contextKey = _contextKey;
        this._contextKeyService = _contextKeyService;
        this._commandService = _commandService;
        this._contextKeyValue = observableContextKey(this._contextKey, this._contextKeyService).recomputeInitiallyAndOnChange(this._store);
        this.isEnabled = ( this._contextKeyValue.map(v => v !== undefined));
    }
    log(data) {
        const commandId = this._contextKeyValue.get();
        if (!commandId) {
            return false;
        }
        this._commandService.executeCommand(commandId, data);
        return true;
    }
};
StructuredLogger = ( __decorate([
    ( __param(1, IContextKeyService)),
    ( __param(2, ICommandService))
], StructuredLogger));
function observableContextKey(key, contextKeyService) {
    return observableFromEvent(contextKeyService.onDidChangeContext, () => contextKeyService.getContextKeyValue(key));
}

export { InlineCompletionWithUpdatedRange, InlineCompletionsSource, StructuredLogger, UpToDateInlineCompletions, formatRecordableLogEntry, observableContextKey };
