import { RefObject, useMemo, useRef } from "react";
import { DragOverEvent, Over } from "@dnd-kit/core";
import { SwiperRef } from "swiper/swiper-react";
import { useDebouncedCallback } from "@mantine/hooks";
import { useDealsContext } from "@/app/deals/contexts/DealsContext";
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
import useGetNewRank from "@/app/deals/hooks/useGetNewRank";
import isItemGroup from "@/app/deals/utils/isItemGroup";
import {
    getContainerId,
    isContainerId,
} from "@/components/dnd/FunnelDnd/utils/columnId";
import {
    getGroupId,
    isGroupId,
} from "@/components/dnd/FunnelDnd/utils/groupId";
import useIsMobile from "@/hooks/utils/useIsMobile";
import { StatusSchema } from "@/lib/client";
import { sortByLexorank } from "@/utils/lexorank/sort";

type ReturnType = {
    sortedStatuses: StatusSchema[];
    handleDragOver: ({ active, over }: DragOverEvent) => void;
    handleDragEnd: ({ active, over }: DragOverEvent) => void;
    swiperRef: RefObject<SwiperRef | null>;
};

const useDealsAndStatusesDnd = (): ReturnType => {
    const swiperRef = useRef<SwiperRef>(null);
    const { statuses, setStatuses, statusesCrud } = useStatusesContext();
    const {
        deals,
        dealsWithoutGroup,
        groupsWithDeals,
        setDeals,
        dealsCrud,
        groupsCrud,
    } = useDealsContext();
    const sortedStatuses = useMemo(() => sortByLexorank(statuses), [statuses]);
    const isMobile = useIsMobile();

    const {
        getNewRankForSameStatus,
        getNewRankForAnotherStatus,
        getNewStatusRank,
    } = useGetNewRank();

    const debouncedSetStatuses = useDebouncedCallback(setStatuses, 200);
    const debouncedSetDeals = useDebouncedCallback(setDeals, 200);

    const getStatusByDealId = (dealId: number) => {
        const deal = dealsWithoutGroup.find(deal => deal.id === dealId);
        if (!deal) return;
        return statuses.find(status => status.id === deal.status.id);
    };

    const getStatusByGroupId = (groupId: number) => {
        const group = groupsWithDeals.find(group => group.id === groupId);
        if (!group || group.items.length === 0) return;
        return statuses.find(status => status.id === group.items[0].status.id);
    };

    const getStatusById = (statusId: number) => {
        return statuses.find(status => status.id === statusId);
    };

    const getStatusDealsAndGroups = (statusId: number) =>
        sortByLexorank([
            ...dealsWithoutGroup.filter(d => d.status.id === statusId),
            ...groupsWithDeals.filter(
                g => g.items.length > 0 && g.items[0].status.id === statusId
            ),
        ]);

    const swipeSliderDuringDrag = (activeId: number | string, over: Over) => {
        let activeStatus: StatusSchema | undefined;
        if (typeof activeId === "string") {
            activeStatus = getStatusByGroupId(getGroupId(activeId));
        } else {
            activeStatus = getStatusByDealId(Number(activeId));
        }
        const swiperActiveStatus =
            statuses[swiperRef.current?.swiper.activeIndex ?? 0];
        if (swiperActiveStatus.id !== activeStatus?.id) return;

        const activeStatusLexorank = activeStatus?.lexorank;
        let overStatusLexorank: string | undefined;

        if (typeof over.id === "string") {
            if (isContainerId(over.id)) {
                const overStatusId = getContainerId(over.id);
                overStatusLexorank = statuses.find(
                    s => s.id === overStatusId
                )?.lexorank;
            } else {
                const overGroupId = getGroupId(over.id);
                overStatusLexorank = getStatusByGroupId(overGroupId)?.lexorank;
            }
        } else {
            overStatusLexorank = getStatusByDealId(Number(over.id))?.lexorank;
        }

        if (
            !activeStatusLexorank ||
            !overStatusLexorank ||
            !swiperRef.current?.swiper
        )
            return;

        const activeIndex = sortedStatuses.findIndex(
            s => s.lexorank === activeStatusLexorank
        );
        const overIndex = sortedStatuses.findIndex(
            s => s.lexorank === overStatusLexorank
        );

        if (activeIndex > overIndex) {
            swiperRef.current.swiper.slidePrev();
            return;
        }
        if (activeIndex < overIndex) {
            swiperRef.current.swiper.slideNext();
        }
    };

    const handleDragOver = ({ active, over }: DragOverEvent) => {
        if (!over) return;
        const activeId = active.id as string | number;

        if (isMobile && (typeof activeId !== "string" || isGroupId(activeId))) {
            swipeSliderDuringDrag(activeId, over);
        }

        if (typeof activeId !== "string") {
            handleDealDragOver(activeId, over);
            return;
        }

        if (isContainerId(activeId)) {
            handleColumnDragOver(activeId, over);
            return;
        }
        handleGroupDragOver(activeId, over);
    };

    const handleDealDragOver = (activeId: string | number, over: Over) => {
        const activeDealId = Number(activeId);
        const activeStatusId = getStatusByDealId(activeDealId)?.id;
        if (!activeStatusId) return;

        const { overStatus, newLexorank } = getDropTarget(
            over.id,
            activeDealId,
            undefined,
            activeStatusId
        );
        if (!overStatus) return;

        debouncedSetDeals(
            deals.map(deal =>
                deal.id === activeDealId
                    ? {
                          ...deal,
                          status: overStatus,
                          lexorank: newLexorank || deal.lexorank,
                      }
                    : deal
            )
        );
    };

    const handleGroupDragOver = (activeId: string, over: Over) => {
        const activeGroupId = getGroupId(activeId);
        const activeStatusId = getStatusByGroupId(activeGroupId)?.id;
        if (!activeStatusId) return;

        const { overStatus, newLexorank } = getDropTarget(
            over.id,
            undefined,
            activeGroupId,
            activeStatusId
        );
        if (!overStatus) return;

        debouncedSetDeals(
            deals.map(deal =>
                deal.group && deal.group.id === activeGroupId
                    ? {
                          ...deal,
                          status: overStatus,
                          group: {
                              ...deal.group,
                              lexorank: newLexorank || deal.group.lexorank,
                          },
                      }
                    : deal
            )
        );
    };

    const handleColumnDragOver = (activeId: string, over: Over) => {
        const activeStatusId = getContainerId(activeId);
        let overStatusId: number;

        if (typeof over.id === "string") {
            if (isContainerId(over.id)) {
                overStatusId = getContainerId(over.id);
            } else {
                const status = getStatusByGroupId(getGroupId(over.id));
                if (!status) return;
                overStatusId = status.id;
            }
        } else {
            const deal = dealsWithoutGroup.find(deal => deal.id === over.id);
            if (!deal) return;
            overStatusId = deal.status.id;
        }

        if (!overStatusId || activeStatusId === overStatusId) return;

        const newRank = getNewStatusRank(activeStatusId, overStatusId);
        if (!newRank) return;

        debouncedSetStatuses(
            statuses.map(status =>
                status.id === activeStatusId
                    ? { ...status, lexorank: newRank }
                    : status
            )
        );
    };

    const getDropTarget = (
        overId: string | number,
        activeDealId: number | undefined,
        activeGroupId: number | undefined,
        activeStatusId: number,
        isOnDragEnd: boolean = false
    ): { overStatus?: StatusSchema; newLexorank?: string } => {
        if (typeof overId === "string") {
            if (isContainerId(overId)) {
                return getStatusDropTarget(overId);
            }
            if (isGroupId(overId)) {
                return getGroupDropTarget(
                    overId,
                    activeGroupId,
                    activeStatusId,
                    isOnDragEnd
                );
            }
        }

        return getDealDropTarget(
            Number(overId),
            activeDealId,
            activeStatusId,
            isOnDragEnd
        );
    };

    const getStatusDropTarget = (overId: string) => ({
        overStatus: getStatusById(getContainerId(overId)),
        newLexorank: undefined,
    });

    const getDealDropTarget = (
        overId: number,
        activeDealId: number | undefined,
        activeStatusId: number,
        isOnDragEnd: boolean = false
    ) => {
        const overDealId = Number(overId);
        const overStatus = getStatusByDealId(overDealId);

        if (!overStatus || (!isOnDragEnd && activeDealId === overDealId)) {
            return { overStatus: undefined, newLexorank: undefined };
        }

        const statusItems = getStatusDealsAndGroups(overStatus.id);
        const overDealIndex = statusItems.findIndex(
            deal => !isItemGroup(deal) && deal.id === overDealId
        );
        const activeDealIndex = statusItems.findIndex(
            deal => !isItemGroup(deal) && deal.id === activeDealId
        );

        if (activeStatusId === overStatus.id) {
            const newLexorank = getNewRankForSameStatus(
                statusItems,
                overDealIndex,
                activeDealIndex
            );
            return { overStatus, newLexorank };
        }

        const newLexorank = getNewRankForAnotherStatus(
            statusItems,
            overDealIndex
        );
        return { overStatus, newLexorank };
    };

    const getGroupDropTarget = (
        overId: string,
        activeGroupId: number | undefined,
        activeStatusId: number,
        isOnDragEnd: boolean = false
    ) => {
        const overGroupId = getGroupId(overId);
        const overStatus = getStatusByGroupId(overGroupId);

        if (!overStatus || (!isOnDragEnd && activeGroupId === overGroupId)) {
            return { overStatus: undefined, newLexorank: undefined };
        }

        const statusItems = getStatusDealsAndGroups(overStatus.id);
        const overGroupIndex = statusItems.findIndex(
            group => isItemGroup(group) && group.id === overGroupId
        );
        const activeGroupIndex = statusItems.findIndex(
            group => isItemGroup(group) && group.id === activeGroupId
        );

        if (activeStatusId === overStatus.id) {
            const newLexorank = getNewRankForSameStatus(
                statusItems,
                overGroupIndex,
                activeGroupIndex
            );
            return { overStatus, newLexorank };
        }

        const newLexorank = getNewRankForAnotherStatus(
            statusItems,
            overGroupIndex
        );
        return { overStatus, newLexorank };
    };

    const handleDragEnd = ({ active, over }: DragOverEvent) => {
        if (!over) return;

        const activeId: string | number = active.id;

        if (typeof activeId !== "string") {
            handleDealDragEnd(activeId, over);
            return;
        }
        if (isContainerId(activeId)) {
            handleStatusColumnDragEnd(activeId, over);
            return;
        }
        handleGroupDragEnd(activeId, over);
    };

    const handleStatusColumnDragEnd = (activeId: string, over: Over) => {
        const activeStatusId = getContainerId(activeId);
        let overStatusId: number;

        if (typeof over.id === "string" && isContainerId(over.id)) {
            overStatusId = getContainerId(over.id);
        } else {
            const deal = dealsWithoutGroup.find(
                deal => deal.status.id === over.id
            );
            if (!deal) return;
            overStatusId = deal.status.id;
        }

        if (!overStatusId) return;

        const newRank = getNewStatusRank(activeStatusId, overStatusId);
        if (!newRank) return;

        onStatusDragEnd?.(activeStatusId, newRank);
    };

    const onStatusDragEnd = (statusId: number, lexorank: string) => {
        statusesCrud.onUpdate(statusId, { lexorank });
    };

    const handleDealDragEnd = (activeId: number | string, over: Over) => {
        const activeDealId = Number(activeId);
        const activeStatusId = getStatusByDealId(activeDealId)?.id;
        if (!activeStatusId) return;

        const { overStatus, newLexorank } = getDropTarget(
            over.id,
            activeDealId,
            undefined,
            activeStatusId,
            true
        );
        if (!overStatus) return;

        onDealDragEnd(activeDealId, overStatus.id, newLexorank);
    };

    const onDealDragEnd = (
        dealId: number,
        statusId: number,
        lexorank?: string
    ) => {
        dealsCrud.onUpdate(dealId, { statusId, lexorank, name: null });
    };

    const handleGroupDragEnd = (activeId: string, over: Over) => {
        const activeGroupId = getGroupId(activeId);
        const activeStatusId = getStatusByGroupId(activeGroupId)?.id;
        if (!activeStatusId) return;

        const { overStatus, newLexorank } = getDropTarget(
            over.id,
            undefined,
            activeGroupId,
            activeStatusId,
            true
        );
        if (!overStatus) return;

        onGroupDragEnd(activeGroupId, overStatus.id, newLexorank);
    };

    const onGroupDragEnd = (
        groupId: number,
        statusId: number,
        lexorank?: string
    ) => {
        groupsCrud.onUpdate(groupId, { statusId, lexorank, name: null });
    };

    return {
        swiperRef,
        sortedStatuses,
        handleDragOver,
        handleDragEnd,
    };
};

export default useDealsAndStatusesDnd;
