<!-- Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/ -->

<script setup lang="ts">
import { useWindowSize } from '@vueuse/core'
import { isEqual } from 'lodash-es'
import { storeToRefs } from 'pinia'
import {
  computed,
  onActivated,
  onDeactivated,
  ref,
  type Ref,
  toRef,
  useTemplateRef,
  watch,
} from 'vue'
import { onBeforeRouteLeave, onBeforeRouteUpdate, useRouter } from 'vue-router'

import { useSorting } from '#shared/composables/list/useSorting.ts'
import { usePagination } from '#shared/composables/usePagination.ts'
import { useQueryPolling } from '#shared/composables/useQueryPolling.ts'
import type { TicketById } from '#shared/entities/ticket/types.ts'
import {
  EnumObjectManagerObjects,
  EnumOrderDirection,
  type TicketsCachedByOverviewQueryVariables,
} from '#shared/graphql/types.ts'
import { getIdFromGraphQLId } from '#shared/graphql/utils.ts'
import { QueryHandler } from '#shared/server/apollo/handler/index.ts'
import { useApplicationStore } from '#shared/stores/application.ts'
import { useSessionStore } from '#shared/stores/session.ts'
import type { ObjectWithId } from '#shared/types/utils.ts'
import hasPermission from '#shared/utils/hasPermission.ts'
import { edgesToArray } from '#shared/utils/helpers.ts'

import CommonButton from '#desktop/components/CommonButton/CommonButton.vue'
import CommonAdvancedTable from '#desktop/components/CommonTable/CommonAdvancedTable.vue'
import CommonTableSkeleton from '#desktop/components/CommonTable/Skeleton/CommonTableSkeleton.vue'
import CommonTicketPriorityIndicatorIcon from '#desktop/components/CommonTicketPriorityIndicator/CommonTicketPriorityIndicatorIcon.vue'
import CommonTicketStateIndicatorIcon from '#desktop/components/CommonTicketStateIndicator/CommonTicketStateIndicatorIcon.vue'
import { useElementScroll } from '#desktop/composables/useElementScroll.ts'
import { useScrollPosition } from '#desktop/composables/useScrollPosition.ts'
import { useTicketsCachedByOverviewCache } from '#desktop/entities/ticket/composables/useTicketsCachedByOverviewCache.ts'
import { useTicketsCachedByOverviewQuery } from '#desktop/entities/ticket/graphql/queries/ticketsCachedByOverview.api.ts'
import { useTicketOverviewsStore } from '#desktop/entities/ticket/stores/ticketOverviews.ts'
import { useLifetimeCustomerTicketsCount } from '#desktop/entities/user/current/composables/useLifetimeCustomerTicketsCount.ts'
import TicketOverviewsEmptyText from '#desktop/pages/ticket-overviews/components/TicketOverviewsEmptyText.vue'

interface Props {
  overviewId: string
  orderBy: string
  orderDirection: EnumOrderDirection
  headers: string[]
  overviewName: string
  groupBy?: string
  overviewCount?: number
}

const props = defineProps<Props>()

const router = useRouter()

const { readTicketsByOverviewCache, forceTicketsByOverviewCacheOnlyFirstPage } =
  useTicketsCachedByOverviewCache()

const { queryPollingConfig } = storeToRefs(useTicketOverviewsStore())

let lastFirstPageCollectionSignature: string
const foreground = ref(true)
const pollingInterval = computed(
  () =>
    (foreground.value
      ? queryPollingConfig.value.foreground.interval_sec
      : queryPollingConfig.value.background.interval_sec) * 1000,
)

const ticketsQueryVariables = computed<TicketsCachedByOverviewQueryVariables>(
  (currentVariables) => {
    const newVariables: TicketsCachedByOverviewQueryVariables = {
      pageSize: queryPollingConfig.value.page_size,
      overviewId: props.overviewId,
      orderBy: props.orderBy,
      orderDirection: props.orderDirection,
      cacheTtl: queryPollingConfig.value.foreground.cache_ttl_sec,
      renewCache: currentVariables?.overviewId !== props.overviewId,
    }

    const cachedTickets = readTicketsByOverviewCache(newVariables)

    newVariables.knownCollectionSignature =
      cachedTickets?.ticketsCachedByOverview?.collectionSignature

    if (currentVariables && isEqual(currentVariables, newVariables)) {
      return currentVariables
    }

    return newVariables
  },
)

const ticketsQuery = new QueryHandler(
  useTicketsCachedByOverviewQuery(ticketsQueryVariables, {
    fetchPolicy: 'cache-and-network',
    nextFetchPolicy: 'cache-and-network',
    context: {
      batch: {
        active: false,
      },
    },
  }),
)

const ticketsResult = ticketsQuery.result()
const loading = ticketsQuery.loading()

const currentCollectionSignature = computed(() => {
  return ticketsResult.value?.ticketsCachedByOverview?.collectionSignature
})

const isLoadingTickets = computed(() => {
  if (ticketsResult.value !== undefined) return false

  return loading.value
})

const tickets = computed(() =>
  edgesToArray(ticketsResult.value?.ticketsCachedByOverview),
)

onActivated(() => {
  if (foreground.value) return

  ticketsQuery.refetch({
    renewCache: true,
  })
  foreground.value = true
})

onDeactivated(() => {
  foreground.value = false
})

const pagination = usePagination(
  ticketsQuery,
  'ticketsCachedByOverview',
  queryPollingConfig.value.page_size,
  () => ({
    knownCollectionSignature: null,
    renewCache: false,
  }),
)

const scrollContainerElement = useTemplateRef('scroll-container')

const {
  sort,
  orderBy: localOrderBy,
  orderDirection: localOrderDirection,
  isSorting,
} = useSorting(
  ticketsQuery,
  toRef(props, 'orderBy'),
  toRef(props, 'orderDirection'),
  scrollContainerElement,
)

const resort = (column: string, direction: EnumOrderDirection) => {
  forceTicketsByOverviewCacheOnlyFirstPage(
    {
      ...ticketsQueryVariables.value,
      orderBy: localOrderBy.value,
      orderDirection: localOrderDirection.value,
    },
    lastFirstPageCollectionSignature,
    queryPollingConfig.value.page_size,
  )

  const cachedTickets = readTicketsByOverviewCache({
    ...ticketsQueryVariables.value,
    orderBy: column,
    orderDirection: direction,
  })

  sort(column, direction, {
    knownCollectionSignature:
      cachedTickets?.ticketsCachedByOverview?.collectionSignature,
    renewCache: false,
  })
}

const { startPolling, stopPolling } = useQueryPolling(
  ticketsQuery,
  pollingInterval,
  () => ({
    knownCollectionSignature: currentCollectionSignature.value,
    renewCache: false,
    pageSize: queryPollingConfig.value.page_size * pagination.currentPage,
  }),
  () => ({
    enabled: queryPollingConfig.value.enabled && !isSorting.value,
  }),
)

ticketsQuery.watchOnceOnResult((result) => {
  if (!queryPollingConfig.value.enabled) return

  lastFirstPageCollectionSignature =
    result.ticketsCachedByOverview.collectionSignature

  startPolling()
})

onBeforeRouteLeave(() => {
  forceTicketsByOverviewCacheOnlyFirstPage(
    ticketsQueryVariables.value,
    lastFirstPageCollectionSignature,
    queryPollingConfig.value.page_size,
  )
})

watch(
  () => props.overviewId,
  () => {
    ticketsQuery.watchOnceOnResult((result) => {
      if (!queryPollingConfig.value.enabled) return

      lastFirstPageCollectionSignature =
        result.ticketsCachedByOverview.collectionSignature

      startPolling()
    })
  },
)

onBeforeRouteUpdate(() => {
  forceTicketsByOverviewCacheOnlyFirstPage(
    ticketsQueryVariables.value,
    lastFirstPageCollectionSignature,
    queryPollingConfig.value.page_size,
  )

  if (!queryPollingConfig.value.enabled) return

  stopPolling()
})

ticketsQuery.onResult((result) => {
  if (isSorting.value && !result.loading) {
    // If sorting comes from the cache, we immediately dispose the loading state
    isSorting.value = false
  }
})

const totalCount = computed(
  () => ticketsResult.value?.ticketsCachedByOverview.totalCount || 0,
)

const loadMore = async () => pagination.fetchNextPage()

const { config } = storeToRefs(useApplicationStore())
const { user, userId } = storeToRefs(useSessionStore())

const storageKeyId = computed(
  () => `${userId.value}-table-headers-${props.overviewId}`,
)

// Scrolling position is preserved when user visits another page and returns to overview page
const { scrollPosition, restoreScrollPosition } = useScrollPosition(
  scrollContainerElement,
)

const resetScrollPosition = () => {
  scrollPosition.value = 0
  restoreScrollPosition()
}

// Reset scroll-position back to the start, when user navigates between overviews
onBeforeRouteUpdate(resetScrollPosition)

const { reachedTop } = useElementScroll(
  scrollContainerElement as Ref<HTMLDivElement>,
)

const { hasAnyTicket } = useLifetimeCustomerTicketsCount()

const isCustomerAndCanCreateTickets = computed(
  () =>
    hasPermission('ticket.customer', user.value?.permissions?.names ?? []) &&
    config.value.customer_ticket_create,
)

const goToTicket = (ticket: ObjectWithId) =>
  router.push(`/tickets/${getIdFromGraphQLId(ticket.id)}`)

const goToTicketLinkColumn = {
  internal: true,
  getLink: (item: ObjectWithId) => `/tickets/${getIdFromGraphQLId(item.id)}`,
}

const localHeaders = computed(() => {
  const extendedHeaders = [...props.headers]

  extendedHeaders.unshift('stateIcon')

  if (config.value.ui_ticket_priority_icons) {
    extendedHeaders.unshift('priorityIcon')
  }

  return extendedHeaders
})

const maxItems = computed(() => config.value.ui_ticket_overview_ticket_limit)

const { height: screenHeight } = useWindowSize()

const visibleOverviewCount = computed(() => {
  const maxVisibleRowCount = Math.ceil(screenHeight.value / 40)

  if (props.overviewCount && props.overviewCount > maxVisibleRowCount)
    return maxVisibleRowCount

  return props.overviewCount
})
</script>

<template>
  <div
    ref="scroll-container"
    class="overflow-y-auto focus-visible:outline-none"
  >
    <div v-if="isLoadingTickets && !pagination.loadingNewPage">
      <CommonTableSkeleton
        data-test-id="table-skeleton"
        :rows="visibleOverviewCount"
      />
    </div>

    <template v-else-if="!isLoadingTickets && !tickets.length">
      <TicketOverviewsEmptyText
        v-if="isCustomerAndCanCreateTickets && !hasAnyTicket"
        class="space-y-2.5"
        :title="$t('Welcome!')"
      >
        <CommonLabel class="block" tag="p">{{
          $t('You have not created a ticket yet.')
        }}</CommonLabel>
        <CommonLabel class="block" tag="p">{{
          $t('The way to communicate with us is this thing called "ticket".')
        }}</CommonLabel>
        <CommonLabel class="block" tag="p">{{
          $t('Please click on the button below to create your first one.')
        }}</CommonLabel>
        <CommonButton
          size="large"
          class="mx-auto !mt-8"
          variant="primary"
          @click="router.push({ name: 'TicketCreate' })"
          >{{ $t('Create your first ticket') }}
        </CommonButton>
      </TicketOverviewsEmptyText>

      <TicketOverviewsEmptyText
        v-else
        :title="$t('Empty Overview')"
        :text="$t('No tickets in this state.')"
        with-illustration
      />
    </template>

    <div v-else-if="tickets.length">
      <CommonAdvancedTable
        v-model:order-by="localOrderBy"
        v-model:order-direction="localOrderDirection"
        :caption="$t('Overview: %s', overviewName)"
        :headers="localHeaders"
        :object="EnumObjectManagerObjects.Ticket"
        :group-by="groupBy"
        :reached-scroll-top="reachedTop"
        :table-id="overviewId"
        :scroll-container="scrollContainerElement"
        :attributes="[
          {
            name: 'priorityIcon',
            label: __('Priority Icon'),
            headerPreferences: {
              noResize: true,
              hideLabel: true,
              displayWidth: 25,
            },
            columnPreferences: {},
            dataType: 'icon',
          },
          {
            name: 'stateIcon',
            label: __('State Icon'),
            headerPreferences: {
              noResize: true,
              hideLabel: true,
              displayWidth: 30,
            },
            columnPreferences: {},
            dataType: 'icon',
          },
        ]"
        :attribute-extensions="{
          title: {
            columnPreferences: {
              link: goToTicketLinkColumn,
            },
          },
          number: {
            label: config.ticket_hook,
            columnPreferences: {
              link: goToTicketLinkColumn,
            },
          },
        }"
        :items="tickets"
        :total-items="totalCount"
        :storage-key-id="storageKeyId"
        :max-items="maxItems"
        :is-sorting="isSorting"
        :is-loading="loading"
        @load-more="loadMore"
        @click-row="goToTicket"
        @sort="resort"
      >
        <template #column-cell-priorityIcon="{ item, isRowSelected }">
          <CommonTicketPriorityIndicatorIcon
            :ui-color="(item as TicketById).priority?.uiColor"
            with-text-color
            class="shrink-0 group-hover:text-black group-focus-visible:text-white group-active:text-white group-hover:dark:text-white group-active:dark:text-white"
            :class="{
              'ltr:text-black rtl:text-black dark:text-white': isRowSelected,
            }"
          />
        </template>
        <template #column-cell-stateIcon="{ item, isRowSelected }">
          <CommonTicketStateIndicatorIcon
            class="shrink-0 group-hover:text-black group-focus-visible:text-white group-active:text-white group-hover:dark:text-white group-active:dark:text-white"
            :class="{
              'ltr:text-black rtl:text-black dark:text-white': isRowSelected,
            }"
            :color-code="(item as TicketById).stateColorCode"
            :label="(item as TicketById).state.name"
            :aria-labelledby="(item as TicketById).id"
            icon-size="tiny"
          />
        </template>
      </CommonAdvancedTable>
    </div>
  </div>
</template>
