<template>
  <main
    class="mx-auto flex w-full max-w-6xl flex-col gap-8 px-4 py-8 sm:px-6 lg:px-8"
  >
    <template v-if="!hideHeading">
      <header class="space-y-2">
        <h1 class="text-3xl font-bold text-base-content">{{ headingText }}</h1>
        <p class="text-base text-base-content/70">
          {{ descriptionText }}
        </p>
      </header>
    </template>

    <nav
      v-if="allowCategorySwitch"
      class="tabs tabs-boxed inline-flex"
      role="tablist"
      aria-label="Catégories"
    >
      <button
        v-for="option in categories"
        :key="option.value"
        type="button"
        class="tab"
        :class="{ 'tab-active': option.value === selectedCategory }"
        role="tab"
        :aria-selected="option.value === selectedCategory"
        @click="onCategoryChange(option.value)"
      >
        {{ option.label }}
      </button>
    </nav>

    <DataTable
      :columns="columns"
      :rows="items"
      :loading="loading"
      :sort="currentSort"
      :pagination="paginationState"
      :show-per-page="true"
      row-key="id"
      empty-message="Aucune catégorie trouvée."
      no-results-message="Aucune catégorie ne correspond à votre recherche."
      @sort="handleSort"
      @update:current-page="handlePageChange"
      @update:per-page="handlePerPageChange"
    >
      <template #toolbar>
        <label class="input input-bordered flex items-center gap-2 w-full sm:w-72" :aria-busy="loading">
          <IconLucideSearch class="w-4 h-4" aria-hidden="true" />
          <input
            v-model="searchInput"
            type="search"
            class="grow min-w-0"
            placeholder="Rechercher par nom…"
            autocomplete="off"
          />
        </label>

      </template>

      <template #cell-name="{ row }">
        <span class="font-medium">{{ row.name }}</span>
      </template>

      <template #cell-notes="{ row }">
        <span v-if="row.notes" class="block text-sm text-base-content/80 break-words">{{ row.notes }}</span>
        <span v-else class="text-base-content/50">—</span>
      </template>

      <template #cell-createdAt="{ row }">
        <span class="whitespace-nowrap">{{ formatDate(row.createdAt) }}</span>
      </template>

      <template #cell-actions="{ row }">
        <div class="flex justify-end gap-2">
          <button type="button" class="btn btn-ghost btn-xs" @click="openRelatedModal(row)">
            Liés
          </button>
          <button type="button" class="btn btn-ghost btn-xs" @click="openEditPage(row)">
            Éditer
          </button>
          <button v-if="canEdit" type="button" class="btn btn-ghost btn-xs text-error" @click="confirmDelete(row)">
            Supprimer
          </button>
        </div>
      </template>
    </DataTable>

    <RelatedItemsModal
      :open="relatedModalOpen"
      :model-type="relatedType"
      @close="relatedModalOpen = false"
      @open-edit="openRelatedEdit"
    />
  </main>
</template>

<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch, type Ref } from 'vue'
import { useHead, useRouter } from '#imports'
import DataTable from '~/components/common/DataTable.vue'
import { useUrlState } from '~/composables/useUrlState'
import type { DataTableSort } from '~/shared/types/dataTable'
import {
  deleteModelType,
  listModelTypes,
  type ModelCategory,
  type ModelType,
  type ModelTypeListResponse,
} from '~/services/modelTypes'
import { useToast } from '~/composables/useToast'
import { humanizeError } from '~/shared/utils/errorMessages'
import { invalidateEntityTypeCache } from '~/composables/useEntityTypes'
import IconLucideSearch from '~icons/lucide/search'
import { formatFrenchDate } from '~/utils/date'

const DEFAULT_DESCRIPTION
  = 'Gérez les catégories utilisées pour structurer les catalogues de composants, de pièces et de produits. Ajoutez, modifiez ou supprimez des entrées avec tri, recherche et pagination.'

const props = withDefaults(
  defineProps<{
    category: ModelCategory
    heading: string
    description?: string
    allowCategorySwitch?: boolean
    hideHeading?: boolean
  }>(),
  {
    allowCategorySwitch: false,
    hideHeading: false,
  },
)

const selectedCategory = ref<ModelCategory>(props.category)
const searchInput = ref('')

// State synced with URL query params
const urlState = useUrlState({
  q: { default: '' },
  sort: { default: 'name' },
  dir: { default: 'asc' },
  limit: { default: 20, type: 'number' },
  offset: { default: 0, type: 'number' },
}, {
  onRestore: () => {
    searchInput.value = urlState.q.value
    doRefresh()
  },
})
const searchTerm = urlState.q
const sort = urlState.sort as Ref<'name' | 'createdAt'>
const dir = urlState.dir as Ref<'asc' | 'desc'>
const limit = urlState.limit
const offset = urlState.offset

// Initialize searchInput from URL
searchInput.value = searchTerm.value

const items = ref<ModelType[]>([])
const total = ref(0)
const loading = ref(false)

let debounceTimer: ReturnType<typeof setTimeout> | null = null
let activeController: AbortController | null = null

const router = useRouter()
const { showError, showSuccess } = useToast()
const { canEdit } = usePermissions()

const headingText = computed(() => props.heading)
const descriptionText = computed(() => props.description ?? DEFAULT_DESCRIPTION)
const allowCategorySwitch = computed(() => props.allowCategorySwitch ?? false)

useHead(() => ({ title: headingText.value }))

const columns = [
  { key: 'name', label: 'Nom', sortable: true },
  { key: 'notes', label: 'Notes' },
  { key: 'createdAt', label: 'Date', sortable: true },
  { key: 'actions', label: 'Actions', align: 'right' as const, width: 'w-48' },
]

const formatDate = formatFrenchDate

const categories: Array<{ label: string, value: ModelCategory }> = [
  { label: 'Composants', value: 'COMPONENT' },
  { label: 'Pièces', value: 'PIECE' },
  { label: 'Produits', value: 'PRODUCT' },
]

// Sort state for DataTable
const currentSort = computed<DataTableSort>(() => ({
  field: sort.value,
  direction: dir.value,
}))

const handleSort = (newSort: DataTableSort) => {
  sort.value = newSort.field as 'name' | 'createdAt'
  dir.value = newSort.direction as 'asc' | 'desc'
  offset.value = 0
  doRefresh()
}

// Pagination: convert offset/limit to page-based for DataTable
const currentPage = computed(() => {
  if (limit.value <= 0) return 1
  return Math.floor(offset.value / limit.value) + 1
})

const totalPages = computed(() => {
  if (limit.value <= 0) return 1
  return Math.max(1, Math.ceil(total.value / limit.value))
})

const paginationState = computed(() => ({
  currentPage: currentPage.value,
  totalPages: totalPages.value,
  totalItems: total.value,
  pageItems: items.value.length,
  perPageOptions: [20, 50, 100],
  perPage: limit.value,
}))

const handlePageChange = (page: number) => {
  offset.value = (page - 1) * limit.value
  doRefresh()
}

const handlePerPageChange = (perPage: number) => {
  limit.value = perPage
  offset.value = 0
  doRefresh()
}

const extractErrorMessage = (error: unknown): string => {
  let raw: string | null = null
  if (error && typeof error === 'object') {
    const maybeFetchError = error as {
      data?: Record<string, unknown>
      statusMessage?: string
      message?: string
    }
    if (maybeFetchError.data) {
      const data = maybeFetchError.data
      if (typeof data['hydra:description'] === 'string') raw = data['hydra:description']
      else if (typeof data.detail === 'string') raw = data.detail
      else if (typeof data.message === 'string') raw = data.message
      else if (Array.isArray(data.message) && data.message.length > 0) raw = data.message[0]
      else if (typeof data.error === 'string') raw = data.error
    }
    if (!raw && typeof maybeFetchError.statusMessage === 'string') raw = maybeFetchError.statusMessage
    if (!raw && typeof maybeFetchError.message === 'string') raw = maybeFetchError.message
  }
  return humanizeError(raw)
}

const doRefresh = async ({ resetOffset = false }: { resetOffset?: boolean } = {}) => {
  if (resetOffset) offset.value = 0

  if (activeController) activeController.abort()
  const controller = new AbortController()
  activeController = controller
  loading.value = true

  try {
    const response: ModelTypeListResponse = await listModelTypes(
      {
        q: searchTerm.value || undefined,
        category: selectedCategory.value,
        sort: sort.value,
        dir: dir.value,
        limit: limit.value,
        offset: offset.value,
      },
      { signal: controller.signal },
    )
    items.value = response.items
    total.value = response.total
    offset.value = response.offset
    limit.value = response.limit
  }
  catch (error: unknown) {
    // Requête annulée volontairement (nouvelle recherche / démontage) : pas une
    // vraie erreur. On teste le signal car ofetch encapsule l'AbortError dans
    // une FetchError, donc error.name n'est pas fiable.
    if (controller.signal.aborted) return
    showError(extractErrorMessage(error))
  }
  finally {
    if (activeController === controller) {
      loading.value = false
      activeController = null
    }
  }
}

watch(
  () => props.category,
  (value) => {
    if (value !== selectedCategory.value) {
      selectedCategory.value = value
      doRefresh({ resetOffset: true })
    }
  },
)

const onCategoryChange = (value: ModelCategory) => {
  if (!props.allowCategorySwitch) return
  if (selectedCategory.value !== value) {
    selectedCategory.value = value
    doRefresh({ resetOffset: true })
  }
}

const resolveCategoryBasePath = (category: ModelCategory) => {
  if (category === 'COMPONENT') return '/component-category'
  if (category === 'PIECE') return '/piece-category'
  return '/product-category'
}

const openEditPage = (item: ModelType) => {
  const category = item.category ?? selectedCategory.value
  const basePath = resolveCategoryBasePath(category)
  router.push(`${basePath}/${item.id}/edit`).catch(() => {
    showError("Navigation impossible vers la page d'édition.")
  })
}

const { confirm } = useConfirm()

const confirmDelete = async (item: ModelType) => {
  const confirmed = await confirm({
    message: 'Supprimer ce type ? Cette action est irréversible.',
  })
  if (!confirmed) return

  try {
    await deleteModelType(item.id)
    invalidateEntityTypeCache(item.category)
    showSuccess(`Type « ${item.name} » supprimé avec succès.`)
    if (items.value.length === 1 && offset.value >= limit.value) {
      offset.value = Math.max(0, offset.value - limit.value)
    }
    await doRefresh()
  }
  catch (error) {
    showError(extractErrorMessage(error))
  }
}

const relatedModalOpen = ref(false)
const relatedType = ref<ModelType | null>(null)

const resolveRelatedEditBasePath = (category: ModelCategory) => {
  if (category === 'COMPONENT') return '/component'
  if (category === 'PIECE') return '/pieces'
  return '/product'
}

const openRelatedModal = (item: ModelType) => {
  relatedType.value = item
  relatedModalOpen.value = true
}

const openRelatedEdit = (entry: { id: string }) => {
  const current = relatedType.value
  if (!current) return
  const basePath = resolveRelatedEditBasePath(current.category)
  relatedModalOpen.value = false
  router.push(`${basePath}/${entry.id}/edit`).catch(() => {
    showError("Navigation impossible vers la fiche d'édition.")
  })
}

watch(
  () => searchInput.value,
  (value) => {
    if (debounceTimer) clearTimeout(debounceTimer)
    debounceTimer = setTimeout(() => {
      searchTerm.value = value.trim()
      doRefresh({ resetOffset: true })
    }, 300)
  },
)

onMounted(() => {
  doRefresh()
})

onBeforeUnmount(() => {
  if (debounceTimer) clearTimeout(debounceTimer)
  if (activeController) activeController.abort()
})
</script>
