import { __decorate, __param } from '../../../../../../../external/tslib/tslib.es6.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { ResourceFileEdit, ResourceTextEdit, ResourceEdit } from '../../../../editor/browser/services/bulkEditService.js';
import { CTX_INLINE_CHAT_HAS_STASHED_SESSION } from '../common/inlineChat.js';
import { Range } from '../../../../editor/common/core/range.js';
import { ModelDecorationOptions } from '../../../../editor/common/model/textModel.js';
import { toErrorMessage } from '../../../../base/common/errorMessage.js';
import { isCancellationError } from '../../../../base/common/errors.js';
import { EditOperation } from '../../../../editor/common/core/editOperation.js';
import { DetailedLineRangeMapping } from '../../../../editor/common/diff/rangeMapping.js';
import { ITextFileService } from '../../../services/textfile/common/textfiles.service.js';
import { ILanguageService } from '../../../../editor/common/languages/language.js';
import { ResourceMap } from '../../../../base/common/map.js';
import { Schemas } from '../../../../base/common/network.js';
import { isEqual } from '../../../../base/common/resources.js';
import { IInlineChatSessionService } from './inlineChatSessionService.service.js';
import { IEditorWorkerService } from '../../../../editor/common/services/editorWorker.js';
import { asRange } from './utils.js';
import { coalesceInPlace } from '../../../../base/common/arrays.js';
import { Iterable } from '../../../../base/common/iterator.js';
import { DisposableStore } from '../../../../base/common/lifecycle.js';
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.service.js';
import { ILogService } from '../../../../platform/log/common/log.service.js';
import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';
var HunkData_1;
class SessionWholeRange {
    static { this._options = ModelDecorationOptions.register({ description: 'inlineChat/session/wholeRange' }); }
    constructor(_textModel, wholeRange) {
        this._textModel = _textModel;
        this._onDidChange = ( new Emitter());
        this.onDidChange = this._onDidChange.event;
        this._decorationIds = [];
        this._decorationIds = _textModel.deltaDecorations([], [{ range: wholeRange, options: SessionWholeRange._options }]);
    }
    dispose() {
        this._onDidChange.dispose();
        if (!this._textModel.isDisposed()) {
            this._textModel.deltaDecorations(this._decorationIds, []);
        }
    }
    trackEdits(edits) {
        const newDeco = [];
        for (const edit of edits) {
            newDeco.push({ range: edit.range, options: SessionWholeRange._options });
        }
        this._decorationIds.push(...this._textModel.deltaDecorations([], newDeco));
        this._onDidChange.fire(this);
    }
    fixup(changes) {
        const newDeco = [];
        for (const { modified } of changes) {
            const modifiedRange = modified.isEmpty
                ? ( new Range(
                modified.startLineNumber,
                1,
                modified.startLineNumber,
                this._textModel.getLineLength(modified.startLineNumber)
            ))
                : ( new Range(
                modified.startLineNumber,
                1,
                modified.endLineNumberExclusive - 1,
                this._textModel.getLineLength(modified.endLineNumberExclusive - 1)
            ));
            newDeco.push({ range: modifiedRange, options: SessionWholeRange._options });
        }
        const [first, ...rest] = this._decorationIds;
        const newIds = this._textModel.deltaDecorations(rest, newDeco);
        this._decorationIds = [first].concat(newIds);
        this._onDidChange.fire(this);
    }
    get trackedInitialRange() {
        const [first] = this._decorationIds;
        return this._textModel.getDecorationRange(first) ?? ( new Range(1, 1, 1, 1));
    }
    get value() {
        let result;
        for (const id of this._decorationIds) {
            const range = this._textModel.getDecorationRange(id);
            if (range) {
                if (!result) {
                    result = range;
                }
                else {
                    result = Range.plusRange(result, range);
                }
            }
        }
        return result;
    }
}
class Session {
    constructor(editMode,
    targetUri,
    textModel0,
    textModelN, provider, session, wholeRange, hunkData, chatModel) {
        this.editMode = editMode;
        this.targetUri = targetUri;
        this.textModel0 = textModel0;
        this.textModelN = textModelN;
        this.provider = provider;
        this.session = session;
        this.wholeRange = wholeRange;
        this.hunkData = hunkData;
        this.chatModel = chatModel;
        this._isUnstashed = false;
        this._exchange = [];
        this._startTime = ( new Date());
        this.textModelNAltVersion = textModelN.getAlternativeVersionId();
        this._teldata = {
            extension: ExtensionIdentifier.toKey(provider.extensionId),
            startTime: this._startTime.toISOString(),
            endTime: this._startTime.toISOString(),
            edits: 0,
            finishedByEdit: false,
            rounds: '',
            undos: '',
            editMode,
            unstashed: 0,
            acceptedHunks: 0,
            discardedHunks: 0,
            responseTypes: ''
        };
    }
    addInput(input) {
        this._lastInput = input;
    }
    get lastInput() {
        return this._lastInput;
    }
    get isUnstashed() {
        return this._isUnstashed;
    }
    markUnstashed() {
        this._teldata.unstashed += 1;
        this._isUnstashed = true;
    }
    get textModelNSnapshotAltVersion() {
        return this._textModelNSnapshotAltVersion;
    }
    createSnapshot() {
        this._textModelNSnapshotAltVersion = this.textModelN.getAlternativeVersionId();
    }
    addExchange(exchange) {
        this._isUnstashed = false;
        const newLen = this._exchange.push(exchange);
        this._teldata.rounds += `${newLen}|`;
        this._teldata.responseTypes += `${exchange.response instanceof ReplyResponse ? exchange.response.responseType : "empty" }|`;
    }
    get exchanges() {
        return this._exchange;
    }
    get lastExchange() {
        return this._exchange[this._exchange.length - 1];
    }
    get hasChangedText() {
        return !this.textModel0.equalsTextBuffer(this.textModelN.getTextBuffer());
    }
    asChangedText(changes) {
        if (changes.length === 0) {
            return undefined;
        }
        let startLine = Number.MAX_VALUE;
        let endLine = Number.MIN_VALUE;
        for (const change of changes) {
            startLine = Math.min(startLine, change.modified.startLineNumber);
            endLine = Math.max(endLine, change.modified.endLineNumberExclusive);
        }
        return this.textModelN.getValueInRange(( new Range(startLine, 1, endLine, Number.MAX_VALUE)));
    }
    recordExternalEditOccurred(didFinish) {
        this._teldata.edits += 1;
        this._teldata.finishedByEdit = didFinish;
    }
    asTelemetryData() {
        for (const item of this.hunkData.getInfo()) {
            switch (item.getState()) {
                case 1 :
                    this._teldata.acceptedHunks += 1;
                    break;
                case 2 :
                    this._teldata.discardedHunks += 1;
                    break;
            }
        }
        this._teldata.endTime = ( new Date()).toISOString();
        return this._teldata;
    }
    asRecording() {
        const result = {
            session: this.session,
            when: this._startTime,
            exchanges: []
        };
        for (const exchange of this._exchange) {
            const response = exchange.response;
            if (response instanceof ReplyResponse) {
                result.exchanges.push({ prompt: exchange.prompt.value, res: response.raw });
            }
        }
        return result;
    }
}
class SessionPrompt {
    constructor(value, attempt, withIntentDetection) {
        this.value = value;
        this.attempt = attempt;
        this.withIntentDetection = withIntentDetection;
    }
}
class SessionExchange {
    constructor(prompt, response) {
        this.prompt = prompt;
        this.response = response;
    }
}
class EmptyResponse {
}
class ErrorResponse {
    constructor(error) {
        this.error = error;
        this.message = toErrorMessage(error, false);
        this.isCancellation = isCancellationError(error);
    }
}
let ReplyResponse = class ReplyResponse {
    constructor(raw, mdContent, localUri, modelAltVersionId, progressEdits, requestId, _textFileService, _languageService) {
        this.raw = raw;
        this.mdContent = mdContent;
        this.modelAltVersionId = modelAltVersionId;
        this.requestId = requestId;
        this._textFileService = _textFileService;
        this._languageService = _languageService;
        this.allLocalEdits = [];
        const editsMap = ( new ResourceMap());
        editsMap.set(localUri, [...progressEdits]);
        if (raw.type === "editorEdit" ) {
            editsMap.get(localUri).push(raw.edits);
        }
        else if (raw.type === "bulkEdit" ) {
            const edits = ResourceEdit.convert(raw.edits);
            for (const edit of edits) {
                if (edit instanceof ResourceFileEdit) {
                    if (edit.newResource && !edit.oldResource) {
                        editsMap.set(edit.newResource, []);
                        if (edit.options.contents) {
                            console.warn('CONTENT not supported');
                        }
                    }
                }
                else if (edit instanceof ResourceTextEdit) {
                    const array = editsMap.get(edit.resource);
                    if (array) {
                        array.push([edit.textEdit]);
                    }
                    else {
                        editsMap.set(edit.resource, [[edit.textEdit]]);
                    }
                }
            }
        }
        let needsWorkspaceEdit = false;
        for (const [uri, edits] of editsMap) {
            const flatEdits = edits.flat();
            if (flatEdits.length === 0) {
                editsMap.delete(uri);
                continue;
            }
            const isLocalUri = isEqual(uri, localUri);
            needsWorkspaceEdit = needsWorkspaceEdit || (uri.scheme !== Schemas.untitled && !isLocalUri);
            if (uri.scheme === Schemas.untitled && !isLocalUri && !this.untitledTextModel) {
                const langSelection = this._languageService.createByFilepathOrFirstLine(uri, undefined);
                const untitledTextModel = this._textFileService.untitled.create({
                    associatedResource: uri,
                    languageId: langSelection.languageId
                });
                this.untitledTextModel = untitledTextModel;
                untitledTextModel.resolve().then(async () => {
                    const model = untitledTextModel.textEditorModel;
                    model.applyEdits(( flatEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))));
                });
            }
        }
        this.allLocalEdits = editsMap.get(localUri) ?? [];
        if (needsWorkspaceEdit) {
            const workspaceEdits = [];
            for (const [uri, edits] of editsMap) {
                for (const edit of edits.flat()) {
                    workspaceEdits.push({ resource: uri, textEdit: edit, versionId: undefined });
                }
            }
            this.workspaceEdit = { edits: workspaceEdits };
        }
        const hasEdits = editsMap.size > 0;
        const hasMessage = mdContent.value.length > 0;
        if (hasEdits && hasMessage) {
            this.responseType = "mixed" ;
        }
        else if (hasEdits) {
            this.responseType = "onlyEdits" ;
        }
        else if (hasMessage) {
            this.responseType = "onlyMessages" ;
        }
        else {
            this.responseType = "empty" ;
        }
    }
};
ReplyResponse = ( __decorate([
    ( __param(6, ITextFileService)),
    ( __param(7, ILanguageService))
], ReplyResponse));
let StashedSession = class StashedSession {
    constructor(editor, session, _undoCancelEdits, contextKeyService, _sessionService, _logService) {
        this._undoCancelEdits = _undoCancelEdits;
        this._sessionService = _sessionService;
        this._logService = _logService;
        this._ctxHasStashedSession = CTX_INLINE_CHAT_HAS_STASHED_SESSION.bindTo(contextKeyService);
        this._session = session;
        this._ctxHasStashedSession.set(true);
        this._listener = Event.once(Event.any(editor.onDidChangeCursorSelection, editor.onDidChangeModelContent, editor.onDidChangeModel))(() => {
            this._session = undefined;
            this._sessionService.releaseSession(session);
            this._ctxHasStashedSession.reset();
        });
    }
    dispose() {
        this._listener.dispose();
        this._ctxHasStashedSession.reset();
        if (this._session) {
            this._sessionService.releaseSession(this._session);
        }
    }
    unstash() {
        if (!this._session) {
            return undefined;
        }
        this._listener.dispose();
        const result = this._session;
        result.markUnstashed();
        result.hunkData.ignoreTextModelNChanges = true;
        result.textModelN.pushEditOperations(null, this._undoCancelEdits, () => null);
        result.hunkData.ignoreTextModelNChanges = false;
        this._session = undefined;
        this._logService.debug('[IE] Unstashed session');
        return result;
    }
};
StashedSession = ( __decorate([
    ( __param(3, IContextKeyService)),
    ( __param(4, IInlineChatSessionService)),
    ( __param(5, ILogService))
], StashedSession));
let HunkData = class HunkData {
    static { HunkData_1 = this; }
    static { this._HUNK_TRACKED_RANGE = ModelDecorationOptions.register({
        description: 'inline-chat-hunk-tracked-range',
        stickiness: 0
    }); }
    static { this._HUNK_THRESHOLD = 8; }
    constructor(_editorWorkerService, _textModel0, _textModelN) {
        this._editorWorkerService = _editorWorkerService;
        this._textModel0 = _textModel0;
        this._textModelN = _textModelN;
        this._store = ( new DisposableStore());
        this._data = ( new Map());
        this._ignoreChanges = false;
        this._store.add(_textModelN.onDidChangeContent(e => {
            if (!this._ignoreChanges) {
                this._mirrorChanges(e);
            }
        }));
    }
    dispose() {
        if (!this._textModelN.isDisposed()) {
            this._textModelN.changeDecorations(accessor => {
                for (const { textModelNDecorations } of ( this._data.values())) {
                    textModelNDecorations.forEach(accessor.removeDecoration, accessor);
                }
            });
        }
        if (!this._textModel0.isDisposed()) {
            this._textModel0.changeDecorations(accessor => {
                for (const { textModel0Decorations } of ( this._data.values())) {
                    textModel0Decorations.forEach(accessor.removeDecoration, accessor);
                }
            });
        }
        this._data.clear();
        this._store.dispose();
    }
    set ignoreTextModelNChanges(value) {
        this._ignoreChanges = value;
    }
    get ignoreTextModelNChanges() {
        return this._ignoreChanges;
    }
    _mirrorChanges(event) {
        const hunkRanges = [];
        const ranges0 = [];
        for (const { textModelNDecorations, textModel0Decorations, state } of ( this._data.values())) {
            if (state === 0 ) {
                for (let i = 1; i < textModelNDecorations.length; i++) {
                    const rangeN = this._textModelN.getDecorationRange(textModelNDecorations[i]);
                    const range0 = this._textModel0.getDecorationRange(textModel0Decorations[i]);
                    if (rangeN && range0) {
                        hunkRanges.push({ rangeN, range0 });
                    }
                }
            }
            else if (state === 1 ) {
                for (let i = 1; i < textModel0Decorations.length; i++) {
                    const range = this._textModel0.getDecorationRange(textModel0Decorations[i]);
                    if (range) {
                        ranges0.push(range);
                    }
                }
            }
        }
        hunkRanges.sort((a, b) => Range.compareRangesUsingStarts(a.rangeN, b.rangeN));
        ranges0.sort(Range.compareRangesUsingStarts);
        const edits = [];
        for (const change of event.changes) {
            let isOverlapping = false;
            let pendingChangesLen = 0;
            for (const { rangeN, range0 } of hunkRanges) {
                if (rangeN.getEndPosition().isBefore(Range.getStartPosition(change.range))) {
                    pendingChangesLen += this._textModelN.getValueLengthInRange(rangeN);
                    pendingChangesLen -= this._textModel0.getValueLengthInRange(range0);
                }
                else if (Range.areIntersectingOrTouching(rangeN, change.range)) {
                    isOverlapping = true;
                    break;
                }
                else {
                    break;
                }
            }
            if (isOverlapping) {
                continue;
            }
            const offset0 = change.rangeOffset - pendingChangesLen;
            const start0 = this._textModel0.getPositionAt(offset0);
            let acceptedChangesLen = 0;
            for (const range of ranges0) {
                if (range.getEndPosition().isBefore(start0)) {
                    acceptedChangesLen += this._textModel0.getValueLengthInRange(range);
                }
            }
            const start = this._textModel0.getPositionAt(offset0 + acceptedChangesLen);
            const end = this._textModel0.getPositionAt(offset0 + acceptedChangesLen + change.rangeLength);
            edits.push(EditOperation.replace(Range.fromPositions(start, end), change.text));
        }
        this._textModel0.pushEditOperations(null, edits, () => null);
    }
    async recompute() {
        const diff = await this._editorWorkerService.computeDiff(this._textModel0.uri, this._textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, computeMoves: false }, 'advanced');
        if (!diff || diff.changes.length === 0) {
            return;
        }
        const mergedChanges = [diff.changes[0]];
        for (let i = 1; i < diff.changes.length; i++) {
            const lastChange = mergedChanges[mergedChanges.length - 1];
            const thisChange = diff.changes[i];
            if (thisChange.modified.startLineNumber - lastChange.modified.endLineNumberExclusive <= HunkData_1._HUNK_THRESHOLD) {
                mergedChanges[mergedChanges.length - 1] = ( new DetailedLineRangeMapping(
                    lastChange.original.join(thisChange.original),
                    lastChange.modified.join(thisChange.modified),
                    (lastChange.innerChanges ?? []).concat(thisChange.innerChanges ?? [])
                ));
            }
            else {
                mergedChanges.push(thisChange);
            }
        }
        const hunks = ( mergedChanges.map(change => ( new RawHunk(change.original, change.modified, change.innerChanges ?? []))));
        this._textModelN.changeDecorations(accessorN => {
            this._textModel0.changeDecorations(accessor0 => {
                for (const { textModelNDecorations, textModel0Decorations } of ( this._data.values())) {
                    textModelNDecorations.forEach(accessorN.removeDecoration, accessorN);
                    textModel0Decorations.forEach(accessor0.removeDecoration, accessor0);
                }
                this._data.clear();
                for (const hunk of hunks) {
                    const textModelNDecorations = [];
                    const textModel0Decorations = [];
                    textModelNDecorations.push(accessorN.addDecoration(asRange(hunk.modified, this._textModelN), HunkData_1._HUNK_TRACKED_RANGE));
                    textModel0Decorations.push(accessor0.addDecoration(asRange(hunk.original, this._textModel0), HunkData_1._HUNK_TRACKED_RANGE));
                    for (const change of hunk.changes) {
                        textModelNDecorations.push(accessorN.addDecoration(change.modifiedRange, HunkData_1._HUNK_TRACKED_RANGE));
                        textModel0Decorations.push(accessor0.addDecoration(change.originalRange, HunkData_1._HUNK_TRACKED_RANGE));
                    }
                    this._data.set(hunk, {
                        textModelNDecorations,
                        textModel0Decorations,
                        state: 0
                    });
                }
            });
        });
    }
    get size() {
        return this._data.size;
    }
    get pending() {
        return Iterable.reduce(( this._data.values()), (r, { state }) => r + (state === 0  ? 1 : 0), 0);
    }
    _discardEdits(item) {
        const edits = [];
        const rangesN = item.getRangesN();
        const ranges0 = item.getRanges0();
        for (let i = 1; i < rangesN.length; i++) {
            const modifiedRange = rangesN[i];
            const originalValue = this._textModel0.getValueInRange(ranges0[i]);
            edits.push(EditOperation.replace(modifiedRange, originalValue));
        }
        return edits;
    }
    discardAll() {
        const edits = [];
        for (const item of this.getInfo()) {
            if (item.getState() !== 2 ) {
                edits.push(this._discardEdits(item));
            }
        }
        const undoEdits = [];
        this._textModelN.pushEditOperations(null, edits.flat(), (_undoEdits) => {
            undoEdits.push(_undoEdits);
            return null;
        });
        return undoEdits.flat();
    }
    getInfo() {
        const result = [];
        for (const [hunk, data] of this._data.entries()) {
            const item = {
                getState: () => {
                    return data.state;
                },
                isInsertion: () => {
                    return hunk.original.isEmpty;
                },
                getRangesN: () => {
                    const ranges = ( data.textModelNDecorations.map(id => this._textModelN.getDecorationRange(id)));
                    coalesceInPlace(ranges);
                    return ranges;
                },
                getRanges0: () => {
                    const ranges = ( data.textModel0Decorations.map(id => this._textModel0.getDecorationRange(id)));
                    coalesceInPlace(ranges);
                    return ranges;
                },
                discardChanges: () => {
                    if (data.state === 0 ) {
                        const edits = this._discardEdits(item);
                        this._textModelN.pushEditOperations(null, edits, () => null);
                        data.state = 2 ;
                    }
                },
                acceptChanges: () => {
                    if (data.state === 0 ) {
                        const edits = [];
                        const rangesN = item.getRangesN();
                        const ranges0 = item.getRanges0();
                        for (let i = 1; i < ranges0.length; i++) {
                            const originalRange = ranges0[i];
                            const modifiedValue = this._textModelN.getValueInRange(rangesN[i]);
                            edits.push(EditOperation.replace(originalRange, modifiedValue));
                        }
                        this._textModel0.pushEditOperations(null, edits, () => null);
                        data.state = 1 ;
                    }
                }
            };
            result.push(item);
        }
        return result;
    }
};
HunkData = HunkData_1 = ( __decorate([
    ( __param(0, IEditorWorkerService))
], HunkData));
class RawHunk {
    constructor(original, modified, changes) {
        this.original = original;
        this.modified = modified;
        this.changes = changes;
    }
}
export { EmptyResponse, ErrorResponse, HunkData, ReplyResponse, Session, SessionExchange, SessionPrompt, SessionWholeRange, StashedSession };
