'use client'

import { useMutation, useQueryClient } from '@tanstack/react-query'
import { apiClient, OfflineError } from '../client'
import { enqueue } from '../offline-queue'
import type { PaginatedResponse, ApiMessageSummary, ApiMessageFull } from '../types'
import {
  mailFlagIsRead,
  mailFlagsWithRead,
  mailFlagsWithStarred,
  mailFlagsWithImportant,
} from '@/lib/mail-flags'
import { useDemoMail } from '@/lib/demo/demo-mail-context'
import { useDemoMailStore } from '@/lib/demo/demo-mail-store'
import { mailMessagesQueryRoot } from '@/lib/api/hooks/use-mail-queries'

function messageDetailQueryRoot(isDemo: boolean) {
  return isDemo ? (['demo', 'message'] as const) : (['message'] as const)
}

export function useUpdateFlags() {
  const queryClient = useQueryClient()
  const demoMail = useDemoMail()

  return useMutation({
    mutationFn: async ({ id, flags }: { id: string; flags: string[] }) => {
      if (demoMail) {
        const updated = useDemoMailStore.getState().updateFlags(id, flags)
        if (!updated) throw new Error('Message introuvable')
        return updated
      }
      try {
        return await apiClient.put<ApiMessageFull>(`/mail/messages/${id}/flags`, { flags })
      } catch (err) {
        if (err instanceof OfflineError) {
          await enqueue({
            id: `flags-${id}-${Date.now()}`,
            timestamp: Date.now(),
            type: 'update_flags',
            payload: { message_id: id, flags },
            retries: 0,
          })
          return undefined
        }
        throw err
      }
    },
    onMutate: async ({ id, flags }) => {
      const messagesRoot = mailMessagesQueryRoot(!!demoMail)
      const messageRoot = messageDetailQueryRoot(!!demoMail)

      await queryClient.cancelQueries({ queryKey: messagesRoot })
      await queryClient.cancelQueries({ queryKey: [...messageRoot, id] })

      const previousMessages = queryClient.getQueriesData<PaginatedResponse<ApiMessageSummary>>({
        queryKey: messagesRoot,
      })

      queryClient.setQueriesData<PaginatedResponse<ApiMessageSummary>>(
        { queryKey: messagesRoot },
        (old) => {
          if (!old) return old
          return { ...old, data: old.data.map((m) => (m.id === id ? { ...m, flags } : m)) }
        }
      )

      queryClient.setQueriesData<ApiMessageFull>({ queryKey: [...messageRoot, id] }, (old) =>
        old ? { ...old, flags } : old
      )

      return { previousMessages }
    },
    onError: (_err, _vars, context) => {
      context?.previousMessages?.forEach(([key, data]) => queryClient.setQueryData(key, data))
    },
    onSettled: () => {
      if (demoMail) {
        useDemoMailStore.getState().bump()
      }
      queryClient.invalidateQueries({ queryKey: mailMessagesQueryRoot(!!demoMail) })
    },
  })
}

export function useUpdateLabels() {
  const queryClient = useQueryClient()
  const demoMail = useDemoMail()

  return useMutation({
    mutationFn: async ({ id, labels }: { id: string; labels: string[] }) => {
      if (demoMail) {
        const updated = useDemoMailStore.getState().updateLabels(id, labels)
        if (!updated) throw new Error('Message introuvable')
        return updated
      }
      try {
        return await apiClient.put<ApiMessageFull>(`/mail/messages/${id}/labels`, { labels })
      } catch (err) {
        if (err instanceof OfflineError) {
          await enqueue({
            id: `labels-${id}-${Date.now()}`,
            timestamp: Date.now(),
            type: 'update_labels',
            payload: { message_id: id, labels },
            retries: 0,
          })
          return undefined
        }
        throw err
      }
    },
    onMutate: async ({ id, labels }) => {
      const messagesRoot = mailMessagesQueryRoot(!!demoMail)
      const messageRoot = messageDetailQueryRoot(!!demoMail)

      await queryClient.cancelQueries({ queryKey: messagesRoot })
      await queryClient.cancelQueries({ queryKey: [...messageRoot, id] })

      const previousMessages = queryClient.getQueriesData<PaginatedResponse<ApiMessageSummary>>({
        queryKey: messagesRoot,
      })

      queryClient.setQueriesData<PaginatedResponse<ApiMessageSummary>>(
        { queryKey: messagesRoot },
        (old) => {
          if (!old) return old
          return { ...old, data: old.data.map((m) => (m.id === id ? { ...m, labels } : m)) }
        }
      )

      queryClient.setQueriesData<ApiMessageFull>({ queryKey: [...messageRoot, id] }, (old) =>
        old ? { ...old, labels } : old
      )

      return { previousMessages }
    },
    onError: (_err, _vars, context) => {
      context?.previousMessages?.forEach(([key, data]) => queryClient.setQueryData(key, data))
    },
    onSettled: () => {
      if (demoMail) {
        useDemoMailStore.getState().bump()
      }
      queryClient.invalidateQueries({ queryKey: mailMessagesQueryRoot(!!demoMail) })
    },
  })
}

export function useDeleteMessage() {
  const queryClient = useQueryClient()
  const demoMail = useDemoMail()

  return useMutation({
    mutationFn: async ({ id }: { id: string }) => {
      if (demoMail) {
        const updated = useDemoMailStore.getState().deleteMessage(id)
        if (!updated) throw new Error('Message introuvable')
        demoMail.notify('Message placé dans la corbeille')
        return
      }
      try {
        await apiClient.delete(`/mail/messages/${id}`)
      } catch (err) {
        if (err instanceof OfflineError) {
          await enqueue({
            id: `delete-${id}-${Date.now()}`,
            timestamp: Date.now(),
            type: 'delete_message',
            payload: { message_id: id },
            retries: 0,
          })
          return
        }
        throw err
      }
    },
    onMutate: async ({ id }) => {
      const messagesRoot = mailMessagesQueryRoot(!!demoMail)
      const messageRoot = messageDetailQueryRoot(!!demoMail)

      await queryClient.cancelQueries({ queryKey: messagesRoot })

      const previousMessages = queryClient.getQueriesData<PaginatedResponse<ApiMessageSummary>>({
        queryKey: messagesRoot,
      })

      queryClient.setQueriesData<PaginatedResponse<ApiMessageSummary>>(
        { queryKey: messagesRoot },
        (old) => {
          if (!old) return old
          return { ...old, data: old.data.filter((m) => m.id !== id) }
        }
      )

      queryClient.removeQueries({ queryKey: [...messageRoot, id] })

      return { previousMessages }
    },
    onError: (_err, _vars, context) => {
      context?.previousMessages?.forEach(([key, data]) => queryClient.setQueryData(key, data))
    },
    onSettled: () => {
      if (demoMail) {
        useDemoMailStore.getState().bump()
      }
      queryClient.invalidateQueries({ queryKey: mailMessagesQueryRoot(!!demoMail) })
    },
  })
}

export function useToggleStar() {
  const updateFlags = useUpdateFlags()

  return useMutation({
    mutationFn: async ({ id, flags, starred }: { id: string; flags: string[]; starred: boolean }) => {
      const newFlags = mailFlagsWithStarred(flags, !starred)
      return updateFlags.mutateAsync({ id, flags: newFlags })
    },
  })
}

export function useToggleImportant() {
  const updateFlags = useUpdateFlags()

  return useMutation({
    mutationFn: async ({
      id,
      flags,
      important,
    }: {
      id: string
      flags: string[]
      important: boolean
    }) => {
      const newFlags = mailFlagsWithImportant(flags, !important)
      return updateFlags.mutateAsync({ id, flags: newFlags })
    },
  })
}

export function useMarkRead() {
  const updateFlags = useUpdateFlags()

  return useMutation({
    mutationFn: async ({ id, flags }: { id: string; flags: string[] }) => {
      if (mailFlagIsRead(flags)) return
      return updateFlags.mutateAsync({ id, flags: mailFlagsWithRead(flags, true) })
    },
  })
}
