"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RecycleList = exports.RECYCLE_LIST_OVER_SCAN_COUNT = exports.RECYCLE_LIST_STABILIZATION_TIME = void 0;
const tslib_1 = require("tslib");
const classnames_1 = tslib_1.__importDefault(require("classnames"));
const react_1 = tslib_1.__importStar(require("react"));
const react_virtualized_auto_sizer_1 = tslib_1.__importDefault(require("react-virtualized-auto-sizer"));
const react_window_1 = require("react-window");
const scrollbars_1 = require("../scrollbars");
exports.RECYCLE_LIST_STABILIZATION_TIME = 500;
exports.RECYCLE_LIST_OVER_SCAN_COUNT = 50;
const RecycleList = ({ width, height, maxHeight, minHeight, className, style, data, onReady, onScroll: handleScroll, itemHeight, header: Header, footer: Footer, template: Template, paddingBottomSize, getSize: customGetSize, hiddenVerticalScrollbar, hiddenHorizontalScrollbar, }) => {
    const listRef = (0, react_1.useRef)();
    const outerRef = (0, react_1.useRef)();
    const sizeMap = (0, react_1.useRef)({});
    const prevScrollOffset = (0, react_1.useRef)(0);
    const scrollToIndexTimer = (0, react_1.useRef)();
    (0, react_1.useEffect)(() => {
        if (typeof onReady === 'function') {
            const api = {
                scrollTo: (offset) => {
                    var _a;
                    (_a = listRef.current) === null || _a === void 0 ? void 0 : _a.scrollTo(offset);
                },
                // custom alignment: center, start, or end
                scrollToIndex: (index, position = 'start') => {
                    var _a, _b;
                    let locationIndex = index;
                    if (Header) {
                        locationIndex++;
                    }
                    if (typeof itemHeight === 'number') {
                        (_a = listRef.current) === null || _a === void 0 ? void 0 : _a.scrollToItem(locationIndex, position);
                    }
                    else {
                        if (scrollToIndexTimer.current) {
                            clearTimeout(scrollToIndexTimer.current);
                        }
                        const keys = sizeMap.current ? Object.keys(sizeMap.current) : [];
                        const offset = keys.slice(0, locationIndex).reduce((p, i) => p + getSize(i), 0);
                        (_b = listRef.current) === null || _b === void 0 ? void 0 : _b.scrollToItem(index, position);
                        // 在动态列表情况下，由于渲染抖动问题，可能需要再渲染后尝试再进行一次滚动位置定位
                        scrollToIndexTimer.current = setTimeout(() => {
                            var _a;
                            const keys = sizeMap.current ? Object.keys(sizeMap.current) : [];
                            const nextOffset = keys.slice(0, locationIndex).reduce((p, i) => p + getSize(i), 0);
                            if (nextOffset !== offset) {
                                (_a = listRef.current) === null || _a === void 0 ? void 0 : _a.scrollToItem(index, position);
                            }
                            scrollToIndexTimer.current = null;
                        }, exports.RECYCLE_LIST_STABILIZATION_TIME);
                    }
                },
            };
            onReady(api);
        }
    }, []);
    const setSize = (index, size) => {
        var _a;
        if (sizeMap.current[index] !== size) {
            sizeMap.current = Object.assign(Object.assign({}, sizeMap.current), { [index]: size });
            if (listRef.current) {
                // 清理缓存数据并重新渲染
                (_a = listRef.current) === null || _a === void 0 ? void 0 : _a.resetAfterIndex(0);
            }
        }
    };
    const getSize = (0, react_1.useCallback)((index) => {
        if (customGetSize) {
            return customGetSize(Number(index));
        }
        return ((sizeMap === null || sizeMap === void 0 ? void 0 : sizeMap.current) || [])[index] || itemHeight || 100;
    }, [itemHeight, customGetSize]);
    const getMaxListHeight = (0, react_1.useCallback)(() => {
        if (maxHeight) {
            let height = 0;
            for (let i = 0; i < data.length; i++) {
                height += getSize(i);
            }
            if (height > maxHeight) {
                return maxHeight;
            }
            else {
                return height;
            }
        }
    }, [maxHeight, data]);
    const getMinListHeight = (0, react_1.useCallback)(() => {
        if (minHeight) {
            return minHeight;
        }
    }, [minHeight, data]);
    const adjustedRowCount = (0, react_1.useMemo)(() => {
        let count = data.length;
        if (Header) {
            count++;
        }
        if (Footer) {
            count++;
        }
        return count;
    }, [data, Footer, Header]);
    const renderItem = ({ index, style }) => {
        let node;
        if (index === 0) {
            if (Header) {
                return (react_1.default.createElement("div", { style: style },
                    react_1.default.createElement(Header, null)));
            }
        }
        if (index + 1 === adjustedRowCount) {
            if (Footer) {
                return (react_1.default.createElement("div", { style: style },
                    react_1.default.createElement(Footer, null)));
            }
        }
        if (Header) {
            node = data[index - 1];
        }
        else {
            node = data[index];
        }
        if (!node) {
            return react_1.default.createElement("div", { style: style });
        }
        // ref: https://developers.google.com/web/fundamentals/accessibility/semantics-aria/aria-labels-and-relationships
        const ariaInfo = {
            'aria-setsize': adjustedRowCount,
            'aria-posinset': index,
        };
        return (react_1.default.createElement("div", Object.assign({ tabIndex: 0, style: style, role: 'listitem' }, ariaInfo),
            react_1.default.createElement(Template, { data: node, index: index })));
    };
    const renderDynamicItem = ({ index, style }) => {
        const rowRoot = (0, react_1.useRef)(null);
        const observer = (0, react_1.useRef)();
        const setItemSize = () => {
            if (rowRoot.current) {
                let height = 0;
                // eslint-disable-next-line @typescript-eslint/prefer-for-of
                for (let i = 0; i < rowRoot.current.children.length; i++) {
                    height += rowRoot.current.children[i].getBoundingClientRect().height;
                }
                setSize(index, height);
            }
        };
        (0, react_1.useEffect)(() => {
            if (rowRoot.current && listRef.current) {
                observer.current = new MutationObserver(() => {
                    setItemSize();
                });
                const observerOption = {
                    childList: true,
                    attributes: true,
                    characterData: true,
                    attributeFilter: ['class', 'style'],
                    attributeOldValue: true,
                    characterDataOldValue: true, // 观察 characterData 变动，是否需要记录变动前的值
                };
                // 监听子节点属性变化
                observer.current.observe(rowRoot.current, observerOption);
                setItemSize();
            }
            return () => {
                observer.current.disconnect();
            };
        }, [rowRoot.current]);
        let node;
        if (index === 0) {
            if (Header) {
                return (react_1.default.createElement("div", { style: style },
                    react_1.default.createElement(Header, null)));
            }
        }
        if (index + 1 === adjustedRowCount) {
            if (Footer) {
                return (react_1.default.createElement("div", { style: style },
                    react_1.default.createElement(Footer, null)));
            }
        }
        if (Header) {
            node = data[index - 1];
        }
        else {
            node = data[index];
        }
        if (!node) {
            return react_1.default.createElement("div", { style: style });
        }
        return (react_1.default.createElement("div", { style: style, ref: rowRoot },
            react_1.default.createElement(Template, { data: node, index: index })));
    };
    // 通过计算平均行高来提高准确性
    // 修复滚动条行为，见: https://github.com/bvaughn/react-window/issues/408
    const calcEstimatedSize = (0, react_1.useMemo)(() => {
        const estimatedHeight = data.reduce((p, i) => p + getSize(i), 0);
        return estimatedHeight / data.length;
    }, [data]);
    const onScroll = (0, react_1.useCallback)((props) => {
        // 当 width/height 改变时，FixedSizeList 会重置 scrollTop
        // 这里存储一下上次的滚动条高度后用于下次渲染时进行同步
        prevScrollOffset.current = props.scrollOffset;
        handleScroll && handleScroll(props);
    }, [handleScroll]);
    // 为 List 添加下边距
    const innerElementType = (0, react_1.useMemo)(() => (0, react_1.forwardRef)((props, ref) => {
        const _a = props, { style } = _a, rest = tslib_1.__rest(_a, ["style"]);
        return (react_1.default.createElement("div", Object.assign({ ref: ref, style: Object.assign(Object.assign({}, style), { height: `${parseFloat(style.height) + (paddingBottomSize ? paddingBottomSize : 0)}px` }) }, rest)));
    }), [paddingBottomSize]);
    const outerElementType = (0, react_1.useMemo)(() => (0, react_1.forwardRef)((props, ref) => (react_1.default.createElement(scrollbars_1.Scrollbars, Object.assign({ hiddenVertical: hiddenVerticalScrollbar, hiddenHorizontal: hiddenHorizontalScrollbar }, props, { thumbSize: 10, forwardedRef: ref })))), [hiddenVerticalScrollbar, hiddenHorizontalScrollbar]);
    const render = () => {
        const isDynamicList = typeof itemHeight !== 'number';
        const isAutoSizeList = !width || !height;
        const renderList = () => {
            const renderContent = ({ width, height }) => {
                if (outerRef.current) {
                    // 渲染时同步一下上次记录的滚动条高度
                    outerRef.current.scrollTop = prevScrollOffset.current;
                }
                const maxH = getMaxListHeight();
                const minH = getMinListHeight();
                let currentHeight = height;
                if (minH) {
                    currentHeight = Math.min(height, minH);
                }
                else if (maxH) {
                    currentHeight = maxH;
                }
                if (isDynamicList) {
                    return (react_1.default.createElement(react_window_1.VariableSizeList, { onScroll: onScroll, width: width, height: currentHeight, 
                        // 这里的数据不是必要的，主要用于在每次更新列表
                        itemData: [], itemSize: getSize, itemCount: adjustedRowCount, overscanCount: exports.RECYCLE_LIST_OVER_SCAN_COUNT, ref: listRef, style: Object.assign({ transform: 'translate3d(0px, 0px, 0px)' }, style), className: (0, classnames_1.default)(className, 'kt-recycle-list'), innerElementType: innerElementType, outerElementType: outerElementType, estimatedItemSize: calcEstimatedSize }, renderDynamicItem));
                }
                else {
                    return (react_1.default.createElement(react_window_1.FixedSizeList, { onScroll: onScroll, width: width, height: currentHeight, 
                        // 这里的数据不是必要的，主要用于在每次更新列表
                        itemData: [], itemSize: itemHeight, itemCount: adjustedRowCount, overscanCount: exports.RECYCLE_LIST_OVER_SCAN_COUNT, ref: listRef, outerRef: outerRef, style: Object.assign({ transform: 'translate3d(0px, 0px, 0px)' }, style), className: (0, classnames_1.default)(className, 'recycle-list'), innerElementType: innerElementType, outerElementType: outerElementType }, renderItem));
                }
            };
            if (!isAutoSizeList) {
                return renderContent({ width, height });
            }
            else {
                return react_1.default.createElement(react_virtualized_auto_sizer_1.default, null, renderContent);
            }
        };
        return renderList();
    };
    return render();
};
exports.RecycleList = RecycleList;
//# sourceMappingURL=RecycleList.js.map