export class VirtualScroller {
    constructor() {
        this._viewport = null;
        this._resizeObserver = null;
        this._options = null;
        this._handleScroll = () => {
            this._compute(this._viewport.clientHeight);
        };
    }
    /** Attach to a scrollable viewport element and begin observing */
    attach(viewport, options) {
        this._viewport = viewport;
        this._options = options;
        this._resizeObserver = new ResizeObserver(entries => {
            for (const entry of entries) {
                this._compute(entry.contentRect.height);
            }
        });
        this._resizeObserver.observe(viewport);
        viewport.addEventListener('scroll', this._handleScroll);
    }
    /** Tear down observers and event listeners */
    detach() {
        this._resizeObserver?.disconnect();
        this._resizeObserver = null;
        if (this._viewport) {
            this._viewport.removeEventListener('scroll', this._handleScroll);
        }
        this._viewport = null;
        this._options = null;
    }
    /** Call after item count or row height changes (e.g. after sort/filter/page change) */
    invalidate() {
        if (!this._viewport)
            return;
        this._compute(this._viewport.clientHeight);
    }
    _compute(viewportHeight) {
        if (!this._viewport || !this._options)
            return;
        const { rowHeight, itemCount, onUpdate, enabled, buffer = 3 } = this._options;
        const count = itemCount();
        if (enabled && !enabled()) {
            onUpdate(0, count);
            return;
        }
        const scrollTop = this._viewport.scrollTop;
        const rowH = rowHeight();
        const newStart = Math.max(0, Math.floor(scrollTop / rowH) - buffer);
        const newEnd = Math.min(count, Math.ceil((scrollTop + viewportHeight) / rowH) + buffer);
        onUpdate(newStart, newEnd);
    }
}
