/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
import { getWindow, h, scheduleAtNextAnimationFrame } from '../../../../base/browser/dom.js';
import { SmoothScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js';
import { findFirstMaxBy } from '../../../../base/common/arraysFind.js';
import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';
import { autorun, derived, derivedObservableWithCache, derivedWithStore, observableFromEvent, observableValue } from '../../../../base/common/observable.js';
import { disposableObservableValue, globalTransaction, transaction } from '../../../../base/common/observableInternal/base.js';
import { Scrollable } from '../../../../base/common/scrollable.js';
import './style.css';
import { ObservableElementSizeObserver } from '../diffEditor/utils.js';
import { OffsetRange } from '../../../common/core/offsetRange.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { DiffEditorItemTemplate, TemplateData } from './diffEditorItemTemplate.js';
import { ObjectPool } from './objectPool.js';
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js';
import { EditorContextKeys } from '../../../common/editorContextKeys.js';
let MultiDiffEditorWidgetImpl = class MultiDiffEditorWidgetImpl extends Disposable {
    constructor(_element, _dimension, _viewModel, _workbenchUIElementFactory, _parentContextKeyService, _parentInstantiationService) {
        super();
        this._element = _element;
        this._dimension = _dimension;
        this._viewModel = _viewModel;
        this._workbenchUIElementFactory = _workbenchUIElementFactory;
        this._parentContextKeyService = _parentContextKeyService;
        this._parentInstantiationService = _parentInstantiationService;
        this._elements = h('div', {
            style: {
                overflowY: 'hidden',
            }
        }, [
            h('div@content', {
                style: {
                    overflow: 'hidden',
                }
            }),
            h('div.monaco-editor@overflowWidgetsDomNode', {}),
        ]);
        this._sizeObserver = this._register(new ObservableElementSizeObserver(this._element, undefined));
        this._objectPool = this._register(new ObjectPool((data) => {
            const template = this._instantiationService.createInstance(DiffEditorItemTemplate, this._elements.content, this._elements.overflowWidgetsDomNode, this._workbenchUIElementFactory);
            template.setData(data);
            return template;
        }));
        this._scrollable = this._register(new Scrollable({
            forceIntegerValues: false,
            scheduleAtNextAnimationFrame: (cb) => scheduleAtNextAnimationFrame(getWindow(this._element), cb),
            smoothScrollDuration: 100,
        }));
        this._scrollableElement = this._register(new SmoothScrollableElement(this._elements.root, {
            vertical: 1 /* ScrollbarVisibility.Auto */,
            horizontal: 1 /* ScrollbarVisibility.Auto */,
            className: 'monaco-component',
            useShadows: false,
        }, this._scrollable));
        this.scrollTop = observableFromEvent(this._scrollableElement.onScroll, () => /** @description scrollTop */ this._scrollableElement.getScrollPosition().scrollTop);
        this.scrollLeft = observableFromEvent(this._scrollableElement.onScroll, () => /** @description scrollLeft */ this._scrollableElement.getScrollPosition().scrollLeft);
        this._viewItems = derivedWithStore(this, (reader, store) => {
            const vm = this._viewModel.read(reader);
            if (!vm) {
                return [];
            }
            const items = vm.items.read(reader);
            return items.map(d => store.add(new VirtualizedViewItem(d, this._objectPool, this.scrollLeft)));
        });
        this._totalHeight = this._viewItems.map(this, (items, reader) => items.reduce((r, i) => r + i.contentHeight.read(reader), 0));
        this.activeDiffItem = derived(this, reader => this._viewItems.read(reader).find(i => { var _a; return (_a = i.template.read(reader)) === null || _a === void 0 ? void 0 : _a.isFocused.read(reader); }));
        this.lastActiveDiffItem = derivedObservableWithCache((reader, lastValue) => { var _a; return (_a = this.activeDiffItem.read(reader)) !== null && _a !== void 0 ? _a : lastValue; });
        this._contextKeyService = this._register(this._parentContextKeyService.createScoped(this._element));
        this._instantiationService = this._parentInstantiationService.createChild(new ServiceCollection([IContextKeyService, this._contextKeyService]));
        this._contextKeyService.createKey(EditorContextKeys.inMultiDiffEditor.key, true);
        const ctxAllCollapsed = this._parentContextKeyService.createKey(EditorContextKeys.multiDiffEditorAllCollapsed.key, false);
        this._register(autorun((reader) => {
            const viewModel = this._viewModel.read(reader);
            if (viewModel) {
                const allCollapsed = viewModel.items.read(reader).every(item => item.collapsed.read(reader));
                ctxAllCollapsed.set(allCollapsed);
            }
        }));
        this._register(autorun((reader) => {
            const lastActiveDiffItem = this.lastActiveDiffItem.read(reader);
            transaction(tx => {
                var _a;
                (_a = this._viewModel.read(reader)) === null || _a === void 0 ? void 0 : _a.activeDiffItem.set(lastActiveDiffItem === null || lastActiveDiffItem === void 0 ? void 0 : lastActiveDiffItem.viewModel, tx);
            });
        }));
        this._register(autorun((reader) => {
            /** @description Update widget dimension */
            const dimension = this._dimension.read(reader);
            this._sizeObserver.observe(dimension);
        }));
        this._elements.content.style.position = 'relative';
        this._register(autorun((reader) => {
            /** @description Update scroll dimensions */
            const height = this._sizeObserver.height.read(reader);
            this._elements.root.style.height = `${height}px`;
            const totalHeight = this._totalHeight.read(reader);
            this._elements.content.style.height = `${totalHeight}px`;
            const width = this._sizeObserver.width.read(reader);
            let scrollWidth = width;
            const viewItems = this._viewItems.read(reader);
            const max = findFirstMaxBy(viewItems, i => i.maxScroll.read(reader).maxScroll);
            if (max) {
                const maxScroll = max.maxScroll.read(reader);
                scrollWidth = width + maxScroll.maxScroll;
            }
            this._scrollableElement.setScrollDimensions({
                width: width,
                height: height,
                scrollHeight: totalHeight,
                scrollWidth,
            });
        }));
        _element.replaceChildren(this._scrollableElement.getDomNode());
        this._register(toDisposable(() => {
            _element.replaceChildren();
        }));
        this._register(this._register(autorun(reader => {
            /** @description Render all */
            globalTransaction(tx => {
                this.render(reader);
            });
        })));
    }
    render(reader) {
        const scrollTop = this.scrollTop.read(reader);
        let contentScrollOffsetToScrollOffset = 0;
        let itemHeightSumBefore = 0;
        let itemContentHeightSumBefore = 0;
        const viewPortHeight = this._sizeObserver.height.read(reader);
        const contentViewPort = OffsetRange.ofStartAndLength(scrollTop, viewPortHeight);
        const width = this._sizeObserver.width.read(reader);
        for (const v of this._viewItems.read(reader)) {
            const itemContentHeight = v.contentHeight.read(reader);
            const itemHeight = Math.min(itemContentHeight, viewPortHeight);
            const itemRange = OffsetRange.ofStartAndLength(itemHeightSumBefore, itemHeight);
            const itemContentRange = OffsetRange.ofStartAndLength(itemContentHeightSumBefore, itemContentHeight);
            if (itemContentRange.isBefore(contentViewPort)) {
                contentScrollOffsetToScrollOffset -= itemContentHeight - itemHeight;
                v.hide();
            }
            else if (itemContentRange.isAfter(contentViewPort)) {
                v.hide();
            }
            else {
                const scroll = Math.max(0, Math.min(contentViewPort.start - itemContentRange.start, itemContentHeight - itemHeight));
                contentScrollOffsetToScrollOffset -= scroll;
                const viewPort = OffsetRange.ofStartAndLength(scrollTop + contentScrollOffsetToScrollOffset, viewPortHeight);
                v.render(itemRange, scroll, width, viewPort);
            }
            itemHeightSumBefore += itemHeight;
            itemContentHeightSumBefore += itemContentHeight;
        }
        this._elements.content.style.transform = `translateY(${-(scrollTop + contentScrollOffsetToScrollOffset)}px)`;
    }
};
MultiDiffEditorWidgetImpl = __decorate([
    __param(4, IContextKeyService),
    __param(5, IInstantiationService)
], MultiDiffEditorWidgetImpl);
export { MultiDiffEditorWidgetImpl };
class VirtualizedViewItem extends Disposable {
    constructor(viewModel, _objectPool, _scrollLeft) {
        super();
        this.viewModel = viewModel;
        this._objectPool = _objectPool;
        this._scrollLeft = _scrollLeft;
        // TODO this should be in the view model
        this._lastTemplateData = observableValue(this, { contentHeight: 500, maxScroll: { maxScroll: 0, width: 0 }, });
        this._templateRef = this._register(disposableObservableValue(this, undefined));
        this.contentHeight = derived(this, reader => { var _a, _b, _c; return (_c = (_b = (_a = this._templateRef.read(reader)) === null || _a === void 0 ? void 0 : _a.object.height) === null || _b === void 0 ? void 0 : _b.read(reader)) !== null && _c !== void 0 ? _c : this._lastTemplateData.read(reader).contentHeight; });
        this.maxScroll = derived(this, reader => { var _a, _b; return (_b = (_a = this._templateRef.read(reader)) === null || _a === void 0 ? void 0 : _a.object.maxScroll.read(reader)) !== null && _b !== void 0 ? _b : this._lastTemplateData.read(reader).maxScroll; });
        this.template = derived(this, reader => { var _a; return (_a = this._templateRef.read(reader)) === null || _a === void 0 ? void 0 : _a.object; });
        this._isHidden = observableValue(this, false);
        this._register(autorun((reader) => {
            var _a;
            const scrollLeft = this._scrollLeft.read(reader);
            (_a = this._templateRef.read(reader)) === null || _a === void 0 ? void 0 : _a.object.setScrollLeft(scrollLeft);
        }));
        this._register(autorun(reader => {
            const ref = this._templateRef.read(reader);
            if (!ref) {
                return;
            }
            const isHidden = this._isHidden.read(reader);
            if (!isHidden) {
                return;
            }
            const isFocused = ref.object.isFocused.read(reader);
            if (isFocused) {
                return;
            }
            transaction(tx => {
                this._lastTemplateData.set({
                    contentHeight: ref.object.height.get(),
                    maxScroll: { maxScroll: 0, width: 0, } // Reset max scroll
                }, tx);
                ref.object.hide();
                this._templateRef.set(undefined, tx);
            });
        }));
    }
    dispose() {
        this.hide();
        super.dispose();
    }
    toString() {
        return `VirtualViewItem(${this.viewModel.entry.value.title})`;
    }
    hide() {
        this._isHidden.set(true, undefined);
    }
    render(verticalSpace, offset, width, viewPort) {
        this._isHidden.set(false, undefined);
        let ref = this._templateRef.get();
        if (!ref) {
            ref = this._objectPool.getUnusedObj(new TemplateData(this.viewModel));
            this._templateRef.set(ref, undefined);
        }
        ref.object.render(verticalSpace, width, offset, viewPort);
    }
}
