import type {ILabel} from '@/models/ILabel'
import {db} from './database'
import type {UseMutationResult, UseSuspenseQueryResult} from '@tanstack/react-query'
import {useMutation, useQueryClient, useSuspenseQuery} from '@tanstack/react-query'
import {labelsTable} from '@/db/schema'
import {eq} from 'drizzle-orm'
import type {IOnMutateResult} from '@/models/IOnMutateResult'

//region Mutations & queries

/**
 * ---------------------------------------------------------------------------------------------------------------------
 *                                          MUTATIONS & QUERIES
 * ---------------------------------------------------------------------------------------------------------------------
 */

export const useGetLabels = (): UseSuspenseQueryResult<ILabel[], Error> => {
  return useSuspenseQuery({
    queryKey: ['labels'],
    queryFn: getLabels,
  })
}

export const useGetLabel = (id?: number): UseSuspenseQueryResult<ILabel | null, Error> => {
  return useSuspenseQuery({
    queryKey: ['labels', id],
    queryFn: () => (id ? getLabel(id) : null),
  })
}

export const useCreateLabel = (): UseMutationResult<ILabel, Error, CreateLabelsParams, IOnMutateResult<ILabel[]>> => {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: createLabel,
    onMutate: newLabel => {
      const queryKey = ['labels']
      const oldData = queryClient.getQueryData<ILabel[]>(queryKey) ?? []
      queryClient.setQueryData<ILabel[]>(queryKey, [...oldData, {...newLabel, id: -1}])
      return {queryKey, oldData}
    },
    onSuccess: (data, _, context) => {
      if (context) {
        queryClient.setQueryData<ILabel[]>(context.queryKey, [...context.oldData, data])
      }
    },
  })
}

export const useDeleteLabel = (): UseMutationResult<void, Error, DeleteLabelsParams, void> => {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: deleteLabel,
    onMutate: ({id}) => {
      queryClient.setQueryData<ILabel[]>(['labels'], old => (old ?? []).filter(label => label.id !== id))
      queryClient.setQueryData<ILabel>(['labels', id], undefined)
    },
    onSuccess: async () => {
      await queryClient.invalidateQueries({queryKey: ['tasks']})
    },
  })
}

export const useUpdateLabel = (): UseMutationResult<ILabel, Error, ILabel, IOnMutateResult<ILabel>> => {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: updateLabel,
    onMutate: updatedLabel => {
      const queryKey = ['labels', updatedLabel.id]
      const oldData = queryClient.getQueryData<ILabel>(queryKey)!
      queryClient.setQueryData<ILabel>(queryKey, updatedLabel)
      queryClient.setQueryData<ILabel[]>(['labels'], old =>
        (old ?? []).map(t => (t.id === updatedLabel.id ? updatedLabel : t)),
      )
      return {queryKey, oldData}
    },
    onSuccess: (data, _, context) => {
      if (context) {
        queryClient.setQueryData<ILabel>(context.queryKey, data)
      }
    },
  })
}

//endregion

//region API functions

/**
 * ---------------------------------------------------------------------------------------------------------------------
 *                                          API functions
 * ---------------------------------------------------------------------------------------------------------------------
 */

async function getLabels(): Promise<ILabel[]> {
  return db.select().from(labelsTable)
}

async function getLabel(id: number): Promise<ILabel> {
  const [label] = await db.select().from(labelsTable).where(eq(labelsTable.id, id))
  return label
}

type CreateLabelsParams = Omit<ILabel, 'id'>

async function createLabel(params: CreateLabelsParams): Promise<ILabel> {
  // Via returning wordt de aangemaakte rij teruggegeven.
  const [label] = await db.insert(labelsTable).values(params).returning()
  return label
}

interface DeleteLabelsParams {
  id: number
}

async function deleteLabel({id}: DeleteLabelsParams): Promise<void> {
  await db.delete(labelsTable).where(eq(labelsTable.id, id))
}

async function updateLabel({id, ...rest}: ILabel): Promise<ILabel> {
  // Via returning wordt de aangepast rij teruggegeven.
  const [label] = await db.update(labelsTable).set(rest).where(eq(labelsTable.id, id)).returning()
  return label
}

//endregion
