import clsx from 'clsx';
import { forwardRef, ReactElement, useEffect, useMemo, useRef, useState } from 'react';

import styles from './play-queue.module.css';

import { useItemListColumnReorder } from '/@/renderer/components/item-list/helpers/use-item-list-column-reorder';
import { useItemListColumnResize } from '/@/renderer/components/item-list/helpers/use-item-list-column-resize';
import {
    ItemTableList,
    TableGroupHeader,
} from '/@/renderer/components/item-list/item-table-list/item-table-list';
import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
import { ItemListHandle } from '/@/renderer/components/item-list/types';
import { useIsPlayerFetching, usePlayer } from '/@/renderer/features/player/context/player-context';
import { searchLibraryItems } from '/@/renderer/features/shared/utils';
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
import {
    subscribeCurrentTrack,
    subscribePlayerQueue,
    useListSettings,
    usePlayerActions,
    usePlayerQueueType,
    usePlayerSong,
} from '/@/renderer/store';
import { Flex } from '/@/shared/components/flex/flex';
import { LoadingOverlay } from '/@/shared/components/loading-overlay/loading-overlay';
import { Text } from '/@/shared/components/text/text';
import { useDebouncedValue } from '/@/shared/hooks/use-debounced-value';
import { useFocusWithin } from '/@/shared/hooks/use-focus-within';
import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
import { useMergedRef } from '/@/shared/hooks/use-merged-ref';
import { LibraryItem, QueueSong, Song } from '/@/shared/types/domain-types';
import { DragTarget } from '/@/shared/types/drag-and-drop';
import { ItemListKey, Play, PlayerQueueType } from '/@/shared/types/types';

type QueueProps = {
    listKey: ItemListKey;
    searchTerm: string | undefined;
};

export const PlayQueue = forwardRef<ItemListHandle, QueueProps>(({ listKey, searchTerm }, ref) => {
    const { table } = useListSettings(listKey) || {};

    const isFetching = useIsPlayerFetching();
    const tableRef = useRef<ItemListHandle>(null);
    const previousSongCountRef = useRef<number>(0);
    const mergedRef = useMergedRef(ref, tableRef);
    const { getQueue } = usePlayerActions();
    const queueType = usePlayerQueueType();

    const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 200);

    const [data, setData] = useState<QueueSong[]>([]);
    const [groups, setGroups] = useState<TableGroupHeader[]>([]);
    const [containerKey, setContainerKey] = useState<string>(() => Math.random().toString(36));

    useEffect(() => {
        const setQueue = () => {
            const queue = getQueue() || { groups: [], items: [] };

            setData(queue.items);

            if (queueType === PlayerQueueType.PRIORITY && queue.groups && queue.groups.length > 0) {
                const transformedGroups: TableGroupHeader[] = queue.groups.map((group) => ({
                    itemCount: group.count,
                    render: (): ReactElement => {
                        return (
                            <div className={styles.groupRow}>
                                <Text
                                    fw={600}
                                    overflow="visible"
                                    size="md"
                                    style={{
                                        textWrap: 'nowrap',
                                        whiteSpace: 'nowrap',
                                    }}
                                >
                                    {group.name}
                                </Text>
                            </div>
                        );
                    },
                    rowHeight: 40,
                }));
                setGroups(transformedGroups);
            } else {
                setGroups([]);
            }
        };

        const unsub = subscribePlayerQueue(() => {
            setQueue();
        });

        const unsubCurrentTrack = subscribeCurrentTrack((e) => {
            if (e.index !== -1) {
                tableRef.current?.scrollToIndex(e.index, {
                    align: 'top',
                    behavior: 'smooth',
                });
            }
        });

        setQueue();

        return () => {
            unsub();
            unsubCurrentTrack();
        };
    }, [getQueue, queueType, tableRef]);

    useEffect(() => {
        const currentCount = data.length;
        const previousCount = previousSongCountRef.current;

        if (previousCount === 0 && currentCount > 0) {
            setContainerKey(Math.random().toString(36));
        }

        previousSongCountRef.current = currentCount;
    }, [data.length]);

    const filteredData: QueueSong[] = useMemo(() => {
        if (debouncedSearchTerm) {
            const searched = searchLibraryItems(data, debouncedSearchTerm, LibraryItem.SONG);
            return searched;
        }

        return data;
    }, [data, debouncedSearchTerm]);

    const isEmpty = filteredData.length === 0;

    const { handleColumnReordered } = useItemListColumnReorder({
        itemListKey: listKey,
    });

    const { handleColumnResized } = useItemListColumnResize({
        itemListKey: listKey,
    });

    const currentSong = usePlayerSong();
    const currentSongUniqueId = currentSong?._uniqueId;

    const { focused, ref: containerFocusRef } = useFocusWithin();
    const player = usePlayer();

    useHotkeys([
        [
            'delete',
            () => {
                if (focused) {
                    const selectedItems =
                        tableRef.current?.internalState.getSelected() as QueueSong[];

                    if (!selectedItems || selectedItems.length === 0) {
                        return;
                    }

                    player.clearSelected(selectedItems);
                }
            },
        ],
    ]);

    return (
        <div className={styles.container} key={containerKey} ref={containerFocusRef}>
            <LoadingOverlay pos="absolute" visible={isFetching} />
            <ItemTableList
                activeRowId={currentSongUniqueId}
                autoFitColumns={table.autoFitColumns}
                CellComponent={ItemTableListColumn}
                columns={table.columns}
                data={filteredData}
                enableAlternateRowColors={table.enableAlternateRowColors}
                enableDrag
                enableExpansion={false}
                enableHeader
                enableHorizontalBorders={table.enableHorizontalBorders}
                enableRowHoverHighlight={table.enableRowHoverHighlight}
                enableSelection
                enableVerticalBorders={table.enableVerticalBorders}
                getRowId="_uniqueId"
                groups={groups.length > 0 ? groups : undefined}
                initialTop={{
                    to: 0,
                    type: 'offset',
                }}
                itemType={LibraryItem.QUEUE_SONG}
                onColumnReordered={handleColumnReordered}
                onColumnResized={handleColumnResized}
                ref={mergedRef}
                size={table.size}
            />
            {isEmpty && <EmptyQueueDropZone />}
        </div>
    );
});

const EmptyQueueDropZone = () => {
    const playerContext = usePlayer();

    const { isDraggedOver, ref } = useDragDrop<HTMLDivElement>({
        drop: {
            canDrop: () => {
                return true;
            },
            getData: () => {
                return {
                    id: [],
                    item: [],
                    itemType: LibraryItem.QUEUE_SONG,
                    type: DragTarget.QUEUE_SONG,
                };
            },
            onDrag: () => {
                return;
            },
            onDragLeave: () => {
                return;
            },
            onDrop: (args) => {
                if (args.self.type === DragTarget.QUEUE_SONG) {
                    const sourceServerId = (
                        args.source.item?.[0] as unknown as { _serverId: string }
                    )?._serverId;

                    const sourceItemType = args.source.itemType as LibraryItem;

                    switch (args.source.type) {
                        case DragTarget.ALBUM: {
                            if (sourceServerId) {
                                playerContext.addToQueueByFetch(
                                    sourceServerId,
                                    args.source.id,
                                    sourceItemType,
                                    Play.NOW,
                                );
                            }
                            break;
                        }
                        case DragTarget.ALBUM_ARTIST: {
                            if (sourceServerId) {
                                playerContext.addToQueueByFetch(
                                    sourceServerId,
                                    args.source.id,
                                    sourceItemType,
                                    Play.NOW,
                                );
                            }
                            break;
                        }
                        case DragTarget.ARTIST: {
                            if (sourceServerId) {
                                playerContext.addToQueueByFetch(
                                    sourceServerId,
                                    args.source.id,
                                    sourceItemType,
                                    Play.NOW,
                                );
                            }
                            break;
                        }
                        case DragTarget.GENRE: {
                            if (sourceServerId) {
                                playerContext.addToQueueByFetch(
                                    sourceServerId,
                                    args.source.id,
                                    sourceItemType,
                                    Play.NOW,
                                );
                            }
                            break;
                        }
                        case DragTarget.PLAYLIST: {
                            if (sourceServerId) {
                                playerContext.addToQueueByFetch(
                                    sourceServerId,
                                    args.source.id,
                                    sourceItemType,
                                    Play.NOW,
                                );
                            }
                            break;
                        }
                        case DragTarget.QUEUE_SONG: {
                            const sourceItems = (args.source.item || []) as QueueSong[];
                            if (sourceItems.length > 0) {
                                playerContext.addToQueueByData(sourceItems, Play.NOW);
                            }
                            break;
                        }
                        case DragTarget.SONG: {
                            const sourceItems = (args.source.item || []) as Song[];
                            if (sourceItems.length > 0) {
                                playerContext.addToQueueByData(sourceItems, Play.NOW);
                            }
                            break;
                        }
                        default: {
                            break;
                        }
                    }
                }

                return;
            },
        },
        isEnabled: true,
    });

    return (
        <Flex
            align="center"
            className={clsx(styles.dropZone, {
                [styles.draggedOver]: isDraggedOver,
            })}
            direction="column"
            gap="md"
            justify="center"
            ref={ref}
            w="100%"
        />
    );
};
