"use client";

import React, { FC, useMemo, useState } from "react";
import {
    closestCorners,
    defaultDropAnimation,
    DndContext,
    DragOverEvent,
    DragOverlay,
    DragStartEvent,
    KeyboardSensor,
    PointerSensor,
    useSensor,
    useSensors,
} from "@dnd-kit/core";
import { sortableKeyboardCoordinates } from "@dnd-kit/sortable";
import { LexoRank } from "lexorank";
import { throttle } from "lodash";
import { Group } from "@mantine/core";
import DealCard from "@/app/deals/components/DealCard/DealCard";
import StatusColumn from "@/app/deals/components/StatusColumn/StatusColumn";
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
import { DealSchema } from "@/types/DealSchema";
import { getNewLexorank, sortByLexorank } from "@/utils/lexorank";

type Props = {
    onDealDragEnd: (
        dealId: number,
        statusId: number,
        lexorank?: string
    ) => void;
};

const StatusColumnsDnd: FC<Props> = props => {
    const { statuses, deals, setDeals } = useStatusesContext();
    const [activeDeal, setActiveDeal] = useState<DealSchema | null>(null);
    const throttledSetDeals = useMemo(
        () => throttle(setDeals, 200),
        [setDeals]
    );

    const sensors = useSensors(
        useSensor(PointerSensor),
        useSensor(KeyboardSensor, {
            coordinateGetter: sortableKeyboardCoordinates,
        })
    );

    const handleDragStart = ({ active }: DragStartEvent) => {
        setActiveDeal(
            deals.find(deal => deal.id === (active.id as number)) ?? null
        );
    };

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

    const handleDragOver = ({ active, over }: DragOverEvent) => {
        if (!over?.id) return;

        const activeDealId = Number(active.id);
        const activeStatusId = getStatusByDealId(activeDealId)?.id;
        if (!activeStatusId) return;

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

        throttledSetDeals(deals =>
            deals.map(deal =>
                deal.id === activeDealId
                    ? {
                          ...deal,
                          statusId: overStatusId,
                          rank: newLexorank || deal.rank,
                      }
                    : deal
            )
        );
    };

    const getDropTarget = (
        overId: string | number,
        activeDealId: number,
        activeStatusId: number
    ) => {
        if (typeof overId === "string") {
            return {
                overStatusId: Number(overId.replace(/-status$/, "")),
                newLexorank: undefined,
            };
        }

        const overDealId = Number(overId);
        const overStatusId = getStatusByDealId(overDealId)?.id;

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

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

        let newLexorank;
        if (activeStatusId === overStatusId) {
            newLexorank = getNewRankForSameStatus(
                statusDeals,
                overDealIndex,
                activeDealId
            );
        } else {
            newLexorank = getNewRankForAnotherStatus(
                statusDeals,
                overDealIndex
            );
        }

        return { overStatusId, newLexorank };
    };

    const getNewRankForSameStatus = (
        statusDeals: DealSchema[],
        overDealIndex: number,
        activeDealId: number
    ) => {
        const activeDealIndex = statusDeals.findIndex(
            deal => deal.id === activeDealId
        );
        const [leftIndex, rightIndex] =
            overDealIndex < activeDealIndex
                ? [overDealIndex - 1, overDealIndex]
                : [overDealIndex, overDealIndex + 1];

        const leftLexorank =
            leftIndex >= 0 ? LexoRank.parse(deals[leftIndex].rank) : null;
        const rightLexorank =
            rightIndex < deals.length
                ? LexoRank.parse(deals[rightIndex].rank)
                : null;

        return getNewLexorank(leftLexorank, rightLexorank).toString();
    };

    const getNewRankForAnotherStatus = (
        statusDeals: DealSchema[],
        overDealIndex: number
    ) => {
        const leftLexorank =
            overDealIndex > 0
                ? LexoRank.parse(statusDeals[overDealIndex - 1].rank)
                : null;
        const rightLexorank = LexoRank.parse(statusDeals[overDealIndex].rank);

        return getNewLexorank(leftLexorank, rightLexorank).toString();
    };

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

        const activeDealId = Number(active.id);
        const activeStatusId = getStatusByDealId(activeDealId)?.id;
        if (!activeStatusId) return;

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

        props.onDealDragEnd(activeDealId, overStatusId, newLexorank);
    };

    return (
        <DndContext
            sensors={sensors}
            collisionDetection={closestCorners}
            onDragStart={handleDragStart}
            onDragOver={handleDragOver}
            onDragEnd={handleDragEnd}>
            <Group
                gap={"xs"}
                justify={"start"}>
                {statuses.map(status => (
                    <StatusColumn
                        key={status.id}
                        id={`${status.id}-status`}
                        title={status.name}
                        deals={deals.filter(
                            deal => deal.statusId === status.id
                        )}
                    />
                ))}
                <DragOverlay dropAnimation={defaultDropAnimation}>
                    {activeDeal ? <DealCard deal={activeDeal} /> : null}
                </DragOverlay>
            </Group>
        </DndContext>
    );
};

export default StatusColumnsDnd;
