import { useElementSize, useMergedRef } from '@mantine/hooks';
import clsx from 'clsx';
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
import { AnimatePresence } from 'motion/react';
import { useOverlayScrollbars } from 'overlayscrollbars-react';
import {
    CSSProperties,
    Ref,
    UIEvent,
    useCallback,
    useEffect,
    useImperativeHandle,
    useLayoutEffect,
    useMemo,
    useRef,
    useState,
} from 'react';
import { List, RowComponentProps, useListRef } from 'react-window-v2';

import { ExpandedListContainer } from '../expanded-list-container';
import styles from './item-grid-list.module.css';

import { getDataRowsCount, ItemCard } from '/@/renderer/components/item-card/item-card';
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
import { itemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
import {
    ItemListStateActions,
    useItemListState,
} from '/@/renderer/components/item-list/helpers/item-list-state';
import { ItemListHandle } from '/@/renderer/components/item-list/types';
import { LibraryItem } from '/@/shared/types/domain-types';

export interface GridItemProps {
    columns: number;
    data: any[];
    enableExpansion?: boolean;
    enableSelection?: boolean;
    gap: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
    internalState: ItemListStateActions;
    itemType: LibraryItem;
}

export interface ItemGridListProps {
    data: unknown[];
    enableExpansion?: boolean;
    enableSelection?: boolean;
    gap?: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
    initialTop?: {
        behavior?: 'auto' | 'smooth';
        to: number;
        type: 'index' | 'offset';
    };
    itemsPerRow?: number;
    itemType: LibraryItem;
    onEndReached?: (index: number, handle: ItemListHandle) => void;
    onRangeChanged?: (range: { endIndex: number; startIndex: number }) => void;
    onScroll?: (e: UIEvent<HTMLDivElement>) => void;
    onScrollEnd?: (offset: number, handle: ItemListHandle) => void;
    onStartReached?: (index: number, handle: ItemListHandle) => void;
    ref?: Ref<ItemListHandle>;
}

export const ItemGridList = ({
    data,
    enableExpansion = true,
    enableSelection = true,
    gap = 'sm',
    initialTop,
    itemsPerRow,
    itemType,
    onEndReached,
    onRangeChanged,
    onScroll,
    onScrollEnd,
    onStartReached,
    ref,
}: ItemGridListProps) => {
    const itemGridRef = useListRef(null);
    const scrollContainerRef = useRef<HTMLDivElement | null>(null);
    const { ref: containerRef, width: containerWidth } = useElementSize();
    const mergedContainerRef = useMergedRef(containerRef, scrollContainerRef);

    const internalState = useItemListState();

    const [initialize] = useOverlayScrollbars({
        defer: true,
        events: {
            initialized(osInstance) {
                const { viewport } = osInstance.elements();
                viewport.style.overflowX = `var(--os-viewport-overflow-x)`;
                viewport.style.overflowY = `var(--os-viewport-overflow-y)`;
            },
        },
        options: {
            overflow: { x: 'hidden', y: 'scroll' },
            paddingAbsolute: true,
            scrollbars: {
                autoHide: 'leave',
                autoHideDelay: 500,
                pointers: ['mouse', 'pen', 'touch'],
                theme: 'feishin-os-scrollbar',
            },
        },
    });

    useEffect(() => {
        const { current: root } = scrollContainerRef;

        if (root) {
            initialize({
                elements: { viewport: root.firstElementChild as HTMLElement },
                target: root,
            });
        }
    }, [itemGridRef, initialize]);

    const isInitialScrollPositionSet = useRef<boolean>(false);

    const hasExpanded = internalState.hasExpanded();

    const handleOnScrollEnd = useCallback(
        (scrollTop: number, handle: ItemListHandle) => {
            onScrollEnd?.(scrollTop, handle);
        },
        [onScrollEnd],
    );

    const debouncedOnScrollEnd = debounce(handleOnScrollEnd, 150);

    const handleScroll = useCallback(
        (e: UIEvent<HTMLDivElement>) => {
            onScroll?.(e);
            debouncedOnScrollEnd(
                e.currentTarget.scrollTop,
                itemGridRef.current ?? (undefined as any),
            );
        },
        [onScroll, debouncedOnScrollEnd, itemGridRef],
    );

    const scrollToGridOffset = useCallback((offset: number) => {
        const scrollContainer = scrollContainerRef.current?.firstElementChild as
            | HTMLElement
            | undefined;

        if (scrollContainer) {
            scrollContainer.scrollTo({ behavior: 'instant', top: offset });
        }
    }, []);

    const [tableMeta, setTableMeta] = useState<null | {
        columnCount: number;
        itemHeight: number;
        rowCount: number;
    }>(null);

    // Throttled function to update table meta
    const throttledSetTableMeta = useMemo(() => {
        return throttle((width: number, dataLength: number, type: LibraryItem) => {
            const isSm = width >= 600;
            const isMd = width >= 768;
            const isLg = width >= 960;
            const isXl = width >= 1200;
            const is2xl = width >= 1440;
            const is3xl = width >= 1920;
            const is4xl = width >= 2560;

            let dynamicItemsPerRow = 2;

            if (is4xl) {
                dynamicItemsPerRow = 12;
            } else if (is3xl) {
                dynamicItemsPerRow = 10;
            } else if (is2xl) {
                dynamicItemsPerRow = 8;
            } else if (isXl) {
                dynamicItemsPerRow = 6;
            } else if (isLg) {
                dynamicItemsPerRow = 5;
            } else if (isMd) {
                dynamicItemsPerRow = 4;
            } else if (isSm) {
                dynamicItemsPerRow = 3;
            } else {
                dynamicItemsPerRow = 2;
            }

            const setItemsPerRow = itemsPerRow || dynamicItemsPerRow;

            const widthPerItem = Number(width) / setItemsPerRow;
            const itemHeight = widthPerItem + getDataRowsCount(type) * 26;

            if (widthPerItem === 0) {
                return;
            }

            setTableMeta({
                columnCount: setItemsPerRow,
                itemHeight,
                rowCount: Math.ceil(dataLength / setItemsPerRow),
            });
        }, 200);
    }, [itemsPerRow]);

    useLayoutEffect(() => {
        throttledSetTableMeta(containerWidth, data.length, itemType);
    }, [containerWidth, data.length, itemType, throttledSetTableMeta]);

    const handleOnRowsRendered = useCallback(
        (visibleRows: { startIndex: number; stopIndex: number }) => {
            onRangeChanged?.({
                endIndex: visibleRows.stopIndex * (tableMeta?.columnCount || 0),
                startIndex: visibleRows.startIndex * (tableMeta?.columnCount || 0),
            });

            if (onStartReached || onEndReached) {
                const totalRows = Math.ceil(data.length / (tableMeta?.columnCount || 0));
                const startRow = visibleRows.startIndex;
                const endRow = visibleRows.stopIndex;

                if (startRow === 0) {
                    onStartReached?.(startRow, itemGridRef.current ?? (undefined as any));
                }
                if (endRow >= totalRows) {
                    onEndReached?.(endRow, itemGridRef.current ?? (undefined as any));
                }
            }
        },
        [
            onRangeChanged,
            tableMeta?.columnCount,
            onStartReached,
            onEndReached,
            data.length,
            itemGridRef,
        ],
    );

    const elements = useMemo(() => {
        if (!tableMeta) {
            return [];
        }

        return data
            .map((d, i) => {
                return {
                    data: d,
                    index: i,
                };
            })
            .reduce(
                (acc, d) => {
                    if (d.index % (tableMeta?.columnCount || 0) === 0) {
                        acc.push([]);
                    }
                    const prev = acc[acc.length - 1];
                    prev.push(d);
                    return acc;
                },
                [] as { data: any; index: number }[][],
            );
    }, [tableMeta, data]);

    const itemProps: GridItemProps = {
        columns: tableMeta?.columnCount || 0,
        data: elements,
        enableExpansion,
        enableSelection,
        gap,
        internalState,
        itemType,
    };

    useEffect(() => {
        if (!initialTop || isInitialScrollPositionSet.current || !tableMeta?.itemHeight) return;
        isInitialScrollPositionSet.current = true;

        if (initialTop.type === 'offset') {
            scrollToGridOffset(initialTop.to);
        } else {
            itemGridRef.current?.scrollToRow({
                behavior: initialTop.behavior,
                index: initialTop.to,
            });
        }
    }, [initialTop, itemGridRef, scrollToGridOffset, tableMeta?.itemHeight]);

    const imperativeHandle: ItemListHandle = useMemo(() => {
        return {
            clearExpanded: () => {
                internalState.clearExpanded();
            },
            clearSelected: () => {
                internalState.clearSelected();
            },
            getItem: (index: number) => data[index],
            getItemCount: () => data.length,
            getItems: () => data,
            internalState,
            scrollToIndex: (index: number) => {
                itemGridRef.current?.scrollToRow({
                    align: 'smart',
                    behavior: 'auto',
                    index: Math.floor(index / (tableMeta?.columnCount || 1)),
                });
            },
            scrollToOffset: (offset: number) => {
                scrollToGridOffset(offset);
            },
        };
    }, [data, internalState, scrollToGridOffset, tableMeta?.columnCount, itemGridRef]);

    useImperativeHandle(ref, () => imperativeHandle);

    return (
        <div
            className={styles.itemGridContainer}
            data-overlayscrollbars-initialize=""
            ref={mergedContainerRef}
        >
            <List
                listRef={itemGridRef}
                onRowsRendered={handleOnRowsRendered}
                onScroll={handleScroll}
                rowComponent={ListComponent}
                rowCount={tableMeta?.rowCount || 0}
                rowHeight={tableMeta?.itemHeight || 0}
                rowProps={itemProps}
            />
            <AnimatePresence>
                {hasExpanded && (
                    <ExpandedListContainer>
                        <ExpandedListItem internalState={internalState} itemType={itemType} />
                    </ExpandedListContainer>
                )}
            </AnimatePresence>
        </div>
    );
};

const ListComponent = ({
    columns,
    data,
    enableExpansion,
    enableSelection,
    gap,
    index,
    internalState,
    itemType,
    style,
}: RowComponentProps<GridItemProps>) => {
    return (
        <div className={styles.itemList} style={style}>
            {data[index].map((d) => (
                <div
                    className={clsx(styles.itemRow, styles[`gap-${gap}`])}
                    key={d.index}
                    style={{ '--columns': columns } as CSSProperties}
                >
                    <ItemCard
                        controls={{
                            onClick: enableSelection
                                ? (item, itemType) => {
                                      return itemListControls.handleItemClick(
                                          item,
                                          itemType,
                                          internalState,
                                      );
                                  }
                                : undefined,
                            onDoubleClick: (item, itemType) => {
                                return itemListControls.handleItemDoubleClick(
                                    item,
                                    itemType,
                                    internalState,
                                );
                            },
                            onFavorite: (item, itemType) => {
                                return itemListControls.handleItemFavorite(
                                    item,
                                    itemType,
                                    internalState,
                                );
                            },
                            onItemExpand: enableExpansion
                                ? (item, itemType) => {
                                      return itemListControls.handleItemExpand(
                                          item,
                                          itemType,
                                          internalState,
                                      );
                                  }
                                : undefined,
                            onMore: (item, itemType) => {
                                return itemListControls.handleItemMore(
                                    item,
                                    itemType,
                                    internalState,
                                );
                            },
                            onPlay: (item, itemType, playType) => {
                                return itemListControls.handleItemPlay(
                                    item,
                                    itemType,
                                    playType,
                                    internalState,
                                );
                            },
                            onRating: (item, itemType) => {
                                return itemListControls.handleItemRating(
                                    item,
                                    itemType,
                                    internalState,
                                );
                            },
                        }}
                        data={d.data}
                        itemType={itemType}
                        withControls
                    />
                </div>
            ))}
        </div>
    );
};
