import { __decorate, __param } from '../../../../../../../external/tslib/tslib.es6.js';
import { WindowIntervalTimer } from '../../../../base/browser/dom.js';
import { coalesceInPlace } from '../../../../base/common/arrays.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { DisposableStore } from '../../../../base/common/lifecycle.js';
import { themeColorFromId } from '../../../../base/common/themables.js';
import { StableEditorScrollState } from '../../../../editor/browser/stableEditorScroll.js';
import { RenderOptions, LineSource, renderLines } from '../../../../editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js';
import { LineRange } from '../../../../editor/common/core/lineRange.js';
import { Range } from '../../../../editor/common/core/range.js';
import { OverviewRulerLane, MinimapPosition, TrackedRangeStickiness } from '../../../../editor/common/model.js';
import { ModelDecorationOptions } from '../../../../editor/common/model/textModel.js';
import { IEditorWorkerService } from '../../../../editor/common/services/editorWorker.js';
import { InlineDecoration, InlineDecorationType } from '../../../../editor/common/viewModel.js';
import { localizeWithPath } from '../../../../nls.js';
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.service.js';
import { Progress } from '../../../../platform/progress/common/progress.js';
import { SaveReason } from '../../../common/editor.js';
import { countWords } from '../../chat/common/chatWordCounter.js';
import { CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, InlineChatConfigKeys, overviewRulerInlineChatDiffInserted, minimapInlineChatDiffInserted } from '../common/inlineChat.js';
import { HunkState } from './inlineChatSession.js';
import { assertType } from '../../../../base/common/types.js';
import { IModelService } from '../../../../editor/common/services/model.js';
import { asProgressiveEdit, performAsyncTextEdit } from './utils.js';
import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.service.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.service.js';
import { ITextFileService } from '../../../services/textfile/common/textfiles.service.js';
import { Schemas } from '../../../../base/common/network.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { DefaultChatTextEditor } from '../../chat/browser/codeBlockPart.js';
import { isEqual } from '../../../../base/common/resources.js';
var EditModeStrategy_1;
const _moduleId = "vs/workbench/contrib/inlineChat/browser/inlineChatStrategies";
let EditModeStrategy = class EditModeStrategy {
    static { EditModeStrategy_1 = this; }
    static { this._decoBlock = ModelDecorationOptions.register({
        description: 'inline-chat',
        showIfCollapsed: false,
        isWholeLine: true,
        className: 'inline-chat-block-selection',
    }); }
    constructor(_session, _editor, _zone, _textFileService, _instaService) {
        this._session = _session;
        this._editor = _editor;
        this._zone = _zone;
        this._textFileService = _textFileService;
        this._instaService = _instaService;
        this._store = ( (new DisposableStore()));
        this._onDidAccept = this._store.add(( (new Emitter())));
        this._onDidDiscard = this._store.add(( (new Emitter())));
        this.onDidAccept = this._onDidAccept.event;
        this.onDidDiscard = this._onDidDiscard.event;
    }
    dispose() {
        this._store.dispose();
    }
    async _doApplyChanges(ignoreLocal) {
        const untitledModels = [];
        const editor = this._instaService.createInstance(DefaultChatTextEditor);
        for (const request of this._session.chatModel.getRequests()) {
            if (!request.response?.response) {
                continue;
            }
            for (const item of request.response.response.value) {
                if (item.kind !== 'textEditGroup') {
                    continue;
                }
                if (ignoreLocal && isEqual(item.uri, this._session.textModelN.uri)) {
                    continue;
                }
                await editor.apply(request.response, item, undefined);
                if (item.uri.scheme === Schemas.untitled) {
                    const untitled = this._textFileService.untitled.get(item.uri);
                    if (untitled) {
                        untitledModels.push(untitled);
                    }
                }
            }
        }
        for (const untitledModel of untitledModels) {
            if (!untitledModel.isDisposed()) {
                await untitledModel.resolve();
                await untitledModel.save({ reason: SaveReason.EXPLICIT });
            }
        }
    }
    cancel() {
        return this._session.hunkData.discardAll();
    }
    async acceptHunk() {
        this._onDidAccept.fire();
    }
    async discardHunk() {
        this._onDidDiscard.fire();
    }
    getWholeRangeDecoration() {
        const ranges = [this._session.wholeRange.value];
        const newDecorations = ( (ranges.map(
            range => range.isEmpty() ? undefined : ({ range, options: EditModeStrategy_1._decoBlock })
        )));
        coalesceInPlace(newDecorations);
        return newDecorations;
    }
};
EditModeStrategy = EditModeStrategy_1 = ( (__decorate([
    ( (__param(3, ITextFileService))),
    ( (__param(4, IInstantiationService)))
], EditModeStrategy)));
let PreviewStrategy = class PreviewStrategy extends EditModeStrategy {
    constructor(session, editor, zone, modelService, contextKeyService, textFileService, instaService) {
        super(session, editor, zone, textFileService, instaService);
        this._ctxDocumentChanged = CTX_INLINE_CHAT_DOCUMENT_CHANGED.bindTo(contextKeyService);
        const baseModel = modelService.getModel(session.targetUri);
        Event.debounce(baseModel.onDidChangeContent.bind(baseModel), () => { }, 350)(_ => {
            if (!baseModel.isDisposed() && !session.textModel0.isDisposed()) {
                this._ctxDocumentChanged.set(session.hasChangedText);
            }
        }, undefined, this._store);
    }
    dispose() {
        this._ctxDocumentChanged.reset();
        super.dispose();
    }
    async apply() {
        await super._doApplyChanges(false);
    }
    async makeChanges() {
    }
    async makeProgressiveChanges() {
    }
    async renderChanges() { }
    hasFocus() {
        return this._zone.widget.hasFocus();
    }
};
PreviewStrategy = ( (__decorate([
    ( (__param(3, IModelService))),
    ( (__param(4, IContextKeyService))),
    ( (__param(5, ITextFileService))),
    ( (__param(6, IInstantiationService)))
], PreviewStrategy)));
let LiveStrategy = class LiveStrategy extends EditModeStrategy {
    constructor(session, editor, zone, contextKeyService, _editorWorkerService, _accessibilityService, _configService, textFileService, instaService) {
        super(session, editor, zone, textFileService, instaService);
        this._editorWorkerService = _editorWorkerService;
        this._accessibilityService = _accessibilityService;
        this._configService = _configService;
        this._decoInsertedText = ModelDecorationOptions.register({
            description: 'inline-modified-line',
            className: 'inline-chat-inserted-range-linehighlight',
            isWholeLine: true,
            overviewRuler: {
                position: OverviewRulerLane.Full,
                color: themeColorFromId(overviewRulerInlineChatDiffInserted),
            },
            minimap: {
                position: MinimapPosition.Inline,
                color: themeColorFromId(minimapInlineChatDiffInserted),
            }
        });
        this._decoInsertedTextRange = ModelDecorationOptions.register({
            description: 'inline-chat-inserted-range-linehighlight',
            className: 'inline-chat-inserted-range',
            stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
        });
        this._editCount = 0;
        this.acceptHunk = () => super.acceptHunk();
        this.discardHunk = () => super.discardHunk();
        this._hunkDisplayData = ( (new Map()));
        this._ctxCurrentChangeHasDiff = CTX_INLINE_CHAT_CHANGE_HAS_DIFF.bindTo(contextKeyService);
        this._ctxCurrentChangeShowsDiff = CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF.bindTo(contextKeyService);
        this._progressiveEditingDecorations = this._editor.createDecorationsCollection();
    }
    dispose() {
        this._resetDiff();
        super.dispose();
    }
    _resetDiff() {
        this._ctxCurrentChangeHasDiff.reset();
        this._ctxCurrentChangeShowsDiff.reset();
        this._zone.widget.updateStatus('');
        this._progressiveEditingDecorations.clear();
        for (const data of ( (this._hunkDisplayData.values()))) {
            data.remove();
        }
    }
    async apply() {
        this._resetDiff();
        if (this._editCount > 0) {
            this._editor.pushUndoStop();
        }
        await super._doApplyChanges(true);
    }
    cancel() {
        this._resetDiff();
        return super.cancel();
    }
    async makeChanges(edits, obs, undoStopBefore) {
        return this._makeChanges(edits, obs, undefined, undefined, undoStopBefore);
    }
    async makeProgressiveChanges(edits, obs, opts, undoStopBefore) {
        const progress = ( (new Progress(edits => {
            const newLines = ( (new Set()));
            for (const edit of edits) {
                LineRange.fromRange(edit.range).forEach(line => newLines.add(line));
            }
            const existingRanges = ( (this._progressiveEditingDecorations.getRanges().map(LineRange.fromRange)));
            for (const existingRange of existingRanges) {
                existingRange.forEach(line => newLines.delete(line));
            }
            const newDecorations = [];
            for (const line of newLines) {
                newDecorations.push({ range: ( (new Range(line, 1, line, Number.MAX_VALUE))), options: this._decoInsertedText });
            }
            this._progressiveEditingDecorations.append(newDecorations);
        })));
        return this._makeChanges(edits, obs, opts, progress, undoStopBefore);
    }
    async _makeChanges(edits, obs, opts, progress, undoStopBefore) {
        if (undoStopBefore) {
            this._editor.pushUndoStop();
        }
        this._editCount++;
        if (opts) {
            const durationInSec = opts.duration / 1000;
            for (const edit of edits) {
                const wordCount = countWords(edit.text ?? '');
                const speed = wordCount / durationInSec;
                const asyncEdit = asProgressiveEdit(( (new WindowIntervalTimer(this._zone.domNode))), edit, speed, opts.token);
                await performAsyncTextEdit(this._session.textModelN, asyncEdit, progress, obs);
            }
        }
        else {
            obs.start();
            this._session.textModelN.pushEditOperations(null, edits, (undoEdits) => {
                progress?.report(undoEdits);
                return null;
            });
            obs.stop();
        }
    }
    async renderChanges() {
        this._progressiveEditingDecorations.clear();
        const renderHunks = () => {
            let widgetData;
            changeDecorationsAndViewZones(this._editor, (decorationsAccessor, viewZoneAccessor) => {
                const keysNow = ( (new Set( (this._hunkDisplayData.keys()))));
                widgetData = undefined;
                for (const hunkData of this._session.hunkData.getInfo()) {
                    keysNow.delete(hunkData);
                    const hunkRanges = hunkData.getRangesN();
                    let data = this._hunkDisplayData.get(hunkData);
                    if (!data) {
                        const decorationIds = [];
                        for (let i = 0; i < hunkRanges.length; i++) {
                            decorationIds.push(decorationsAccessor.addDecoration(hunkRanges[i], i === 0
                                ? this._decoInsertedText
                                : this._decoInsertedTextRange));
                        }
                        const acceptHunk = () => {
                            hunkData.acceptChanges();
                            renderHunks();
                        };
                        const discardHunk = () => {
                            hunkData.discardChanges();
                            renderHunks();
                        };
                        const mightContainNonBasicASCII = this._session.textModel0.mightContainNonBasicASCII();
                        const mightContainRTL = this._session.textModel0.mightContainRTL();
                        const renderOptions = RenderOptions.fromEditor(this._editor);
                        const originalRange = hunkData.getRanges0()[0];
                        const source = ( (new LineSource(
                            LineRange.fromRangeInclusive(originalRange).mapToLineArray(l => this._session.textModel0.tokenization.getLineTokens(l)),
                            [],
                            mightContainNonBasicASCII,
                            mightContainRTL
                        )));
                        const domNode = document.createElement('div');
                        domNode.className = 'inline-chat-original-zone2';
                        const result = renderLines(source, renderOptions, [( (new InlineDecoration(
                             (new Range(originalRange.startLineNumber, 1, originalRange.startLineNumber, 1)),
                            '',
                            InlineDecorationType.Regular
                        )))], domNode);
                        const viewZoneData = {
                            afterLineNumber: -1,
                            heightInLines: result.heightInLines,
                            domNode,
                        };
                        const toggleDiff = () => {
                            const scrollState = StableEditorScrollState.capture(this._editor);
                            changeDecorationsAndViewZones(this._editor, (_decorationsAccessor, viewZoneAccessor) => {
                                assertType(data);
                                if (!data.viewZoneId) {
                                    const [hunkRange] = hunkData.getRangesN();
                                    viewZoneData.afterLineNumber = hunkRange.startLineNumber - 1;
                                    data.viewZoneId = viewZoneAccessor.addZone(viewZoneData);
                                }
                                else {
                                    viewZoneAccessor.removeZone(data.viewZoneId);
                                    data.viewZoneId = undefined;
                                }
                            });
                            this._ctxCurrentChangeShowsDiff.set(typeof data?.viewZoneId === 'string');
                            scrollState.restore(this._editor);
                        };
                        const remove = () => {
                            changeDecorationsAndViewZones(this._editor, (decorationsAccessor, viewZoneAccessor) => {
                                assertType(data);
                                for (const decorationId of data.decorationIds) {
                                    decorationsAccessor.removeDecoration(decorationId);
                                }
                                if (data.viewZoneId) {
                                    viewZoneAccessor.removeZone(data.viewZoneId);
                                }
                                data.decorationIds = [];
                                data.viewZoneId = undefined;
                            });
                        };
                        const move = (next) => {
                            assertType(widgetData);
                            const candidates = [];
                            for (const item of this._session.hunkData.getInfo()) {
                                if (item.getState() === HunkState.Pending) {
                                    candidates.push(item.getRangesN()[0].getStartPosition().delta(-1));
                                }
                            }
                            if (candidates.length < 2) {
                                return;
                            }
                            for (let i = 0; i < candidates.length; i++) {
                                if (candidates[i].equals(widgetData.position)) {
                                    let newPos;
                                    if (next) {
                                        newPos = candidates[(i + 1) % candidates.length];
                                    }
                                    else {
                                        newPos = candidates[(i + candidates.length - 1) % candidates.length];
                                    }
                                    this._zone.updatePositionAndHeight(newPos);
                                    renderHunks();
                                    break;
                                }
                            }
                        };
                        const zoneLineNumber = this._zone.position.lineNumber;
                        const myDistance = zoneLineNumber <= hunkRanges[0].startLineNumber
                            ? hunkRanges[0].startLineNumber - zoneLineNumber
                            : zoneLineNumber - hunkRanges[0].endLineNumber;
                        data = {
                            hunk: hunkData,
                            decorationIds,
                            viewZoneId: '',
                            viewZone: viewZoneData,
                            distance: myDistance,
                            position: hunkRanges[0].getStartPosition().delta(-1),
                            acceptHunk,
                            discardHunk,
                            toggleDiff: !hunkData.isInsertion() ? toggleDiff : undefined,
                            remove,
                            move,
                        };
                        this._hunkDisplayData.set(hunkData, data);
                    }
                    else if (hunkData.getState() !== HunkState.Pending) {
                        data.remove();
                    }
                    else {
                        const zoneLineNumber = this._zone.position.lineNumber;
                        const modifiedRangeNow = hunkRanges[0];
                        data.position = modifiedRangeNow.getStartPosition().delta(-1);
                        data.distance = zoneLineNumber <= modifiedRangeNow.startLineNumber
                            ? modifiedRangeNow.startLineNumber - zoneLineNumber
                            : zoneLineNumber - modifiedRangeNow.endLineNumber;
                    }
                    if (hunkData.getState() === HunkState.Pending && (!widgetData || data.distance < widgetData.distance)) {
                        widgetData = data;
                    }
                }
                for (const key of keysNow) {
                    const data = this._hunkDisplayData.get(key);
                    if (data) {
                        this._hunkDisplayData.delete(key);
                        data.remove();
                    }
                }
            });
            if (widgetData) {
                this._zone.updatePositionAndHeight(widgetData.position);
                const remainingHunks = this._session.hunkData.pending;
                this._updateSummaryMessage(remainingHunks, this._session.hunkData.size);
                const mode = this._configService.getValue(InlineChatConfigKeys.AccessibleDiffView);
                if (mode === 'on' || mode === 'auto' && this._accessibilityService.isScreenReaderOptimized()) {
                    this._zone.widget.showAccessibleHunk(this._session, widgetData.hunk);
                }
                this._ctxCurrentChangeHasDiff.set(Boolean(widgetData.toggleDiff));
                this.toggleDiff = widgetData.toggleDiff;
                this.acceptHunk = async () => widgetData.acceptHunk();
                this.discardHunk = async () => widgetData.discardHunk();
                this.move = next => widgetData.move(next);
            }
            else if (this._hunkDisplayData.size > 0) {
                let oneAccepted = false;
                for (const hunkData of this._session.hunkData.getInfo()) {
                    if (hunkData.getState() === HunkState.Accepted) {
                        oneAccepted = true;
                        break;
                    }
                }
                if (oneAccepted) {
                    this._onDidAccept.fire();
                }
                else {
                    this._onDidDiscard.fire();
                }
            }
            return widgetData;
        };
        return renderHunks()?.position;
    }
    _updateSummaryMessage(remaining, total) {
        const needsReview = this._configService.getValue(InlineChatConfigKeys.AcceptedOrDiscardBeforeSave);
        let message;
        if (total === 0) {
            message = ( localizeWithPath(_moduleId, 0, "Nothing changed."));
        }
        else if (remaining === 1) {
            message = needsReview
                ? ( localizeWithPath(_moduleId, 1, "$(info) Accept or Discard change"))
                : ( localizeWithPath(_moduleId, 2, "1 change"));
        }
        else {
            message = needsReview
                ? ( localizeWithPath(_moduleId, 3, "$(info) Accept or Discard {0} changes", remaining))
                : ( localizeWithPath(_moduleId, 4, "{0} changes", total));
        }
        let title;
        if (needsReview) {
            title = ( localizeWithPath(_moduleId, 5, "Review (accept or discard) all changes before continuing"));
        }
        this._zone.widget.updateStatus(message, { title });
    }
    hasFocus() {
        return this._zone.widget.hasFocus();
    }
    getWholeRangeDecoration() {
        return [];
    }
};
LiveStrategy = ( (__decorate([
    ( (__param(3, IContextKeyService))),
    ( (__param(4, IEditorWorkerService))),
    ( (__param(5, IAccessibilityService))),
    ( (__param(6, IConfigurationService))),
    ( (__param(7, ITextFileService))),
    ( (__param(8, IInstantiationService)))
], LiveStrategy)));
function changeDecorationsAndViewZones(editor, callback) {
    editor.changeDecorations(decorationsAccessor => {
        editor.changeViewZones(viewZoneAccessor => {
            callback(decorationsAccessor, viewZoneAccessor);
        });
    });
}
export { EditModeStrategy, LiveStrategy, PreviewStrategy };
