<script setup lang="ts" generic="TData, TValue">
import { Link } from '@inertiajs/vue3';
import type { ColumnDef } from '@tanstack/vue-table';
import { FlexRender, getCoreRowModel, useVueTable } from '@tanstack/vue-table';
import { computed, provide } from 'vue';
import DataTableToolbar from '@/components/data-table/DataTableToolbar.vue';
import { Button } from '@/components/ui/button';
import {
    Table,
    TableBody,
    TableCell,
    TableHead,
    TableHeader,
    TableRow,
} from '@/components/ui/table';
import TableEmpty from '@/components/ui/table/TableEmpty.vue';
import { buildPaginationSummary } from '@/lib/grouped-table';
import type {
    DataTableFilterDef,
    DataTablePagination,
    DataTablePaginationLink,
    DataTableSort,
} from '@/types/data-table';
import DataTableEmpty from './DataTableEmpty.vue';

const props = withDefaults(
    defineProps<{
        columns: ColumnDef<TData, TValue>[];
        data: TData[];
        pagination?: DataTablePagination;
        sort?: DataTableSort | null;
        filterDefs?: DataTableFilterDef[];
        filterValues?: Record<string, string>;
        searchPlaceholder?: string;
        paginationLinks?: DataTablePaginationLink[];
        showRowNumber?: boolean;
        paginationDisplayedCount?: number;
        paginationItemLabel?: string;
        getRowClassName?: (row: TData, index: number) => string | undefined;
        loading?: boolean;
    }>(),
    {
        searchPlaceholder: 'Ketikkan sesuatu...',
        showRowNumber: true,
        paginationItemLabel: 'data',
        loading: false,
    }
);

const search = defineModel<string>('search', { default: '' });

const emit = defineEmits<{
    'sort-change': [sort: DataTableSort | null];
    'filter-change': [key: string, value: string];
    'filters-reset': [];
}>();

const ROW_NUMBER_COLUMN_CLASS = 'w-12 max-w-12 shrink-0 text-center';

const rowNumberColumn: ColumnDef<TData> = {
    id: '_row_number',
    header: 'No.',
    size: 48,
    enableSorting: false,
    enableHiding: false,
    cell: ({ row }) => {
        const offset = props.pagination
            ? (props.pagination.currentPage - 1) * props.pagination.perPage
            : 0;

        return offset + row.index + 1;
    },
};

const resolvedColumns = computed(() => (
    props.showRowNumber ? [rowNumberColumn, ...props.columns] : props.columns
));

const table = useVueTable({
    get data() {
        return props.data;
    },
    get columns() {
        return resolvedColumns.value;
    },
    getCoreRowModel: getCoreRowModel(),
    manualSorting: true,
});

function handleSort(column: string): void {
    const current = props.sort;

    if (current?.column !== column) {
        emit('sort-change', { column, direction: 'asc' });

        return;
    }

    if (current.direction === 'asc') {
        emit('sort-change', { column, direction: 'desc' });

        return;
    }

    emit('sort-change', null);
}

provide('data-table-sort', {
    sort: () => props.sort,
    onSort: handleSort,
});

const showingCount = computed(() => props.paginationDisplayedCount ?? props.data.length);

const paginationSummary = computed(() =>
    buildPaginationSummary(
        props.pagination,
        showingCount.value,
        props.paginationItemLabel,
    ),
);

function resolveRowSpan(row: TData, columnId: string): number | undefined {
    const column = resolvedColumns.value.find((item) => {
        if ('id' in item && item.id === columnId) {
            return true;
        }

        if ('accessorKey' in item && item.accessorKey === columnId) {
            return true;
        }

        return false;
    });

    const getRowSpan = column?.meta?.getRowSpan;

    if (!getRowSpan) {
        return undefined;
    }

    return getRowSpan(row);
}
</script>

<template>
    <div class="space-y-4">
        <DataTableToolbar v-model:search="search" :filter-defs="filterDefs" :filter-values="filterValues"
            :search-placeholder="searchPlaceholder" @filter-change="(key, value) => emit('filter-change', key, value)"
            @filters-reset="emit('filters-reset')" />

        <div class="relative min-w-0 rounded-md border overflow-hidden">
            <div
                v-if="loading"
                class="absolute top-0 left-0 right-0 h-0.5 z-10 overflow-hidden bg-primary/10"
            >
                <div class="h-full w-1/3 animate-[loading-bar_1.2s_ease-in-out_infinite] bg-primary rounded-full" />
            </div>

            <Table>
                <TableHeader>
                    <TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
                        <TableHead v-for="header in headerGroup.headers" :key="header.id"
                            :class="header.column.id === '_row_number' ? ROW_NUMBER_COLUMN_CLASS : undefined">
                            <FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header"
                                :props="header.getContext()" />
                        </TableHead>
                    </TableRow>
                </TableHeader>
                <TableBody>
                    <template v-if="table.getRowModel().rows.length">
                        <TableRow v-for="row in table.getRowModel().rows" :key="row.id"
                            :class="getRowClassName?.(row.original, row.index)"
                            :data-state="row.getIsSelected() ? 'selected' : undefined">
                            <template v-for="cell in row.getVisibleCells()" :key="cell.id">
                                <TableCell v-if="(resolveRowSpan(row.original, cell.column.id) ?? 1) !== 0" :rowspan="(resolveRowSpan(row.original, cell.column.id) ?? 1) > 1
                                    ? resolveRowSpan(row.original, cell.column.id)
                                    : undefined" :class="[
                                            cell.column.id === '_row_number' ? ROW_NUMBER_COLUMN_CLASS : undefined,
                                            (resolveRowSpan(row.original, cell.column.id) ?? 1) > 1 ? 'align-middle' : undefined,
                                            cell.column.columnDef.meta?.cellClassName,
                                        ]">
                                    <FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
                                </TableCell>
                            </template>
                        </TableRow>
                    </template>
                    <TableEmpty v-else :colspan="resolvedColumns.length">
                        <DataTableEmpty plain />
                    </TableEmpty>
                </TableBody>
            </Table>
        </div>

        <div v-if="pagination" class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
            <p class="text-muted-foreground text-sm">
                {{ paginationSummary }}
            </p>

            <div v-if="paginationLinks?.length && pagination.lastPage > 1"
                class="flex flex-wrap items-center justify-center gap-1 sm:justify-end">
                <Button v-for="link in paginationLinks" :key="`${link.label}-${link.url}`" variant="outline" size="sm"
                    :disabled="!link.url || link.active" as-child>
                    <Link v-if="link.url" :href="link.url" preserve-scroll>
                        <span v-html="link.label" />
                    </Link>
                    <span v-else v-html="link.label" />
                </Button>
            </div>
        </div>
    </div>
</template>
