/**
 * VirtualScroll Utility Module
 */
const VirtualScroll = (options = {}) => {
        const itemHeights = new Map()
        const groupHeights = new Map()
        let groupItemCount = 1
        const avg = globalThis.Util.newAvg()
        let padTop = 0, rowGap = 0, topMargin = 0, itemMarginTop = null, itemMarginBottom = null, listInited = false

        const providedItemHeight = options.itemHeight || null;

        return {
                reset: (list, container) => {
                        listInited = false; itemHeights.clear(); groupHeights.clear(); avg.clear(); topMargin = 0; itemMarginTop = null; itemMarginBottom = null;
                        if (!list?.length) return [];
                        const size = list.length; groupItemCount = Math.ceil(Math.sqrt(size)) || 10;
                        const style = window.getComputedStyle(container);
                        padTop = parseFloat(style.paddingTop) || 0; rowGap = parseFloat(style.rowGap) || 0;
                        const visibleCount = Math.max(10, Math.ceil((container.clientHeight || 100) / (providedItemHeight || 32)));
                        return list.slice(0, Math.min(visibleCount * 3, size));
                },
                init: (list, refreshCallback) => {
                        if (listInited) return;
                        const size = list.length;
                        let defaultHeight = providedItemHeight || avg.get() || 32;
                        if (size > 0 && typeof list[0] === 'object' && list[0] !== null && list[0]._itemHeight) {
                                defaultHeight = list[0]._itemHeight;
                        }
                        avg.add(defaultHeight);
                        if (itemMarginTop === null) { itemMarginTop = 0; itemMarginBottom = 0; }
                        for (let i = 0; i < size; i++) {
                                if (!itemHeights.has(i)) {
                                        const ih = (typeof list[i] === 'object' && list[i] !== null && list[i]._itemHeight) ? list[i]._itemHeight : defaultHeight;
                                        itemHeights.set(i, ih);
                                }
                        }
                        for (let i = 0; i < size; i += groupItemCount) groupHeights.set(i, Math.min(groupItemCount, size - i) * defaultHeight);
                        listInited = true; refreshCallback();
                },
                update: (absoluteIndex, node) => {
                        if (node.offsetHeight === 0) return;
                        if (itemMarginTop === null) {
                                const style = window.getComputedStyle(node);
                                itemMarginTop = parseFloat(style.marginTop) || 0; itemMarginBottom = parseFloat(style.marginBottom) || 0;
                        }
                        if (absoluteIndex === 0 && !topMargin) topMargin = itemMarginTop;
                        const newHeight = node.offsetHeight + itemMarginTop + itemMarginBottom + rowGap;
                        const oldHeight = itemHeights.get(absoluteIndex);
                        if (newHeight !== oldHeight) {
                                itemHeights.set(absoluteIndex, newHeight); avg.add(newHeight);
                                const offset = newHeight - (oldHeight || 0), groupIndex = absoluteIndex - (absoluteIndex % groupItemCount);
                                if (groupHeights.has(groupIndex)) groupHeights.set(groupIndex, groupHeights.get(groupIndex) + offset);
                        }
                },
                calc: (container, list) => {
                        if (!listInited || !list) return null;
                        const size = list.length;
                        const avgVal = Math.max(16, avg.get() || 32);
                        let visibleCount = Math.max(10, Math.ceil((container.clientHeight || 100) / avgVal));
                        let prev = padTop + topMargin + rowGap, post = 0, status = 0, listStartIndex = 0, listEndIndex = 0;
                        let renderedList = [];
                        const scrollTop = container.scrollTop;
                        let loopCount = 0;

                        for (let i = 0; i < size; i++) {
                                if (++loopCount > size * 2) throw new Error('VirtualScroll infinite loop');
                                if (status === 0) {
                                        const gh = groupHeights.get(i);
                                        if (gh && prev + gh <= scrollTop && (i + groupItemCount < size)) {
                                                prev += gh; i += groupItemCount - 1;
                                        } else {
                                                const ih = itemHeights.get(i);
                                                if (prev + ih <= scrollTop && i < size - 1) {
                                                        prev += ih;
                                                } else {
                                                        status = 1;
                                                        let visibleStartIndex = Math.max(0, i);
                                                        listStartIndex = Math.max(0, visibleStartIndex - visibleCount);
                                                        listEndIndex = Math.min(listStartIndex + visibleCount * 3, size);
                                                        i = listEndIndex - 1;
                                                        renderedList = list.slice(listStartIndex, listEndIndex);
                                                        for (let j = listStartIndex; j < visibleStartIndex; j++) prev -= itemHeights.get(j);
                                                }
                                        }
                                } else if (status === 1) {
                                        const gh = groupHeights.get(i);
                                        if (gh) { post += gh; i += groupItemCount - 1; }
                                        else post += itemHeights.get(i);
                                }
                        }
                        const finalPrevHeight = Math.max(0, prev - padTop - topMargin - rowGap - (listStartIndex > 0 ? rowGap : 0));
                        const finalPostHeight = post > 0 ? Math.max(0, post - 2 * rowGap) : 0;
                        return { prevHeight: finalPrevHeight, postHeight: finalPostHeight, renderedList, listStartIndex };
                }
        }
}

globalThis.VirtualScroll = VirtualScroll;
export { VirtualScroll };
