import type { EmailTemplate, FileInfo } from '@inject/graphql/fragment-types'
import type { ResultOf, VariablesOf } from '@inject/graphql/graphql'
import {
  DeleteEmailDraft,
  SetEmailDraft,
} from '@inject/graphql/mutations.client'
import { GetEmailTemplate, GetFileInfo } from '@inject/graphql/queries'
import { GetEmailDraft } from '@inject/graphql/queries.client'
import { useClient } from '@inject/graphql/urql/client'
import notEmpty from '@inject/shared/utils/notEmpty'
import type { Dispatch, SetStateAction } from 'react'
import { useCallback, useState } from 'react'

export interface FormStateInput {
  loadDraft: boolean
  emailThreadId: string | undefined
  senderAddress?: string
  selectedContacts?: string[]
  bccSelectedContacts?: string[]
  subject?: string | undefined
  template?: EmailTemplate | undefined
  activateMilestone?: string
  deactivateMilestone?: string
  content?: string
  fileInfo?: FileInfo | undefined
}

export interface FormState {
  senderAddress: string
  setSenderAddress: Dispatch<SetStateAction<string>>
  selectedContacts: string[]
  setSelectedContacts: Dispatch<SetStateAction<string[]>>
  bccSelectedContacts: string[]
  setBccSelectedContacts: Dispatch<SetStateAction<string[]>>
  subject: string | undefined
  setSubject: Dispatch<SetStateAction<string | undefined>>
  template: EmailTemplate | undefined
  setTemplate: Dispatch<SetStateAction<EmailTemplate | undefined>>
  activateMilestone: string
  setActivateMilestone: Dispatch<SetStateAction<string>>
  deactivateMilestone: string
  setDeactivateMilestone: Dispatch<SetStateAction<string>>
  content: string
  setContent: Dispatch<SetStateAction<string>>
  fileInfo: FileInfo | undefined
  setFileInfo: Dispatch<SetStateAction<FileInfo | undefined>>
  storeDraft: () => void
  discardDraft: () => void
  reload: (input: FormStateInput) => void
}

const useFormState = ({
  teamId,
  inInstructor,
  teamAddress,
}: {
  teamId: string
  inInstructor: boolean
  teamAddress?: string
}): FormState => {
  const [emailThreadId, setEmailThreadId] = useState<string | undefined>()
  const client = useClient()

  const [senderAddress, setSenderAddress] = useState<string>(teamAddress || '')
  const [selectedContacts, setSelectedContacts] = useState<string[]>([])
  const [bccSelectedContacts, setBccSelectedContacts] = useState<string[]>([])
  const [subject, setSubject] = useState<string | undefined>()

  const [template, setTemplate] = useState<EmailTemplate>()
  const [activateMilestone, setActivateMilestone] = useState<string>('')
  const [deactivateMilestone, setDeactivateMilestone] = useState<string>('')

  const [content, setContent] = useState<string>('')
  const [fileInfo, setFileInfo] = useState<FileInfo | undefined>()

  const loadDraftCallback = useCallback(
    async (emailThreadId: string | undefined) => {
      client
        .query<
          ResultOf<typeof GetEmailDraft>,
          VariablesOf<typeof GetEmailDraft>
        >(
          GetEmailDraft,
          {
            teamId,
            instructor: inInstructor,
            emailThreadId,
          },
          { requestPolicy: 'network-only' }
        )
        .toPromise()
        .then(({ data }) => {
          const draft = data?.getEmailDraft
          if (!draft) {
            return
          }

          setContent(draft.content || '')
          setActivateMilestone(draft.activateMilestone || '')
          setDeactivateMilestone(draft.deactivateMilestone || '')

          if (draft.fileId) {
            client
              .query<
                ResultOf<typeof GetFileInfo>,
                VariablesOf<typeof GetFileInfo>
              >(GetFileInfo, {
                fileInfoId: draft.fileId,
              })
              .toPromise()
              .then(({ data }) => {
                setFileInfo(data?.fileInfo || undefined)
              })
          } else {
            setFileInfo(undefined)
          }
          if (draft.templateId) {
            client
              .query<
                ResultOf<typeof GetEmailTemplate>,
                VariablesOf<typeof GetEmailTemplate>
              >(GetEmailTemplate, {
                templateId: draft.templateId,
              })
              .toPromise()
              .then(({ data }) =>
                setTemplate(data?.threadTemplate || undefined)
              )
          } else {
            setTemplate(undefined)
          }

          if (draft.emailThreadId) {
            return
          }

          setSenderAddress(draft.senderAddress || teamAddress || '')
          setSelectedContacts(draft.selectedContacts?.filter(notEmpty) || [])
          setSubject(draft.subject || undefined)
        })
    },
    [client, inInstructor, teamAddress, teamId]
  )

  const reload = useCallback(
    ({
      loadDraft,
      emailThreadId,
      activateMilestone,
      content,
      deactivateMilestone,
      fileInfo,
      selectedContacts,
      bccSelectedContacts,
      senderAddress,
      subject,
      template,
    }: FormStateInput) => {
      setEmailThreadId(emailThreadId)

      setSenderAddress(senderAddress || teamAddress || '')
      setSelectedContacts(selectedContacts || [])
      setBccSelectedContacts(bccSelectedContacts || [])
      setSubject(subject)
      setTemplate(template)
      setActivateMilestone(activateMilestone || '')
      setDeactivateMilestone(deactivateMilestone || '')
      setContent(content || '')
      setFileInfo(fileInfo)

      if (loadDraft) {
        loadDraftCallback(emailThreadId)
      }
    },
    [loadDraftCallback, teamAddress]
  )

  const storeDraft = () => {
    client
      .mutation<unknown, VariablesOf<typeof SetEmailDraft>>(SetEmailDraft, {
        emailDraft: {
          teamId,
          instructor: inInstructor,
          emailThreadId: emailThreadId ?? null,
          senderAddress,
          content,
          activateMilestone,
          deactivateMilestone,
          fileId: fileInfo?.id,
          selectedContacts,
          subject: subject ?? null,
          templateId: template?.id ?? null,
        },
      })
      .toPromise()
  }

  /**
   * Resets the form state to the initial state. If the form was loaded from
   * a draft, the draft will be discarded
   * (currently, that means updating its values to the initial values).
   */
  const discardDraft = () => {
    setSenderAddress(teamAddress || '')
    setContent('')
    setActivateMilestone('')
    setDeactivateMilestone('')
    setFileInfo(undefined)
    setSelectedContacts([])
    setSubject(undefined)
    setTemplate(undefined)

    client
      .mutation<unknown, VariablesOf<typeof DeleteEmailDraft>>(
        DeleteEmailDraft,
        {
          teamId,
          instructor: inInstructor,
          emailThreadId: emailThreadId || null,
        }
      )
      .toPromise()
  }

  return {
    senderAddress,
    setSenderAddress,
    selectedContacts,
    setSelectedContacts,
    bccSelectedContacts,
    setBccSelectedContacts,
    subject,
    setSubject,
    template,
    setTemplate,
    activateMilestone,
    setActivateMilestone,
    deactivateMilestone,
    setDeactivateMilestone,
    content,
    setContent,
    fileInfo,
    setFileInfo,
    storeDraft,
    discardDraft,
    reload,
  }
}

export default useFormState
