import React from "react";
import {
    useMutation,
    UseMutationOptions,
    useQueryClient,
} from "@tanstack/react-query";
import { AxiosError } from "axios";
import { Text } from "@mantine/core";
import { modals } from "@mantine/modals";
import { HttpValidationError } from "@/lib/client";
import { notifications } from "@/lib/notifications";
import { sortByLexorank } from "@/utils/lexorank";
import {
    BaseEntity,
    CreateMutationOptions,
    DeleteMutationOptions,
    UpdateMutationOptions,
} from "./types";

type CrudOperations<TEntity, TUpdate> = {
    onCreate: (name: string) => void;
    onUpdate: (id: number, update: TUpdate) => void;
    onDelete: (entity: TEntity) => void;
};

type UseEntityOperationsProps<TEntity extends BaseEntity, TUpdate, TCreate> = {
    key: string;
    queryKey: any[];
    mutations: {
        create: UseMutationOptions<
            any,
            AxiosError<HttpValidationError>,
            CreateMutationOptions
        >;
        update: UseMutationOptions<
            any,
            AxiosError<HttpValidationError>,
            UpdateMutationOptions
        >;
        delete: UseMutationOptions<
            any,
            AxiosError<HttpValidationError>,
            DeleteMutationOptions
        >;
    };
    getCreateEntity: (name: string) => TCreate | null;
    getUpdateEntity: (oldEntity: TEntity, update: TUpdate) => TEntity;
    getDeleteConfirmTitle: (entity: TEntity) => string;
};

const useCrudOperations = <
    TEntity extends BaseEntity,
    TUpdate extends object,
    TCreate extends object,
>({
    key,
    queryKey,
    mutations,
    getCreateEntity,
    getUpdateEntity,
    getDeleteConfirmTitle,
}: UseEntityOperationsProps<TEntity, TUpdate, TCreate>): CrudOperations<
    TEntity,
    TUpdate
> => {
    const queryClient = useQueryClient();

    const onError = (
        error: AxiosError<HttpValidationError>,
        _: any,
        context: any
    ) => {
        console.error(error);
        notifications.error({
            message: error.response?.data?.detail as string | undefined,
        });
        if (context?.previous) {
            queryClient.setQueryData(queryKey, context.previous);
        }
    };

    const onSettled = () => {
        queryClient.invalidateQueries({
            predicate: (query: { queryKey: any }) =>
                query.queryKey[0]?._id === key,
        });
    };

    const createMutation = useMutation({
        ...mutations.create,
        onError,
        onSettled,
    });

    const updateMutation = useMutation({
        ...mutations.update,
        onError,
        onSettled,
        onMutate: async ({ body: { entity: update } }) => {
            await queryClient.cancelQueries({ queryKey: [key] });

            const previous = queryClient.getQueryData(queryKey);

            queryClient.setQueryData(queryKey, (old: { items: TEntity[] }) => {
                let updated = old.items.map((entity: TEntity) =>
                    entity.id === update.id
                        ? getUpdateEntity(entity, update)
                        : entity
                );

                if ("lexorank" in update) {
                    updated = sortByLexorank(
                        updated as (TEntity & { lexorank: string })[]
                    );
                }

                return {
                    ...old,
                    items: updated,
                };
            });

            return { previous };
        },
    });

    const deleteMutation = useMutation({
        ...mutations.delete,
        onError,
        onSettled,
        onMutate: async ({ path: { pk } }) => {
            await queryClient.cancelQueries({ queryKey: [key] });

            const previous = queryClient.getQueryData(queryKey);

            queryClient.setQueryData(queryKey, (old: { items: TEntity[] }) => {
                const filtered = old.items.filter(e => e.id !== pk);
                return {
                    ...old,
                    items: filtered,
                };
            });

            return { previous };
        },
    });

    const onCreate = (name: string) => {
        const entity = getCreateEntity(name);
        if (!entity) return;
        createMutation.mutate({
            body: {
                entity,
            },
            path: undefined,
            query: undefined,
        });
    };

    const onUpdate = async (id: number, update: TUpdate) => {
        updateMutation.mutate({
            body: {
                entity: update,
            },
            path: { pk: id },
            query: undefined,
        });
    };

    const onDelete = (entity: TEntity, onSuccess?: () => void) => {
        modals.openConfirmModal({
            title: getDeleteConfirmTitle(entity),
            children: (
                <Text>Вы уверены, что хотите удалить "{entity.name}"?</Text>
            ),
            confirmProps: { color: "red" },
            onConfirm: () => {
                deleteMutation.mutate({ path: { pk: entity.id } } as any);
                onSuccess && onSuccess();
            },
        });
    };

    return { onCreate, onUpdate, onDelete };
};

export default useCrudOperations;
