import { ref, type Ref } from 'vue'
import { defineStore } from 'pinia'

export interface ChildElement {
  id: number
  title: string
  description: string
}

export interface Element extends ChildElement {
  iconImageUrl: string | null
  richText: string
  shortPdfPath: string | null
  longPdfPath: string | null
  youtubeUrl: string | null
}

type ElementFileType = 'iconImage' | 'shortPdf' | 'longPdf'

interface ElementResponse {
  element: Element
  childElements: ChildElement[]
  parentElement: ChildElement | null
  siblingElements: ChildElement[]
}

export interface UpdateElementInput {
  title: string
  description: string
  richText: string
  youtubeUrl: string
}

export interface CreateChildElementInput {
  title: string
}

interface UpdateElementResponse {
  element: Element
}

interface ReorderChildElementsResponse {
  childElements: ChildElement[]
}

interface ElementPatchInput {
  title?: string
  description?: string
  iconImageUrl?: string
  richText?: string
  shortPdfPath?: string
  longPdfPath?: string
  youtubeUrl?: string
}

interface ErrorResponse {
  error?: string
}

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string
const ELEMENTS_URL = `${API_BASE_URL}/api/elements`
const ELEMENT_UPDATE_URL = `${API_BASE_URL}/api/element/update`

export const useElementsStore = defineStore('elements', () => {
  const element = ref<Element | null>(null)
  const childElements = ref<ChildElement[]>([])
  const parentElement = ref<ChildElement | null>(null)
  const siblingElements = ref<ChildElement[]>([])
  const isLoading = ref(false)
  const error = ref<string | null>(null)
  const isSaving = ref(false)
  const isUploadingIconImage = ref(false)
  const isUploadingShortPdf = ref(false)
  const isUploadingLongPdf = ref(false)
  const isManagingChildren = ref(false)
  const saveError = ref<string | null>(null)
  const childActionError = ref<string | null>(null)

  async function fetchElement(elementId: string): Promise<void> {
    element.value = null
    childElements.value = []
    parentElement.value = null
    siblingElements.value = []
    error.value = null
    saveError.value = null
    childActionError.value = null
    isLoading.value = true

    try {
      const encodedElementId = encodeURIComponent(elementId)
      const elementUrl = `${API_BASE_URL}/api/elements/${encodedElementId}`
      const response = await fetch(elementUrl)

      if (response.status === 404) {
        error.value = 'Element not found'
        return
      }

      if (!response.ok) {
        error.value = 'Could not load element'
        return
      }

      const data: ElementResponse = await response.json()
      element.value = data.element
      childElements.value = data.childElements
      parentElement.value = data.parentElement
      siblingElements.value = data.siblingElements
    } catch {
      childElements.value = []
      parentElement.value = null
      siblingElements.value = []
      error.value = 'Network error - could not load element'
    } finally {
      isLoading.value = false
    }
  }

  async function updateElement(
    elementId: string,
    input: UpdateElementInput,
  ): Promise<boolean> {
    return await saveElementPatch(elementId, input, 'Could not save element')
  }

  async function createChildElement(
    parentElementId: string,
    input: CreateChildElementInput,
  ): Promise<Element | null> {
    childActionError.value = null
    isManagingChildren.value = true

    try {
      const encodedParentElementId = encodeURIComponent(parentElementId)
      const response = await fetch(
        `${ELEMENTS_URL}/${encodedParentElementId}/children`,
        {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          credentials: 'include',
          body: JSON.stringify(input),
        },
      )

      return await handleChildElementResponse(
        response,
        'Could not add child element',
      )
    } catch {
      childActionError.value = 'Network error - could not add child element'
      return null
    } finally {
      isManagingChildren.value = false
    }
  }

  async function reorderChildElements(
    parentElementId: string,
    childElementIds: number[],
  ): Promise<boolean> {
    childActionError.value = null
    isManagingChildren.value = true

    try {
      const encodedParentElementId = encodeURIComponent(parentElementId)
      const response = await fetch(
        `${ELEMENTS_URL}/${encodedParentElementId}/children/order`,
        {
          method: 'PUT',
          headers: { 'Content-Type': 'application/json' },
          credentials: 'include',
          body: JSON.stringify({ childElementIds }),
        },
      )

      return await handleReorderChildElementsResponse(response)
    } catch {
      childActionError.value = 'Network error - could not save child order'
      return false
    } finally {
      isManagingChildren.value = false
    }
  }

  async function deleteChildElement(
    parentElementId: string,
    childElementId: number,
  ): Promise<boolean> {
    childActionError.value = null
    isManagingChildren.value = true

    try {
      const encodedParentElementId = encodeURIComponent(parentElementId)
      const encodedChildElementId = encodeURIComponent(
        childElementId.toString(),
      )
      const response = await fetch(
        `${ELEMENTS_URL}/${encodedParentElementId}` +
          `/children/${encodedChildElementId}`,
        {
          method: 'DELETE',
          credentials: 'include',
        },
      )

      return await handleDeleteChildElementResponse(response, childElementId)
    } catch {
      childActionError.value = 'Network error - could not remove child element'
      return false
    } finally {
      isManagingChildren.value = false
    }
  }

  async function clearElementIconImage(elementId: string): Promise<boolean> {
    return await saveElementPatch(
      elementId,
      { iconImageUrl: '' },
      'Could not remove icon image',
    )
  }

  async function clearElementShortPdf(elementId: string): Promise<boolean> {
    return await saveElementPatch(
      elementId,
      { shortPdfPath: '' },
      'Could not remove short PDF',
    )
  }

  async function clearElementLongPdf(elementId: string): Promise<boolean> {
    return await saveElementPatch(
      elementId,
      { longPdfPath: '' },
      'Could not remove long PDF',
    )
  }

  async function saveElementPatch(
    elementId: string,
    input: ElementPatchInput,
    failureMessage: string,
  ): Promise<boolean> {
    saveError.value = null
    isSaving.value = true

    try {
      const response = await fetch(ELEMENT_UPDATE_URL, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'include',
        body: JSON.stringify({ elementId, ...input }),
      })

      return await handleElementResponse(response, failureMessage)
    } catch {
      saveError.value = `Network error - ${failureMessage.toLowerCase()}`
      return false
    } finally {
      isSaving.value = false
    }
  }

  async function uploadElementIconImage(
    elementId: string,
    file: File,
  ): Promise<boolean> {
    return await uploadElementFile(
      elementId,
      file,
      'iconImage',
      isUploadingIconImage,
      'Could not upload icon image',
      'Network error - could not upload icon image',
    )
  }

  async function uploadElementShortPdf(
    elementId: string,
    file: File,
  ): Promise<boolean> {
    return await uploadElementFile(
      elementId,
      file,
      'shortPdf',
      isUploadingShortPdf,
      'Could not upload short PDF',
      'Network error - could not upload short PDF',
    )
  }

  async function uploadElementLongPdf(
    elementId: string,
    file: File,
  ): Promise<boolean> {
    return await uploadElementFile(
      elementId,
      file,
      'longPdf',
      isUploadingLongPdf,
      'Could not upload long PDF',
      'Network error - could not upload long PDF',
    )
  }

  async function uploadElementFile(
    elementId: string,
    file: File,
    fileType: ElementFileType,
    isUploadingFile: Ref<boolean>,
    failureMessage: string,
    networkErrorMessage: string,
  ): Promise<boolean> {
    saveError.value = null
    isUploadingFile.value = true

    try {
      const formData = new FormData()
      formData.append('elementId', elementId)
      formData.append('fileType', fileType)
      formData.append('file', file)
      const response = await fetch(ELEMENT_UPDATE_URL, {
        method: 'POST',
        headers: { Accept: 'application/json' },
        credentials: 'include',
        body: formData,
      })

      return await handleElementResponse(response, failureMessage)
    } catch {
      saveError.value = networkErrorMessage
      return false
    } finally {
      isUploadingFile.value = false
    }
  }

  async function handleElementResponse(
    response: Response,
    failureMessage: string,
  ): Promise<boolean> {
    if (response.status === 401) {
      saveError.value = 'Please log in again'
      return false
    }

    if (response.status === 404) {
      saveError.value = 'Element not found'
      return false
    }

    if (response.status === 400) {
      saveError.value = await errorMessage(response, failureMessage)
      return false
    }

    if (!response.ok) {
      saveError.value = failureMessage
      return false
    }

    const data: UpdateElementResponse = await response.json()
    element.value = data.element
    return true
  }

  async function handleChildElementResponse(
    response: Response,
    failureMessage: string,
  ): Promise<Element | null> {
    if (response.status === 401) {
      childActionError.value = 'Please log in again'
      return null
    }

    if (response.status === 400 || response.status === 404) {
      childActionError.value = await errorMessage(response, failureMessage)
      return null
    }

    if (!response.ok) {
      childActionError.value = failureMessage
      return null
    }

    const data: UpdateElementResponse = await response.json()
    return data.element
  }

  async function handleReorderChildElementsResponse(
    response: Response,
  ): Promise<boolean> {
    if (response.status === 401) {
      childActionError.value = 'Please log in again'
      return false
    }

    if (response.status === 400 || response.status === 404) {
      childActionError.value = await errorMessage(
        response,
        'Could not save child order',
      )
      return false
    }

    if (!response.ok) {
      childActionError.value = 'Could not save child order'
      return false
    }

    const data: ReorderChildElementsResponse = await response.json()
    childElements.value = data.childElements
    return true
  }

  async function handleDeleteChildElementResponse(
    response: Response,
    childElementId: number,
  ): Promise<boolean> {
    if (response.status === 401) {
      childActionError.value = 'Please log in again'
      return false
    }

    if (response.status === 400 || response.status === 404) {
      childActionError.value = await errorMessage(
        response,
        'Could not remove child element',
      )
      return false
    }

    if (!response.ok) {
      childActionError.value = 'Could not remove child element'
      return false
    }

    childElements.value = childElements.value.filter((childElement) => {
      return childElement.id !== childElementId
    })
    return true
  }

  async function errorMessage(
    response: Response,
    fallbackMessage: string,
  ): Promise<string> {
    try {
      const data: ErrorResponse = await response.json()
      return data.error ?? fallbackMessage
    } catch {
      return fallbackMessage
    }
  }

  return {
    element,
    childElements,
    parentElement,
    siblingElements,
    isLoading,
    error,
    isSaving,
    isUploadingIconImage,
    isUploadingShortPdf,
    isUploadingLongPdf,
    isManagingChildren,
    saveError,
    childActionError,
    fetchElement,
    updateElement,
    createChildElement,
    reorderChildElements,
    deleteChildElement,
    uploadElementIconImage,
    uploadElementShortPdf,
    uploadElementLongPdf,
    clearElementIconImage,
    clearElementShortPdf,
    clearElementLongPdf,
  }
})
