/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
import * as assert from '../../../base/common/assert.js';
import { Emitter } from '../../../base/common/event.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import * as objects from '../../../base/common/objects.js';
import { Range } from '../../common/core/range.js';
const defaultOptions = {
    followsCaret: true,
    ignoreCharChanges: true,
    alwaysRevealFirst: true
};
/**
 * Create a new diff navigator for the provided diff editor.
 */
export class DiffNavigator extends Disposable {
    constructor(editor, options = {}) {
        super();
        this._onDidUpdate = this._register(new Emitter());
        this._editor = editor;
        this._options = objects.mixin(options, defaultOptions, false);
        this.disposed = false;
        this.nextIdx = -1;
        this.ranges = [];
        this.ignoreSelectionChange = false;
        this.revealFirst = Boolean(this._options.alwaysRevealFirst);
        // hook up to diff editor for diff, disposal, and caret move
        this._register(this._editor.onDidDispose(() => this.dispose()));
        this._register(this._editor.onDidUpdateDiff(() => this._onDiffUpdated()));
        if (this._options.followsCaret) {
            this._register(this._editor.getModifiedEditor().onDidChangeCursorPosition((e) => {
                if (this.ignoreSelectionChange) {
                    return;
                }
                this.nextIdx = -1;
            }));
        }
        if (this._options.alwaysRevealFirst) {
            this._register(this._editor.getModifiedEditor().onDidChangeModel((e) => {
                this.revealFirst = true;
            }));
        }
        // init things
        this._init();
    }
    _init() {
        const changes = this._editor.getLineChanges();
        if (!changes) {
            return;
        }
    }
    _onDiffUpdated() {
        this._init();
        this._compute(this._editor.getLineChanges());
        if (this.revealFirst) {
            // Only reveal first on first non-null changes
            if (this._editor.getLineChanges() !== null) {
                this.revealFirst = false;
                this.nextIdx = -1;
                this.next(1 /* Immediate */);
            }
        }
    }
    _compute(lineChanges) {
        // new ranges
        this.ranges = [];
        if (lineChanges) {
            // create ranges from changes
            lineChanges.forEach((lineChange) => {
                if (!this._options.ignoreCharChanges && lineChange.charChanges) {
                    lineChange.charChanges.forEach((charChange) => {
                        this.ranges.push({
                            rhs: true,
                            range: new Range(charChange.modifiedStartLineNumber, charChange.modifiedStartColumn, charChange.modifiedEndLineNumber, charChange.modifiedEndColumn)
                        });
                    });
                }
                else {
                    if (lineChange.modifiedEndLineNumber === 0) {
                        // a deletion
                        this.ranges.push({
                            rhs: true,
                            range: new Range(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedStartLineNumber + 1, 1)
                        });
                    }
                    else {
                        // an insertion or modification
                        this.ranges.push({
                            rhs: true,
                            range: new Range(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber + 1, 1)
                        });
                    }
                }
            });
        }
        // sort
        this.ranges.sort((left, right) => Range.compareRangesUsingStarts(left.range, right.range));
        this._onDidUpdate.fire(this);
    }
    _initIdx(fwd) {
        let found = false;
        const position = this._editor.getPosition();
        if (!position) {
            this.nextIdx = 0;
            return;
        }
        for (let i = 0, len = this.ranges.length; i < len && !found; i++) {
            const range = this.ranges[i].range;
            if (position.isBeforeOrEqual(range.getStartPosition())) {
                this.nextIdx = i + (fwd ? 0 : -1);
                found = true;
            }
        }
        if (!found) {
            // after the last change
            this.nextIdx = fwd ? 0 : this.ranges.length - 1;
        }
        if (this.nextIdx < 0) {
            this.nextIdx = this.ranges.length - 1;
        }
    }
    _move(fwd, scrollType) {
        assert.ok(!this.disposed, 'Illegal State - diff navigator has been disposed');
        if (!this.canNavigate()) {
            return;
        }
        if (this.nextIdx === -1) {
            this._initIdx(fwd);
        }
        else if (fwd) {
            this.nextIdx += 1;
            if (this.nextIdx >= this.ranges.length) {
                this.nextIdx = 0;
            }
        }
        else {
            this.nextIdx -= 1;
            if (this.nextIdx < 0) {
                this.nextIdx = this.ranges.length - 1;
            }
        }
        const info = this.ranges[this.nextIdx];
        this.ignoreSelectionChange = true;
        try {
            const pos = info.range.getStartPosition();
            this._editor.setPosition(pos);
            this._editor.revealRangeInCenter(info.range, scrollType);
        }
        finally {
            this.ignoreSelectionChange = false;
        }
    }
    canNavigate() {
        return this.ranges && this.ranges.length > 0;
    }
    next(scrollType = 0 /* Smooth */) {
        this._move(true, scrollType);
    }
    previous(scrollType = 0 /* Smooth */) {
        this._move(false, scrollType);
    }
    dispose() {
        super.dispose();
        this.ranges = [];
        this.disposed = true;
    }
}
