/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
import { ArrayQueue } from '../../../../base/common/arrays.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { observableSignalFromEvent, derived } from '../../../../base/common/observable.js';
import { autorunWithStore2 } from '../../../../base/common/observableImpl/autorun.js';
import { joinCombine } from './utils.js';
import { LineRange } from '../../../common/core/lineRange.js';
import { Position } from '../../../common/core/position.js';
export class ViewZoneAlignment extends Disposable {
    constructor(_originalEditor, _modifiedEditor, _diffModel) {
        super();
        this._originalEditor = _originalEditor;
        this._modifiedEditor = _modifiedEditor;
        this._diffModel = _diffModel;
        let isChangingViewZones = false;
        const origViewZonesChanged = observableSignalFromEvent('origViewZonesChanged', e => this._originalEditor.onDidChangeViewZones((args) => { if (!isChangingViewZones) {
            e(args);
        } }));
        const modViewZonesChanged = observableSignalFromEvent('modViewZonesChanged', e => this._modifiedEditor.onDidChangeViewZones((args) => { if (!isChangingViewZones) {
            e(args);
        } }));
        const alignmentViewZoneIdsOrig = new Set();
        const alignmentViewZoneIdsMod = new Set();
        const alignments = derived('alignments', (reader) => {
            var _a;
            const diff = (_a = this._diffModel.read(reader)) === null || _a === void 0 ? void 0 : _a.diff.read(reader);
            if (!diff) {
                return null;
            }
            origViewZonesChanged.read(reader);
            modViewZonesChanged.read(reader);
            return computeRangeAlignment(this._originalEditor, this._modifiedEditor, diff.changes, alignmentViewZoneIdsOrig, alignmentViewZoneIdsMod);
        });
        function createFakeLinesDiv() {
            const r = document.createElement('div');
            r.className = 'diagonal-fill';
            return r;
        }
        const alignmentViewZones = derived('alignment viewzones', (reader) => {
            const alignments_ = alignments.read(reader);
            const origViewZones = [];
            const modViewZones = [];
            if (alignments_) {
                for (const a of alignments_) {
                    const delta = a.modifiedHeightInPx - a.originalHeightInPx;
                    if (delta > 0) {
                        origViewZones.push({
                            afterLineNumber: a.originalRange.endLineNumberExclusive - 1,
                            domNode: createFakeLinesDiv(),
                            heightInPx: delta,
                        });
                    }
                    else {
                        modViewZones.push({
                            afterLineNumber: a.modifiedRange.endLineNumberExclusive - 1,
                            domNode: createFakeLinesDiv(),
                            heightInPx: -delta,
                        });
                    }
                }
            }
            return { orig: origViewZones, mod: modViewZones };
        });
        this._register(autorunWithStore2('alignment viewzones', (reader) => {
            const alignmentViewZones_ = alignmentViewZones.read(reader);
            isChangingViewZones = true;
            this._originalEditor.changeViewZones((aOrig) => {
                for (const id of alignmentViewZoneIdsOrig) {
                    aOrig.removeZone(id);
                }
                alignmentViewZoneIdsOrig.clear();
                for (const z of alignmentViewZones_.orig) {
                    alignmentViewZoneIdsOrig.add(aOrig.addZone(z));
                }
            });
            this._modifiedEditor.changeViewZones(aMod => {
                for (const id of alignmentViewZoneIdsMod) {
                    aMod.removeZone(id);
                }
                alignmentViewZoneIdsMod.clear();
                for (const z of alignmentViewZones_.mod) {
                    alignmentViewZoneIdsMod.add(aMod.addZone(z));
                }
            });
            isChangingViewZones = false;
        }));
    }
}
function getAdditionalLineHeights(editor, viewZonesToIgnore) {
    const viewZoneHeights = [];
    const wrappingZoneHeights = [];
    const hasWrapping = editor.getOption(141 /* EditorOption.wrappingInfo */).wrappingColumn !== -1;
    const coordinatesConverter = editor._getViewModel().coordinatesConverter;
    if (hasWrapping) {
        for (let i = 1; i <= editor.getModel().getLineCount(); i++) {
            const lineCount = coordinatesConverter.getModelLineViewLineCount(i);
            if (lineCount > 1) {
                wrappingZoneHeights.push({ lineNumber: i, heightInPx: lineCount - 1 });
            }
        }
    }
    for (const w of editor.getWhitespaces()) {
        if (viewZonesToIgnore.has(w.id)) {
            continue;
        }
        const modelLineNumber = coordinatesConverter.convertViewPositionToModelPosition(new Position(w.afterLineNumber, 1)).lineNumber;
        viewZoneHeights.push({ lineNumber: modelLineNumber, heightInPx: w.height });
    }
    const result = joinCombine(viewZoneHeights, wrappingZoneHeights, v => v.lineNumber, (v1, v2) => ({ lineNumber: v1.lineNumber, heightInPx: v1.heightInPx + v2.heightInPx }));
    return result;
}
function computeRangeAlignment(originalEditor, modifiedEditor, diffs, originalEditorAlignmentViewZones, modifiedEditorAlignmentViewZones) {
    var _a, _b, _c, _d;
    const originalLineHeightOverrides = new ArrayQueue(getAdditionalLineHeights(originalEditor, originalEditorAlignmentViewZones));
    const modifiedLineHeightOverrides = new ArrayQueue(getAdditionalLineHeights(modifiedEditor, modifiedEditorAlignmentViewZones));
    const origLineHeight = originalEditor.getOption(64 /* EditorOption.lineHeight */);
    const modLineHeight = modifiedEditor.getOption(64 /* EditorOption.lineHeight */);
    const result = [];
    let lastOriginalLineNumber = 0;
    let lastModifiedLineNumber = 0;
    function handleAlignmentsOutsideOfDiffs(untilOriginalLineNumberExclusive, untilModifiedLineNumberExclusive) {
        while (true) {
            let origNext = originalLineHeightOverrides.peek();
            let modNext = modifiedLineHeightOverrides.peek();
            if (origNext && origNext.lineNumber >= untilOriginalLineNumberExclusive) {
                origNext = undefined;
            }
            if (modNext && modNext.lineNumber >= untilModifiedLineNumberExclusive) {
                modNext = undefined;
            }
            if (!origNext && !modNext) {
                break;
            }
            const distOrig = origNext ? origNext.lineNumber - lastOriginalLineNumber : Number.MAX_VALUE;
            const distNext = modNext ? modNext.lineNumber - lastModifiedLineNumber : Number.MAX_VALUE;
            if (distOrig < distNext) {
                originalLineHeightOverrides.dequeue();
                modNext = {
                    lineNumber: origNext.lineNumber - lastOriginalLineNumber + lastModifiedLineNumber,
                    heightInPx: 0,
                };
            }
            else if (distOrig > distNext) {
                modifiedLineHeightOverrides.dequeue();
                origNext = {
                    lineNumber: modNext.lineNumber - lastModifiedLineNumber + lastOriginalLineNumber,
                    heightInPx: 0,
                };
            }
            else {
                originalLineHeightOverrides.dequeue();
                modifiedLineHeightOverrides.dequeue();
            }
            result.push({
                originalRange: LineRange.ofLength(origNext.lineNumber, 1),
                modifiedRange: LineRange.ofLength(modNext.lineNumber, 1),
                originalHeightInPx: origLineHeight + origNext.heightInPx,
                modifiedHeightInPx: modLineHeight + modNext.heightInPx,
            });
        }
    }
    for (const c of diffs) {
        handleAlignmentsOutsideOfDiffs(c.originalRange.startLineNumber, c.modifiedRange.startLineNumber);
        const originalAdditionalHeight = (_b = (_a = originalLineHeightOverrides
            .takeWhile(v => v.lineNumber < c.originalRange.endLineNumberExclusive)) === null || _a === void 0 ? void 0 : _a.reduce((p, c) => p + c.heightInPx, 0)) !== null && _b !== void 0 ? _b : 0;
        const modifiedAdditionalHeight = (_d = (_c = modifiedLineHeightOverrides
            .takeWhile(v => v.lineNumber < c.modifiedRange.endLineNumberExclusive)) === null || _c === void 0 ? void 0 : _c.reduce((p, c) => p + c.heightInPx, 0)) !== null && _d !== void 0 ? _d : 0;
        result.push({
            originalRange: c.originalRange,
            modifiedRange: c.modifiedRange,
            originalHeightInPx: c.originalRange.length * origLineHeight + originalAdditionalHeight,
            modifiedHeightInPx: c.modifiedRange.length * modLineHeight + modifiedAdditionalHeight,
        });
        lastOriginalLineNumber = c.originalRange.endLineNumberExclusive;
        lastModifiedLineNumber = c.modifiedRange.endLineNumberExclusive;
    }
    handleAlignmentsOutsideOfDiffs(Number.MAX_VALUE, Number.MAX_VALUE);
    return result;
}
