
import { h, reset, createElement } from '../../../../../base/browser/dom.js';
import { Disposable, DisposableMap, toDisposable } from '../../../../../base/common/lifecycle.js';
import '../../../../../base/common/observableInternal/index.js';
import { LineRange } from '../../../../common/core/ranges/lineRange.js';
import { OffsetRange } from '../../../../common/core/ranges/offsetRange.js';
import { observableFromEvent } from '../../../../../base/common/observableInternal/observables/observableFromEvent.js';
import { observableSignalFromEvent } from '../../../../../base/common/observableInternal/observables/observableSignalFromEvent.js';
import { observableSignal } from '../../../../../base/common/observableInternal/observables/observableSignal.js';
import { autorun } from '../../../../../base/common/observableInternal/reactions/autorun.js';
import { transaction } from '../../../../../base/common/observableInternal/transaction.js';
import { observableValue } from '../../../../../base/common/observableInternal/observables/observableValue.js';

class EditorGutter extends Disposable {
    constructor(_editor, _domNode, itemProvider) {
        super();
        this._editor = _editor;
        this._domNode = _domNode;
        this.itemProvider = itemProvider;
        this.scrollTop = observableFromEvent(
            this,
            this._editor.onDidScrollChange,
            e =>  this._editor.getScrollTop()
        );
        this.isScrollTopZero = ( this.scrollTop.map(scrollTop =>  scrollTop === 0));
        this.modelAttached = observableFromEvent(
            this,
            this._editor.onDidChangeModel,
            e =>  this._editor.hasModel()
        );
        this.editorOnDidChangeViewZones = observableSignalFromEvent("onDidChangeViewZones", this._editor.onDidChangeViewZones);
        this.editorOnDidContentSizeChange = observableSignalFromEvent("onDidContentSizeChange", this._editor.onDidContentSizeChange);
        this.domNodeSizeChanged = observableSignal("domNodeSizeChanged");
        this.views = this._register(( new DisposableMap()));
        this._domNode.className = "gutter monaco-editor";
        const scrollDecoration = this._domNode.appendChild(h("div.scroll-decoration", {
            role: "presentation",
            ariaHidden: "true",
            style: {
                width: "100%"
            }
        }).root);
        const o = ( new ResizeObserver(() => {
            transaction(tx => {
                this.domNodeSizeChanged.trigger(tx);
            });
        }));
        o.observe(this._domNode);
        this._register(toDisposable(() => o.disconnect()));
        this._register(autorun(reader => {
            scrollDecoration.className = this.isScrollTopZero.read(reader) ? "" : "scroll-decoration";
        }));
        this._register(
            autorun(reader =>  this.render(reader))
        );
    }
    dispose() {
        super.dispose();
        reset(this._domNode);
    }
    render(reader) {
        if (!this.modelAttached.read(reader)) {
            return;
        }
        this.domNodeSizeChanged.read(reader);
        this.editorOnDidChangeViewZones.read(reader);
        this.editorOnDidContentSizeChange.read(reader);
        const scrollTop = this.scrollTop.read(reader);
        const visibleRanges = this._editor.getVisibleRanges();
        const unusedIds = ( new Set(( this.views.keys())));
        const viewRange = OffsetRange.ofStartAndLength(0, this._domNode.clientHeight);
        if (!viewRange.isEmpty) {
            for (const visibleRange of visibleRanges) {
                const visibleRange2 = ( new LineRange(visibleRange.startLineNumber, visibleRange.endLineNumber + 1));
                const gutterItems = this.itemProvider.getIntersectingGutterItems(visibleRange2, reader);
                transaction(tx => {
                    for (const gutterItem of gutterItems) {
                        if (!gutterItem.range.intersect(visibleRange2)) {
                            continue;
                        }
                        unusedIds.delete(gutterItem.id);
                        let view = this.views.get(gutterItem.id);
                        if (!view) {
                            const viewDomNode = createElement("div");
                            this._domNode.appendChild(viewDomNode);
                            const gutterItemObs = observableValue("item", gutterItem);
                            const itemView = this.itemProvider.createView(gutterItemObs, viewDomNode);
                            view = ( new ManagedGutterItemView(gutterItemObs, itemView, viewDomNode));
                            this.views.set(gutterItem.id, view);
                        } else {
                            view.item.set(gutterItem, tx);
                        }
                        const top = gutterItem.range.startLineNumber <= this._editor.getModel().getLineCount() ? this._editor.getTopForLineNumber(gutterItem.range.startLineNumber, true) - scrollTop : gutterItem.range.startLineNumber > 1 ? this._editor.getBottomForLineNumber(gutterItem.range.startLineNumber - 1, false) - scrollTop : 0;
                        const bottom = gutterItem.range.endLineNumberExclusive === 1 ? Math.max(
                            top,
                            this._editor.getTopForLineNumber(gutterItem.range.startLineNumber, false) - scrollTop
                        ) : Math.max(
                            top,
                            this._editor.getBottomForLineNumber(gutterItem.range.endLineNumberExclusive - 1, true) - scrollTop
                        );
                        const height = bottom - top;
                        view.domNode.style.top = `${top}px`;
                        view.domNode.style.height = `${height}px`;
                        view.gutterItemView.layout(OffsetRange.ofStartAndLength(top, height), viewRange);
                    }
                });
            }
        }
        for (const id of unusedIds) {
            this.views.deleteAndDispose(id);
        }
    }
}
class ManagedGutterItemView {
    constructor(item, gutterItemView, domNode) {
        this.item = item;
        this.gutterItemView = gutterItemView;
        this.domNode = domNode;
    }
    dispose() {
        this.gutterItemView.dispose();
        this.domNode.remove();
    }
}

export { EditorGutter };
