
import { __decorate, __param } from '../../../../../../../../../external/tslib/tslib.es6.js';
import { equalsIfDefined, itemEquals } from '../../../../../../base/common/equals.js';
import { Event } from '../../../../../../base/common/event.js';
import { Disposable } from '../../../../../../base/common/lifecycle.js';
import '../../../../../../base/common/observableInternal/index.js';
import { localize } from '../../../../../../nls.js';
import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js';
import { observableCodeEditor } from '../../../../../browser/observableCodeEditor.js';
import { EditorOption } from '../../../../../common/config/editorOptions.js';
import { LineRange } from '../../../../../common/core/lineRange.js';
import { Range } from '../../../../../common/core/range.js';
import { StringText, SingleTextEdit } from '../../../../../common/core/textEdit.js';
import { TextLength } from '../../../../../common/core/textLength.js';
import { RangeMapping, lineRangeMappingFromRangeMappings } from '../../../../../common/diff/rangeMapping.js';
import { TextModel } from '../../../../../common/model/textModel.js';
import { InlineEditsGutterIndicator } from './components/gutterIndicatorView.js';
import { InlineEditsIndicator } from './components/indicatorView.js';
import { InlineEditsDeletionView } from './inlineEditsViews/inlineEditsDeletionView.js';
import { InlineEditsInsertionView } from './inlineEditsViews/inlineEditsInsertionView.js';
import { InlineEditsLineReplacementView } from './inlineEditsViews/inlineEditsLineReplacementView.js';
import { InlineEditsSideBySideView } from './inlineEditsViews/inlineEditsSideBySideView.js';
import { InlineEditsWordReplacementView } from './inlineEditsViews/inlineEditsWordReplacementView.js';
import { OriginalEditorInlineDiffView } from './inlineEditsViews/originalEditorInlineDiffView.js';
import { createReindentEdit, applyEditToModifiedRangeMappings, InlineEditTabAction } from './utils/utils.js';
import './view.css.js';
import { derivedObservableWithCache, mapObservableArrayCached } from '../../../../../../base/common/observableInternal/utils.js';
import { derived, derivedWithStore, derivedOpts } from '../../../../../../base/common/observableInternal/derived.js';
import { observableValue } from '../../../../../../base/common/observableInternal/base.js';
import { autorunWithStore } from '../../../../../../base/common/observableInternal/autorun.js';

let InlineEditsView = class InlineEditsView extends Disposable {
    constructor(_editor, _edit, _model, _focusIsInMenu, _instantiationService) {
        super();
        this._editor = _editor;
        this._edit = _edit;
        this._model = _model;
        this._focusIsInMenu = _focusIsInMenu;
        this._instantiationService = _instantiationService;
        this._editorObs = observableCodeEditor(this._editor);
        this._useMixedLinesDiff = ( this._editorObs.getOption(EditorOption.inlineSuggest).map(s => s.edits.useMixedLinesDiff));
        this._useInterleavedLinesDiff = ( this._editorObs.getOption(EditorOption.inlineSuggest).map(s => s.edits.useInterleavedLinesDiff));
        this._useCodeShifting = ( this._editorObs.getOption(EditorOption.inlineSuggest).map(s => s.edits.codeShifting));
        this._renderSideBySide = ( this._editorObs.getOption(EditorOption.inlineSuggest).map(s => s.edits.renderSideBySide));
        this._showCollapsed = ( this._editorObs.getOption(EditorOption.inlineSuggest).map(s => s.edits.showCollapsed));
        this._useMultiLineGhostText = ( this._editorObs.getOption(EditorOption.inlineSuggest).map(s => s.edits.useMultiLineGhostText));
        this._uiState = derived(this, reader => {
            const edit = this._edit.read(reader);
            if (!edit) {
                return undefined;
            }
            this._model.get()?.handleInlineEditShown(edit.inlineCompletion);
            let mappings = RangeMapping.fromEdit(edit.edit);
            let newText = edit.edit.apply(edit.originalText);
            let diff = lineRangeMappingFromRangeMappings(mappings, edit.originalText, ( new StringText(newText)));
            const originalDisplayRange = edit.originalText.lineRange.intersect(edit.originalLineRange.join(LineRange.ofLength(edit.originalLineRange.startLineNumber, edit.lineEdit.newLines.length)));
            let state = this.determineRenderState(edit, reader, diff, ( new StringText(newText)), originalDisplayRange);
            if (!state) {
                this._model.get()?.stop();
                return undefined;
            }
            if (state.kind === 'sideBySide') {
                const indentationAdjustmentEdit = createReindentEdit(newText, edit.modifiedLineRange);
                newText = indentationAdjustmentEdit.applyToString(newText);
                mappings = applyEditToModifiedRangeMappings(mappings, indentationAdjustmentEdit);
                diff = lineRangeMappingFromRangeMappings(mappings, edit.originalText, ( new StringText(newText)));
            }
            this._previewTextModel.setLanguage(this._editor.getModel().getLanguageId());
            const previousNewText = this._previewTextModel.getValue();
            if (previousNewText !== newText) {
                this._previewTextModel.setValue(newText);
            }
            if (this._showCollapsed.read(reader) && this._host.tabAction.read(reader) !== InlineEditTabAction.Accept && !this._indicator.read(reader)?.isHoverVisible.read(reader) && !this._model.get().inAcceptFlow.read(reader)) {
                state = { kind: 'hidden' };
            }
            return {
                state,
                diff,
                edit,
                newText,
                newTextLineCount: edit.modifiedLineRange.length,
                originalDisplayRange: originalDisplayRange,
            };
        });
        this._previewTextModel = this._register(this._instantiationService.createInstance(TextModel, '', this._editor.getModel().getLanguageId(), { ...TextModel.DEFAULT_CREATION_OPTIONS, bracketPairColorizationOptions: { enabled: true, independentColorPoolPerBracketType: false } }, null));
        this._host = {
            displayName: derivedObservableWithCache(this, (reader, previousDisplayName) => {
                const state = this._model.read(reader)?.inlineEditState;
                const item = state?.read(reader);
                const completionSource = item?.inlineCompletion?.source;
                return completionSource?.inlineCompletions?.edits?.[0]?.provider?.displayName ?? previousDisplayName
                    ?? completionSource?.provider.displayName ?? ( localize(1249, "Inline Edit"));
            }),
            tabAction: derived(this, reader => {
                const m = this._model.read(reader);
                if (this._editorObs.isFocused.read(reader)) {
                    if (m && m.tabShouldJumpToInlineEdit.read(reader)) {
                        return InlineEditTabAction.Jump;
                    }
                    if (m && m.tabShouldAcceptInlineEdit.read(reader)) {
                        return InlineEditTabAction.Accept;
                    }
                    if (m && m.inlineCompletionState.read(reader)?.inlineCompletion?.sourceInlineCompletion.showInlineEditMenu) {
                        return InlineEditTabAction.Accept;
                    }
                }
                return InlineEditTabAction.Inactive;
            }),
            action: ( this._model.map((m, r) => m?.state.read(r)?.inlineCompletion?.inlineCompletion.action)),
            extensionCommands: ( this._model.map(
                (m, r) => m?.state.read(r)?.inlineCompletion?.source.inlineCompletions.commands ?? []
            )),
            accept: () => {
                this._model.get()?.accept();
            },
            jump: () => {
                this._model.get()?.jump();
            }
        };
        this._useGutterIndicator = ( observableCodeEditor(this._editor).getOption(EditorOption.inlineSuggest).map(s => s.edits.useGutterIndicator));
        this._indicatorCyclicDependencyCircuitBreaker = observableValue(this, false);
        this._indicator = derivedWithStore(this, (reader, store) => {
            if (!this._indicatorCyclicDependencyCircuitBreaker.read(reader)) {
                return undefined;
            }
            const indicatorDisplayRange = derivedOpts({ owner: this, equalsFn: equalsIfDefined(itemEquals()) }, reader => {
                const s = this._model.read(reader)?.inlineCompletionState.read(reader);
                if (s && s.inlineCompletion?.sourceInlineCompletion.showInlineEditMenu) {
                    return LineRange.ofLength(s.primaryGhostText.lineNumber, 1);
                }
                const state = this._uiState.read(reader);
                if (state?.state?.kind === 'insertionMultiLine') {
                    return this._insertion.originalLines.read(reader);
                }
                return state?.originalDisplayRange;
            });
            if (this._useGutterIndicator.read(reader)) {
                return store.add(this._instantiationService.createInstance(InlineEditsGutterIndicator, this._editorObs, indicatorDisplayRange, this._gutterIndicatorOffset, this._host, this._inlineEditsIsHovered, this._focusIsInMenu));
            }
            else {
                return store.add(( new InlineEditsIndicator(this._editorObs, derived(reader => {
                    const state = this._uiState.read(reader);
                    const range = indicatorDisplayRange.read(reader);
                    if (!state || !state.state || !range) {
                        return undefined;
                    }
                    const top = this._editor.getTopForLineNumber(range.startLineNumber) - this._editorObs.scrollTop.read(reader) + this._gutterIndicatorOffset.read(reader);
                    return { editTop: top, showAlways: state.state.kind !== 'sideBySide' };
                }), this._model)));
            }
        });
        this._inlineEditsIsHovered = derived(this, reader => {
            return this._sideBySide.isHovered.read(reader)
                || ( this._wordReplacementViews.read(reader).some(v => v.isHovered.read(reader)))
                || this._deletion.isHovered.read(reader)
                || this._inlineDiffView.isHovered.read(reader)
                || this._lineReplacementView.isHovered.read(reader)
                || this._insertion.isHovered.read(reader);
        });
        this._gutterIndicatorOffset = derived(this, reader => {
            if (this._uiState.read(reader)?.state?.kind === 'insertionMultiLine') {
                return this._insertion.startLineOffset.read(reader);
            }
            return 0;
        });
        this._sideBySide = this._register(this._instantiationService.createInstance(InlineEditsSideBySideView, this._editor, this._edit, this._previewTextModel, ( this._uiState.map(s => s && s.state?.kind === 'sideBySide' ? ({
            edit: s.edit,
            newTextLineCount: s.newTextLineCount,
            originalDisplayRange: s.originalDisplayRange,
        }) : undefined)), this._host));
        this._deletion = this._register(this._instantiationService.createInstance(InlineEditsDeletionView, this._editor, this._edit, ( this._uiState.map(s => s && s.state?.kind === 'deletion' ? ({
            originalRange: s.state.originalRange,
            deletions: s.state.deletions,
        }) : undefined)), this._host));
        this._insertion = this._register(this._instantiationService.createInstance(InlineEditsInsertionView, this._editor, ( this._uiState.map(s => s && s.state?.kind === 'insertionMultiLine' ? ({
            lineNumber: s.state.lineNumber,
            startColumn: s.state.column,
            text: s.state.text,
        }) : undefined)), this._host));
        this._inlineDiffViewState = derived(this, reader => {
            const e = this._uiState.read(reader);
            if (!e || !e.state) {
                return undefined;
            }
            if (e.state.kind === 'wordReplacements' || e.state.kind === 'lineReplacement' || e.state.kind === 'insertionMultiLine' || e.state.kind === 'hidden') {
                return undefined;
            }
            return {
                modifiedText: ( new StringText(e.newText)),
                diff: e.diff,
                mode: e.state.kind,
                modifiedCodeEditor: this._sideBySide.previewEditor,
            };
        });
        this._inlineDiffView = this._register(( new OriginalEditorInlineDiffView(this._editor, this._inlineDiffViewState, this._previewTextModel)));
        this._wordReplacementViews = mapObservableArrayCached(this, ( this._uiState.map(s => s?.state?.kind === 'wordReplacements' ? s.state.replacements : [])), (e, store) => {
            return store.add(this._instantiationService.createInstance(InlineEditsWordReplacementView, this._editorObs, e, [e], this._host));
        });
        this._lineReplacementView = this._register(this._instantiationService.createInstance(InlineEditsLineReplacementView, this._editorObs, ( this._uiState.map(s => s?.state?.kind === 'lineReplacement' ? ({
            originalRange: s.state.originalRange,
            modifiedRange: s.state.modifiedRange,
            modifiedLines: s.state.modifiedLines,
            replacements: s.state.replacements,
        }) : undefined)), this._host));
        this._register(autorunWithStore((reader, store) => {
            store.add(Event.any(this._sideBySide.onDidClick, this._deletion.onDidClick, this._lineReplacementView.onDidClick, this._insertion.onDidClick, ...( this._wordReplacementViews.read(reader).map(w => w.onDidClick)), this._inlineDiffView.onDidClick)(e => {
                e.preventDefault();
                this._host.accept();
            }));
        }));
        this._indicator.recomputeInitiallyAndOnChange(this._store);
        this._wordReplacementViews.recomputeInitiallyAndOnChange(this._store);
        this._indicatorCyclicDependencyCircuitBreaker.set(true, undefined);
    }
    getCacheId(edit) {
        if (this._model.get()?.inAcceptPartialFlow.get()) {
            return `${edit.inlineCompletion.id}_${( edit.edit.edits.map(edit => ( edit.range.toString()) + edit.text)).join(',')}`;
        }
        return edit.inlineCompletion.id;
    }
    determineView(edit, reader, diff, newText, originalDisplayRange) {
        const canUseCache = this._previousView?.id === this.getCacheId(edit);
        const reconsiderViewAfterJump = edit.userJumpedToIt !== this._previousView?.userJumpedToIt &&
            ((this._useMixedLinesDiff.read(reader) === 'afterJumpWhenPossible' && this._previousView?.view !== 'mixedLines') ||
                (this._useInterleavedLinesDiff.read(reader) === 'afterJump' && this._previousView?.view !== 'interleavedLines'));
        const reconsiderViewEditorWidthChange = this._previousView?.editorWidth !== this._editorObs.layoutInfoWidth.read(reader) &&
            (this._previousView?.view === 'sideBySide' ||
                this._previousView?.view === 'lineReplacement');
        if (canUseCache && !reconsiderViewAfterJump && !reconsiderViewEditorWidthChange) {
            return this._previousView.view;
        }
        const inner = diff.flatMap(d => d.innerChanges ?? []);
        const isSingleInnerEdit = inner.length === 1;
        if (isSingleInnerEdit && (this._useMixedLinesDiff.read(reader) === 'forStableInsertions'
            && this._useCodeShifting.read(reader)
            && isSingleLineInsertionAfterPosition(diff, edit.cursorPosition))) {
            return 'insertionInline';
        }
        const innerValues = ( inner.map(
            m => ({ original: edit.originalText.getValueOfRange(m.originalRange), modified: newText.getValueOfRange(m.modifiedRange) })
        ));
        if (innerValues.every(({ original, modified }) => modified.trim() === '' && original.length > 0 && (original.length > modified.length || original.trim() !== ''))) {
            return 'deletion';
        }
        if (isSingleMultiLineInsertion(diff) && this._useMultiLineGhostText.read(reader) && this._useCodeShifting.read(reader)) {
            return 'insertionMultiLine';
        }
        const numOriginalLines = edit.originalLineRange.length;
        const numModifiedLines = edit.modifiedLineRange.length;
        const allInnerChangesNotTooLong = inner.every(m => TextLength.ofRange(m.originalRange).columnCount < InlineEditsWordReplacementView.MAX_LENGTH && TextLength.ofRange(m.modifiedRange).columnCount < InlineEditsWordReplacementView.MAX_LENGTH);
        if (allInnerChangesNotTooLong && isSingleInnerEdit && numOriginalLines === 1 && numModifiedLines === 1) {
            if (!( inner.some(m => m.originalRange.isEmpty())) ||
                !( growEditsUntilWhitespace(( inner.map(m => ( new SingleTextEdit(m.originalRange, '')))), edit.originalText).some(
                    e => e.range.isEmpty() && TextLength.ofRange(e.range).columnCount < InlineEditsWordReplacementView.MAX_LENGTH
                ))) {
                return 'wordReplacements';
            }
        }
        if (numOriginalLines > 0 && numModifiedLines > 0) {
            if (this._renderSideBySide.read(reader) !== 'never' && InlineEditsSideBySideView.fitsInsideViewport(this._editor, edit, originalDisplayRange, reader)) {
                return 'sideBySide';
            }
            return 'lineReplacement';
        }
        if ((this._useMixedLinesDiff.read(reader) === 'whenPossible' || (edit.userJumpedToIt && this._useMixedLinesDiff.read(reader) === 'afterJumpWhenPossible'))
            && diff.every(m => OriginalEditorInlineDiffView.supportsInlineDiffRendering(m))) {
            return 'mixedLines';
        }
        if (this._useInterleavedLinesDiff.read(reader) === 'always' || (edit.userJumpedToIt && this._useInterleavedLinesDiff.read(reader) === 'afterJump')) {
            return 'interleavedLines';
        }
        return 'sideBySide';
    }
    determineRenderState(edit, reader, diff, newText, originalDisplayRange) {
        const view = this.determineView(edit, reader, diff, newText, originalDisplayRange);
        this._previousView = { id: this.getCacheId(edit), view, userJumpedToIt: edit.userJumpedToIt, editorWidth: this._editor.getLayoutInfo().width };
        switch (view) {
            case 'insertionInline': return { kind: 'insertionInline' };
            case 'mixedLines': return { kind: 'mixedLines' };
            case 'interleavedLines': return { kind: 'interleavedLines' };
            case 'sideBySide': return { kind: 'sideBySide' };
            case 'hidden': return { kind: 'hidden' };
        }
        const inner = diff.flatMap(d => d.innerChanges ?? []);
        if (view === 'deletion') {
            return {
                kind: 'deletion',
                originalRange: edit.originalLineRange,
                deletions: ( inner.map(m => m.originalRange)),
            };
        }
        if (view === 'insertionMultiLine') {
            const change = inner[0];
            return {
                kind: 'insertionMultiLine',
                lineNumber: change.originalRange.startLineNumber,
                column: change.originalRange.startColumn,
                text: newText.getValueOfRange(change.modifiedRange),
            };
        }
        const replacements = ( inner.map(m => ( new SingleTextEdit(m.originalRange, newText.getValueOfRange(m.modifiedRange)))));
        if (replacements.length === 0) {
            return undefined;
        }
        if (view === 'wordReplacements') {
            let grownEdits = growEditsToEntireWord(replacements, edit.originalText);
            if (( grownEdits.some(e => e.range.isEmpty()))) {
                grownEdits = growEditsUntilWhitespace(replacements, edit.originalText);
            }
            return {
                kind: 'wordReplacements',
                replacements: grownEdits,
            };
        }
        if (view === 'lineReplacement') {
            return {
                kind: 'lineReplacement',
                originalRange: edit.originalLineRange,
                modifiedRange: edit.modifiedLineRange,
                modifiedLines: edit.modifiedLineRange.mapToLineArray(line => newText.getLineAt(line)),
                replacements: ( inner.map(m => ({ originalRange: m.originalRange, modifiedRange: m.modifiedRange }))),
            };
        }
        return undefined;
    }
};
InlineEditsView = ( __decorate([
    ( __param(4, IInstantiationService))
], InlineEditsView));
function isSingleLineInsertionAfterPosition(diff, position) {
    if (!position) {
        return false;
    }
    const pos = position;
    return diff.every(m => m.innerChanges.every(r => isStableWordInsertion(r)));
    function isStableWordInsertion(r) {
        if (!r.originalRange.isEmpty()) {
            return false;
        }
        const isInsertionWithinLine = r.modifiedRange.startLineNumber === r.modifiedRange.endLineNumber;
        if (!isInsertionWithinLine) {
            return false;
        }
        const insertPosition = r.originalRange.getStartPosition();
        if (pos.isBeforeOrEqual(insertPosition)) {
            return true;
        }
        if (insertPosition.lineNumber < pos.lineNumber) {
            return true;
        }
        return false;
    }
}
function isSingleMultiLineInsertion(diff) {
    const inner = diff.flatMap(d => d.innerChanges ?? []);
    if (inner.length !== 1) {
        return false;
    }
    const change = inner[0];
    if (!change.originalRange.isEmpty()) {
        return false;
    }
    if (change.modifiedRange.startLineNumber === change.modifiedRange.endLineNumber) {
        return false;
    }
    return true;
}
function growEditsToEntireWord(replacements, originalText) {
    return _growEdits(replacements, originalText, (char) => /^[a-zA-Z]$/.test(char));
}
function growEditsUntilWhitespace(replacements, originalText) {
    return _growEdits(replacements, originalText, (char) => !(/^\s$/.test(char)));
}
function _growEdits(replacements, originalText, fn) {
    const result = [];
    replacements.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range));
    for (const edit of replacements) {
        let startIndex = edit.range.startColumn - 1;
        let endIndex = edit.range.endColumn - 2;
        let prefix = '';
        let suffix = '';
        const startLineContent = originalText.getLineAt(edit.range.startLineNumber);
        const endLineContent = originalText.getLineAt(edit.range.endLineNumber);
        if (isIncluded(startLineContent[startIndex])) {
            while (isIncluded(startLineContent[startIndex - 1])) {
                prefix = startLineContent[startIndex - 1] + prefix;
                startIndex--;
            }
        }
        if (isIncluded(endLineContent[endIndex]) || endIndex < startIndex) {
            while (isIncluded(endLineContent[endIndex + 1])) {
                suffix += endLineContent[endIndex + 1];
                endIndex++;
            }
        }
        let newEdit = ( new SingleTextEdit(( new Range(
            edit.range.startLineNumber,
            startIndex + 1,
            edit.range.endLineNumber,
            endIndex + 2
        )), prefix + edit.text + suffix));
        if (result.length > 0 && Range.areIntersectingOrTouching(result[result.length - 1].range, newEdit.range)) {
            newEdit = SingleTextEdit.joinEdits([result.pop(), newEdit], originalText);
        }
        result.push(newEdit);
    }
    function isIncluded(c) {
        if (c === undefined) {
            return false;
        }
        return fn(c);
    }
    return result;
}

export { InlineEditsView };
