<template>
    <div class="w-full">
        <div class="relative border border-slate-200">
            <div
                class="grid items-center gap-6 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide"
                :style="{ gridTemplateColumns: gridCols }"
            >
                <div v-for="col in columns" :key="col.key" class="min-w-0">
                    <slot :name="`header-${col.key}`" :column="col">{{ col.label }}</slot>
                </div>
                <div v-if="showActions" class="min-w-0">
                    <slot name="header-actions">Actions</slot>
                </div>
            </div>

            <div :class="dimRows ? 'opacity-50 transition-opacity' : ''" :aria-busy="loading || undefined">
                <template v-if="paginatedItems.length">
                    <div
                        v-for="(item, index) in paginatedItems"
                        :key="item.id ?? index"
                        class="grid gap-6 px-4 py-3 text-sm border-t border-slate-200"
                        :class="[
                            rowClickable ? 'hover:bg-slate-50 cursor-pointer' : '',
                            rowClass ? rowClass(item) : ''
                        ]"
                        :style="{ gridTemplateColumns: gridCols }"
                        :role="rowClickable ? 'button' : undefined"
                        :tabindex="rowClickable ? 0 : undefined"
                        @click="onRowClick(item)"
                        @keydown.enter="onRowClick(item)"
                        @keydown.space.prevent="onRowClick(item)"
                    >
                        <div v-for="col in columns" :key="col.key" class="min-w-0 truncate">
                            <slot :name="`cell-${col.key}`" :item="item" :column="col">
                                {{ getNestedValue(item, col.key) }}
                            </slot>
                        </div>
                        <div v-if="showActions" @click.stop>
                            <slot name="actions" :item="item" />
                        </div>
                    </div>
                </template>
                <div
                    v-else-if="loading"
                    class="flex items-center justify-center border-t border-slate-200 px-4 py-8 text-primary-500"
                    role="status"
                    aria-live="polite"
                >
                    <UiLoadingDots />
                    <span class="sr-only">Chargement…</span>
                </div>
                <div
                    v-else
                    class="border-t border-slate-200 px-4 py-8 text-center text-sm text-slate-500"
                >
                    <slot name="empty">{{ emptyMessage }}</slot>
                </div>
            </div>

            <div
                v-if="dimRows"
                class="pointer-events-none absolute inset-0 flex items-center justify-center"
                role="status"
                aria-live="polite"
            >
                <div class="rounded bg-white/80 px-4 py-2 text-primary-500 shadow">
                    <UiLoadingDots />
                    <span class="sr-only">Chargement…</span>
                </div>
            </div>
        </div>

        <div v-if="total > 0" class="flex justify-between pt-2">
            <div class="flex items-center gap-3">
                <label :for="perPageId" class="whitespace-nowrap text-sm text-slate-700">Lignes&nbsp;:</label>
                <select
                    :id="perPageId"
                    :value="currentPerPage"
                    class="h-10 rounded border border-slate-300 bg-white px-2 text-sm text-primary-700"
                    @change="onPerPageChange(($event.target as HTMLSelectElement).value)"
                >
                    <option v-for="n in perPageOptions" :key="n" :value="n">{{ n }}</option>
                </select>
            </div>

            <nav aria-label="Pagination" class="flex items-center gap-1">
                <button
                    type="button"
                    class="h-10 rounded border border-primary-500 bg-white px-3 text-sm text-primary-500 hover:bg-primary-500 hover:text-white disabled:cursor-not-allowed disabled:border-slate-300 disabled:text-slate-400 disabled:hover:bg-white disabled:hover:text-slate-400"
                    :disabled="currentPage <= 1"
                    aria-label="Page précédente"
                    @click="goToPage(currentPage - 1)"
                >
                    Précédent
                </button>

                <template v-for="(entry, i) in visiblePages" :key="`${typeof entry}-${entry}-${i}`">
                    <span
                        v-if="entry === '...'"
                        class="px-1 text-sm text-slate-400"
                        aria-hidden="true"
                    >…</span>
                    <button
                        v-else
                        type="button"
                        class="h-10 min-w-[2.5rem] rounded px-2 text-sm transition-colors"
                        :class="entry === currentPage
                            ? 'bg-primary-500 font-semibold text-white'
                            : 'text-slate-700 hover:bg-slate-100'"
                        :aria-current="entry === currentPage ? 'page' : undefined"
                        @click="goToPage(entry)"
                    >
                        {{ entry }}
                    </button>
                </template>

                <button
                    type="button"
                    class="h-10 rounded border border-primary-500 bg-white px-3 text-sm text-primary-500 hover:bg-primary-500 hover:text-white disabled:cursor-not-allowed disabled:border-slate-300 disabled:text-slate-400 disabled:hover:bg-white disabled:hover:text-slate-400"
                    :disabled="currentPage >= totalPages"
                    aria-label="Page suivante"
                    @click="goToPage(currentPage + 1)"
                >
                    Suivant
                </button>
            </nav>
        </div>
    </div>
</template>

<script setup lang="ts" generic="T extends Record<string, any>">
import { computed, useId } from 'vue'

interface Column {
    key: string
    label: string
    width?: string
}

const props = withDefaults(defineProps<{
    columns: Column[]
    items: T[]
    totalItems?: number
    page?: number
    perPage?: number
    perPageOptions?: number[]
    rowClickable?: boolean
    showActions?: boolean
    emptyMessage?: string
    loading?: boolean
    rowClass?: (item: T) => string | undefined
}>(), {
    totalItems: undefined,
    page: 1,
    perPage: 10,
    perPageOptions: () => [10, 25, 50],
    rowClickable: false,
    showActions: false,
    emptyMessage: 'Aucune donnée',
    loading: false,
    rowClass: undefined
})

const emit = defineEmits<{
    (e: 'update:page', value: number): void
    (e: 'update:perPage', value: number): void
    (e: 'row-click', item: T): void
}>()

const perPageId = useId()

const currentPage = computed(() => props.page)
const currentPerPage = computed(() => props.perPage)

const isServerSide = computed(() => props.totalItems !== undefined)
const total = computed(() => props.totalItems ?? props.items.length)

const totalPages = computed(() =>
    Math.max(1, Math.ceil(total.value / currentPerPage.value))
)

const paginatedItems = computed(() => {
    if (isServerSide.value) return props.items
    const start = (currentPage.value - 1) * currentPerPage.value
    return props.items.slice(start, start + currentPerPage.value)
})

const gridCols = computed(() => {
    const dataCols = props.columns.map(c => c.width ?? '1fr').join(' ')
    return props.showActions ? `${dataCols} 60px` : dataCols
})

const dimRows = computed(() => props.loading && paginatedItems.value.length > 0)

const visiblePages = computed<(number | '...')[]>(() => {
    const tp = totalPages.value
    const cp = currentPage.value

    if (tp <= 5) {
        return Array.from({ length: tp }, (_, i) => i + 1)
    }

    const pages: (number | '...')[] = []
    pages.push(1)

    if (cp > 3) pages.push('...')

    const start = Math.max(2, cp - 1)
    const end = Math.min(tp - 1, cp + 1)
    for (let i = start; i <= end; i++) pages.push(i)

    if (cp < tp - 2) pages.push('...')

    if (tp > 1) pages.push(tp)

    return pages
})

const goToPage = (n: number) => {
    if (n < 1 || n > totalPages.value || n === currentPage.value) return
    emit('update:page', n)
}

const onPerPageChange = (value: string) => {
    emit('update:perPage', Number(value))
    emit('update:page', 1)
}

const onRowClick = (item: T) => {
    if (!props.rowClickable) return
    emit('row-click', item)
}

const getNestedValue = (obj: any, path: string): string => {
    const value = path.split('.').reduce((acc, key) => acc?.[key], obj)
    return value ?? '—'
}
</script>
