import { RefObject, useMemo, useRef, useState } from "react";
import { DragOverEvent, DragStartEvent, 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 { getStatusId, isStatusId } from "@/app/deals/utils/statusId";
import useIsMobile from "@/hooks/utils/useIsMobile";
import { DealSchema, StatusSchema } from "@/lib/client";

import { sortByLexorank } from "@/utils/lexorank/sort";

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

const useDealsAndStatusesDnd = (): ReturnType => {
    const swiperRef = useRef<SwiperRef>(null);
    const [activeDeal, setActiveDeal] = useState<DealSchema | null>(null);
    const [activeStatus, setActiveStatus] = useState<StatusSchema | null>(null);
    const { statuses, setStatuses, statusesCrud } = useStatusesContext();
    const { deals, setDeals, dealsCrud } = 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 = deals.find(deal => deal.id === dealId);
        if (!deal) return;
        return statuses.find(status => status.id === deal.status.id);
    };

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

    const swipeSliderDuringDrag = (activeId: number, over: Over) => {
        const activeStatus = getStatusByDealId(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" && isStatusId(over.id)) {
            const overStatusId = getStatusId(over.id);
            overStatusLexorank = statuses.find(
                s => s.id === overStatusId
            )?.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") {
            swipeSliderDuringDrag(activeId, over);
        }

        if (typeof activeId === "string" && isStatusId(activeId)) {
            handleColumnDragOver(activeId, over);
            return;
        }
        handleDealDragOver(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,
            activeStatusId
        );
        if (!overStatus) return;

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

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

        if (typeof over.id === "string" && isStatusId(over.id)) {
            overStatusId = getStatusId(over.id);
        } else {
            const deal = deals.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,
        activeStatusId: number,
        isOnDragEnd: boolean = false
    ): { overStatus?: StatusSchema; newLexorank?: string } => {
        if (typeof overId === "string") {
            return {
                overStatus: getStatusById(getStatusId(overId)),
                newLexorank: undefined,
            };
        }

        const overDealId = Number(overId);
        const overStatus = getStatusByDealId(overDealId);

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

        const statusDeals = sortByLexorank(
            deals.filter(deal => deal.status.id === overStatus.id)
        );
        const overDealIndex = statusDeals.findIndex(
            deal => deal.id === overDealId
        );

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

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

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

        const activeId: string | number = active.id;

        if (typeof activeId === "string" && isStatusId(activeId)) {
            handleStatusColumnDragEnd(activeId, over);
            return;
        }
        handleDealDragEnd(activeId, over);
    };

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

        if (typeof over.id === "string" && isStatusId(over.id)) {
            overStatusId = getStatusId(over.id);
        } else {
            const deal = deals.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,
            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 handleDragStart = ({ active }: DragStartEvent) => {
        const activeId = active.id as string | number;

        if (typeof activeId === "string" && isStatusId(activeId)) {
            const statusId = getStatusId(activeId);
            setActiveStatus(
                statuses.find(status => status.id === statusId) ?? null
            );
            return;
        }

        setActiveDeal(
            deals.find(deal => deal.id === (activeId as number)) ?? null
        );
    };

    return {
        swiperRef,
        sortedStatuses,
        handleDragStart,
        handleDragOver,
        handleDragEnd,
        activeStatus,
        activeDeal,
    };
};

export default useDealsAndStatusesDnd;
