<script setup lang="ts">
import { ref, computed, watch, onUnmounted, useSlots } from 'vue';
import {
  ChevronUp,
  ChevronDown,
  ArrowLeft,
  ArrowRight,
  Users,
  GripVertical,
  Download,
  FileSpreadsheet,
  FileText,
  FileType,
  Braces,
  Search,
} from '@lucide/vue';
import * as XLSX from 'xlsx';
import jsPDF from 'jspdf';
import autoTable from 'jspdf-autotable';
import { saveAs } from 'file-saver';
import { cn } from '@mykopkb/core/utils/cn';
import { Button } from '@/components/ui/button';
import {
  MenuRoot,
  MenuTrigger,
  MenuPositioner,
  MenuContent,
  MenuItem,
} from '@/components/ui/menu';
import {
  PaginationContext,
  PaginationRoot,
  PaginationItem,
  PaginationPrevTrigger,
  PaginationNextTrigger,
  PaginationEllipsis,
} from '@/components/ui/pagination';
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table';
import * as select from '@zag-js/select';
import {
  SelectRoot,
  SelectControl,
  SelectTrigger,
  SelectValueText,
  SelectContent,
  SelectItemGroup,
  SelectItem,
  SelectItemText,
} from '@/components/ui/select';

// Type definitions
type ColumnType = 'index' | 'select' | 'action' | 'status' | 'id' | 'date' | 'email' | 'phone' | 'priority' | 'category' | 'registration' | 'model' | 'unit' | 'text';

type SortOrder = 'asc' | 'desc';

export interface SortConfig {
  key: string;
  order: SortOrder;
}

export interface TableHeader {
  title: string;
  key: string;
  sortable?: boolean;
  align?: 'start' | 'center' | 'end';
  width?: number;
  resizable?: boolean;
  /** Optional: format this column's value for CSV/Excel/PDF export. When set, used instead of raw value. */
  exportValue?: (item: TableItem) => string;
}

export interface PaginationData {
  current_page: number;
  per_page: number;
  total: number;
  last_page: number;
  from: number | null;
  to: number | null;
  has_more_pages: boolean;
}

type TableItem = Record<string, any>;

// Props with proper defaults
const props = withDefaults(defineProps<{
  headers: TableHeader[];
  items: TableItem[];
  loading?: boolean;
  itemsPerPage?: number;
  page?: number;
  itemKey?: string;
  pagination?: PaginationData | null;
  showPagination?: boolean;
  currentSort?: SortConfig[];
  exportable?: boolean;
  exportFileName?: string;
}>(), {
  items: () => [],
  headers: () => [],
  loading: false,
  itemsPerPage: 10,
  page: 1,
  itemKey: 'id',
  pagination: null,
  showPagination: false,
  currentSort: () => [],
  exportable: false,
  exportFileName: 'table-data'
});

// Define model props
const pageModel = defineModel<number>('page');
const itemsPerPageModel = defineModel<number>('items-per-page');

// Emits
const emit = defineEmits<{
  'update:sort-by': [value: SortConfig[]];
  'update:page': [value: number];
  'update:items-per-page': [value: number];
  'update:column-widths': [value: Record<string, number>];
  'export-error': [error: Error];
}>();

// State
const sortBy = ref<SortConfig[]>(props.currentSort || []);
const columnWidths = ref<Record<string, number>>({});
const isResizing = ref(false);
const resizingColumn = ref<string | null>(null);
const startX = ref(0);
const perPageOptions = [10, 25, 50, 100, 250, 500, 1000];

type PerPageSelectOption = { label: string; value: number };

const perPageSelectOptions: PerPageSelectOption[] = perPageOptions.map((option) => ({
  label: String(option),
  value: option,
}));

const perPageCollection = select.collection({
  items: perPageSelectOptions,
  itemToValue: (item) => item.label,
});

const perPageInitial = computed(() => [
  String(itemsPerPageModel.value ?? props.pagination?.per_page ?? props.itemsPerPage ?? 10),
]);

const startWidth = ref(0);

// Helper function to detect column type from key and title
const detectColumnType = (header: TableHeader): ColumnType => {
  const key = header.key.toLowerCase();
  const title = header.title.toLowerCase();

  // Index/Number columns (Bil./# row counters — not field labels like "No. Permohonan")
  if (key === '#' || key === 'index' || key === 'bil' ||
    title.includes('bil') || title === '#' ||
    /^no\.?\s*$/i.test(title.trim())) {
    return 'index';
  }

  // Selection/Checkbox columns
  if (key === 'select' || key === 'checkbox' || key === 'check' ||
    title.includes('select') || title === '') {
    return 'select';
  }

  // Action columns
  if (key === 'actions' || key === 'action' || key === 'opsi' ||
    title.includes('action') || title.includes('opsi')) {
    return 'action';
  }

  // Status columns
  if (key === 'status' || key === 'state' || title.includes('status') ||
    title.includes('state') || title.includes('keadaan')) {
    return 'status';
  }

  // ID columns
  if (key === 'id' || key.endsWith('_id') || title.includes('id')) {
    return 'id';
  }

  // Date columns
  if (key.includes('date') || key.includes('time') || key.includes('created') ||
    key.includes('updated') || title.includes('date') || title.includes('time') ||
    title.includes('tarikh') || title.includes('masa')) {
    return 'date';
  }

  // Email columns
  if (key.includes('email') || title.includes('email') || title.includes('emel')) {
    return 'email';
  }

  // Phone columns
  if (key.includes('phone') || key.includes('tel') || title.includes('phone') ||
    title.includes('telefon') || title.includes('no tel')) {
    return 'phone';
  }

  // Priority columns
  if (key.includes('priority') || title.includes('priority') || title.includes('keutamaan')) {
    return 'priority';
  }

  // Category columns
  if (key.includes('category') || key.includes('kategori') || title.includes('category') ||
    title.includes('kategori')) {
    return 'category';
  }

  // Registration/Serial number columns
  if (key.includes('registration') || key.includes('serial') || key.includes('no ') ||
    title.includes('registration') || title.includes('serial') || title.includes('no ')) {
    return 'registration';
  }

  // Model/Type columns
  if (key.includes('model') || key.includes('type') || key.includes('jenis') ||
    title.includes('model') || title.includes('type') || title.includes('jenis')) {
    return 'model';
  }

  // Unit/Department columns
  if (key.includes('unit') || key.includes('department') || key.includes('unit') ||
    title.includes('unit') || title.includes('department') || title.includes('bahagian')) {
    return 'unit';
  }

  // Default to text
  return 'text';
};

// Helper function to estimate text width in pixels
const estimateTextWidth = (text: string, fontSize: number = 14): number => {
  // Average character width estimation based on font size
  // Using a more accurate multiplier for typical fonts (approximately 0.6 * fontSize for average characters)
  const avgCharWidth = fontSize * 0.6;
  // For mixed content (numbers, letters, special chars), use conservative estimate
  return text.length * avgCharWidth;
};

// Helper function to get display value from item
const getDisplayValue = (item: TableItem, headerKey: string): string => {
  // Handle nested properties (e.g., 'kjc_asset_holding.registration_number')
  const keys = headerKey.split('.');
  let value = item;

  for (const key of keys) {
    if (value && typeof value === 'object' && key in value) {
      value = value[key];
    } else {
      return '';
    }
  }

  // Handle arrays (e.g., roles)
  if (Array.isArray(value)) {
    return value.map((v: any) => v?.name || v?.toString() || '').join(', ');
  }

  // Handle objects with name property
  if (value && typeof value === 'object' && 'name' in value) {
    return value.name || '';
  }

  return value !== null && value !== undefined ? String(value) : '';
};

const isIndexColumn = (header: TableHeader) =>
  header.key === '#' || header.key === 'index' || detectColumnType(header) === 'index';

const getRowNumber = (rowIndex: number): number => {
  const from = props.pagination?.from;
  if (from != null) {
    return from + rowIndex;
  }

  const page = pageModel.value ?? props.pagination?.current_page ?? 1;
  const perPage =
    props.pagination?.per_page ?? itemsPerPageModel.value ?? props.itemsPerPage ?? 10;

  return (page - 1) * perPage + rowIndex + 1;
};

const getCellDisplayValue = (
  item: TableItem,
  header: TableHeader,
  rowIndex: number,
): string => {
  if (isIndexColumn(header)) {
    return String(getRowNumber(rowIndex));
  }

  return getDisplayValue(item, header.key);
};

// Helper function to calculate optimal column width based on actual data content
const calculateOptimalWidth = (header: TableHeader, items: TableItem[]): number => {
  // If width is explicitly set, use it
  if (header.width) return header.width;

  // If column width is already set by user, use it
  if (columnWidths.value[header.key] !== undefined) {
    return columnWidths.value[header.key]!;
  }

  const columnType = detectColumnType(header);

  // Base width starts with header title width
  const headerWidth = estimateTextWidth(header.title, 14) + 40; // Add padding
  let baseWidth = headerWidth;

  // Set minimum widths by column type
  const minWidths: Record<string, number> = {
    index: 60,
    select: 50,
    action: 120,
    status: 100,
    id: 80,
    date: 140,
    email: 220,
    phone: 150,
    priority: 100,
    category: 120,
    registration: 160,
    model: 120,
    unit: 140,
    text: 100
  };

  baseWidth = Math.max(baseWidth, minWidths[columnType] || 100);

  // For data columns, calculate based on actual data content
  if (items.length > 0 && columnType !== 'index' && columnType !== 'select') {
    // Sample more items for better accuracy (up to 50 items or all if less)
    const sampleSize = Math.min(items.length, 50);
    const sampleItems = items.slice(0, sampleSize);

    const contentWidths: number[] = [];

    sampleItems.forEach(item => {
      const displayValue = getDisplayValue(item, header.key);

      if (displayValue) {
        // Estimate width based on actual content
        let contentWidth = estimateTextWidth(displayValue, 14);

        // Add extra width for specific column types
        switch (columnType) {
          case 'status':
            // Status chips need more space
            contentWidth += 40;
            break;
          case 'action':
            // Action buttons need more space
            contentWidth += 80;
            break;
          case 'email':
            // Emails typically need more width
            contentWidth += 20;
            break;
          case 'date':
            // Dates are formatted, need consistent width
            contentWidth = Math.max(contentWidth, 140);
            break;
          default:
            // Add padding for other types
            contentWidth += 20;
        }

        contentWidths.push(contentWidth);
      }
    });

    if (contentWidths.length > 0) {
      // Use 95th percentile to avoid outliers but account for most content
      const sortedWidths = contentWidths.sort((a, b) => a - b);
      const percentileIndex = Math.floor(sortedWidths.length * 0.95);
      const dataBasedWidth =
        sortedWidths[percentileIndex] ??
        sortedWidths[sortedWidths.length - 1] ??
        baseWidth;

      baseWidth = Math.max(baseWidth, dataBasedWidth);
    }
  }

  // Cap maximum width to prevent extremely wide columns
  return Math.min(baseWidth, 500);
};

// Computed - recalculate when headers or items change
const sortedHeaders = computed(() => {
  return props.headers.map(header => ({
    ...header,
    sortable: header.sortable ?? false,
    resizable: header.resizable ?? true,
    width: calculateOptimalWidth(header, props.items)
  }));
});

// Methods
const getSortIcon = (columnKey: string) => {
  const currentSort = sortBy.value.find((sort) => sort.key === columnKey);
  if (!currentSort) return null;
  return currentSort.order === 'asc' ? 'asc' : 'desc';
};

const toggleSort = (header: TableHeader) => {
  if (!header.sortable) return;

  const currentSort = sortBy.value.find((sort) => sort.key === header.key);

  if (!currentSort) {
    sortBy.value = [{ key: header.key, order: 'asc' }];
    return;
  }

  if (currentSort.order === 'asc') {
    sortBy.value = [{ key: header.key, order: 'desc' }];
    return;
  }

  sortBy.value = [];
};

const headerAlignClass = (align?: TableHeader['align']) => {
  if (align === 'center') return 'text-center';
  if (align === 'end') return 'text-right';
  return 'text-left';
};

const setItemsPerPageValue = (details: { value: string[] }) => {
  const value = Number(details.value[0]);
  if (Number.isNaN(value)) return;
  itemsPerPageModel.value = value;
  emit('update:items-per-page', value);
};

const handlePageChange = (details: { page: number }) => {
  pageModel.value = details.page;
  emit('update:page', details.page);
};

const getItemKey = (item: TableItem, index: number) => {
  const key = item[props.itemKey];
  return key !== undefined && key !== null ? String(key) : String(index);
};

const currentPage = computed(
  () => pageModel.value ?? props.pagination?.current_page ?? 1,
);

const isFirstPage = computed(() => currentPage.value <= 1);

const isLastPage = computed(
  () => currentPage.value >= (props.pagination?.last_page ?? 1),
);

const slots = useSlots();

const showTableToolbar = computed(() => props.exportable || !!slots.toolbar);

const recordSummary = computed(() => {
  if (!props.pagination) return null;

  const { from, to, total } = props.pagination;

  if (from != null && to != null) {
    return `Menunjukkan ${from}–${to} daripada ${total} rekod`;
  }

  if (total) {
    return `${total} rekod`;
  }

  return null;
});

// Column resizing methods
const startResize = (event: MouseEvent, columnKey: string) => {
  event.preventDefault();
  event.stopPropagation();

  isResizing.value = true;
  resizingColumn.value = columnKey;
  startX.value = event.clientX;

  // Get current width from the header
  const header = props.headers.find(h => h.key === columnKey);
  if (header) {
    startWidth.value = columnWidths.value[columnKey] || calculateOptimalWidth(header, props.items);
  }

  document.addEventListener('mousemove', handleResize);
  document.addEventListener('mouseup', stopResize);
  document.body.style.cursor = 'col-resize';
  document.body.style.userSelect = 'none';
};

const handleResize = (event: MouseEvent) => {
  if (!isResizing.value || !resizingColumn.value) return;

  const deltaX = event.clientX - startX.value;
  const newWidth = Math.max(50, startWidth.value + deltaX); // Minimum width of 50px

  columnWidths.value[resizingColumn.value] = newWidth;
  emit('update:column-widths', columnWidths.value);
};

const stopResize = () => {
  isResizing.value = false;
  resizingColumn.value = null;

  document.removeEventListener('mousemove', handleResize);
  document.removeEventListener('mouseup', stopResize);
  document.body.style.cursor = '';
  document.body.style.userSelect = '';
};

// Cleanup event listeners on component unmount
onUnmounted(() => {
  document.removeEventListener('mousemove', handleResize);
  document.removeEventListener('mouseup', stopResize);
  document.body.style.cursor = '';
  document.body.style.userSelect = '';
});

// Export methods
const prepareExportData = (): { headers: string[]; data: string[][] } => {
  const headers = props.headers.map(header => header.title);
  const data = props.items.map((item, index) =>
    props.headers.map(header => {
      // Use custom export formatter when provided (e.g. for human-readable loan status)
      if (typeof header.exportValue === 'function') {
        return header.exportValue(item);
      }

      // Handle nested properties (e.g., 'unit.name', 'rank.name')
      const keys = header.key.split('.');
      let value: unknown = item;

      for (const key of keys) {
        if (value && typeof value === 'object' && key in value) {
          value = (value as Record<string, unknown>)[key];
        } else {
          value = null;
          break;
        }
      }

      if (isIndexColumn(header)) {
        value = getRowNumber(index);
      } else if (header.key === 'actions') {
        // Skip actions column in export
        value = '';
      }

      return value !== null && value !== undefined ? String(value) : '';
    })
  );
  return { headers, data };
};

const exportToCSV = () => {
  try {
    const { headers, data } = prepareExportData();
    const csvContent = [headers, ...data]
      .map(row => row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(','))
      .join('\n');

    const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
    const fileName = `${props.exportFileName}.csv`;
    saveAs(blob, fileName);
  } catch (error) {
    emit('export-error', error instanceof Error ? error : new Error('CSV export failed'));
  }
};

const exportToExcel = () => {
  try {
    const { headers, data } = prepareExportData();
    const worksheet = XLSX.utils.aoa_to_sheet([headers, ...data]);
    const workbook = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');

    const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
    const blob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    const fileName = `${props.exportFileName}.xlsx`;
    saveAs(blob, fileName);
  } catch (error) {
    emit('export-error', error instanceof Error ? error : new Error('Excel export failed'));
  }
};

const exportToPDF = () => {
  try {
    const { headers, data } = prepareExportData();
    const colCount = headers.length;

    // Use landscape when table has many columns to avoid squeezing
    const useLandscape = colCount > 6;
    const doc = new jsPDF(useLandscape ? 'l' : 'p', 'pt', 'a4');

    // Smaller font and padding when many columns so content remains readable
    let fontSize = 8;
    if (colCount > 12) fontSize = 6;
    else if (colCount > 8) fontSize = 7;
    const cellPadding = colCount > 8 ? 4 : 8;

    const tableOptions: Parameters<typeof autoTable>[1] = {
      head: [headers],
      body: data,
      styles: { fontSize, cellPadding },
      headStyles: { fillColor: [66, 139, 202] },
      margin: { top: 20 },
    };

    // For wide tables: let table use natural column widths and split across pages
    // instead of squeezing all columns into one page
    if (colCount > 8) {
      tableOptions.tableWidth = 'wrap';
      tableOptions.horizontalPageBreak = true;
      tableOptions.styles = {
        ...tableOptions.styles,
        minCellWidth: 36,
        overflow: 'linebreak',
      } as Record<string, unknown>;
    }

    autoTable(doc, tableOptions);

    const fileName = `${props.exportFileName}.pdf`;
    doc.save(fileName);
  } catch (error) {
    emit('export-error', error instanceof Error ? error : new Error('PDF export failed'));
  }
};

const exportToJSON = () => {
  try {
    const jsonData = props.items.map(item => {
      const jsonItem: Record<string, unknown> = {};
      props.headers.forEach(header => {
        // Handle nested properties
        const keys = header.key.split('.');
        let value: unknown = item;
        for (const key of keys) {
          if (value && typeof value === 'object' && key in value) {
            value = (value as Record<string, unknown>)[key];
          } else {
            value = null;
            break;
          }
        }
        jsonItem[header.title] = value;
      });
      return jsonItem;
    });

    const jsonContent = JSON.stringify(jsonData, null, 2);
    const blob = new Blob([jsonContent], { type: 'application/json' });
    const fileName = `${props.exportFileName}.json`;
    saveAs(blob, fileName);
  } catch (error) {
    emit('export-error', error instanceof Error ? error : new Error('JSON export failed'));
  }
};

// Watch for changes in currentSort prop
watch(() => props.currentSort, (newSort) => {
  if (newSort) {
    sortBy.value = newSort;
  }
}, { deep: true });

// Watch for changes in sortBy and emit to parent
watch(sortBy, (newSort) => {
  emit('update:sort-by', newSort);
}, { deep: true });
</script>

<template>
  <div class="w-full">
    <div class="overflow-hidden rounded-xl border border-foreground/10 bg-background shadow-sm">
      <div v-if="showTableToolbar"
        class="flex flex-wrap items-center gap-3 border-b border-foreground/10 bg-foreground/2 px-4 py-3">
        <div class="flex min-w-0 flex-1 flex-wrap items-center gap-3">
          <slot name="toolbar" />
          <p v-if="!slots.toolbar && recordSummary" class="text-sm text-muted-foreground">
            {{ recordSummary }}
          </p>
        </div>

        <div v-if="exportable" class="ms-auto shrink-0">
          <MenuRoot>
            <MenuTrigger as-child>
              <Button look="outline" variant="secondary" size="sm" class="gap-2">
                <Download class="size-4 shrink-0" />
                <span>Export</span>
                <ChevronDown class="size-3.5 shrink-0 opacity-60" />
              </Button>
            </MenuTrigger>
            <MenuPositioner>
              <MenuContent class="min-w-44">
                <MenuItem value="export-csv" @click="exportToCSV">
                  <span class="flex items-center gap-2.5">
                    <FileText class="size-4 shrink-0 opacity-70" />
                    <span>CSV</span>
                  </span>
                </MenuItem>
                <MenuItem value="export-excel" @click="exportToExcel">
                  <span class="flex items-center gap-2.5">
                    <FileSpreadsheet class="size-4 shrink-0 opacity-70" />
                    <span>Excel</span>
                  </span>
                </MenuItem>
                <MenuItem value="export-pdf" @click="exportToPDF">
                  <span class="flex items-center gap-2.5">
                    <FileType class="size-4 shrink-0 opacity-70" />
                    <span>PDF</span>
                  </span>
                </MenuItem>
                <MenuItem value="export-json" @click="exportToJSON">
                  <span class="flex items-center gap-2.5">
                    <Braces class="size-4 shrink-0 opacity-70" />
                    <span>JSON</span>
                  </span>
                </MenuItem>
              </MenuContent>
            </MenuPositioner>
          </MenuRoot>
        </div>
      </div>

      <div class="relative px-4 pt-3" :class="loading && 'pointer-events-none opacity-50'">
        <Table variant="boxed" class="custom-data-table border-separate border-spacing-y-2.5">
          <TableHeader>
            <TableRow class="border-0 hover:bg-transparent">
              <TableHead v-for="header in sortedHeaders" :key="header.key" :class="cn(
                'group/head relative bg-primary text-primary-foreground font-semibold border-y border-primary/20 first:rounded-tl-xl first:border-s last:rounded-tr-xl last:border-e',
                headerAlignClass(header.align),
              )" :style="{ width: `${header.width}px`, minWidth: `${header.width}px` }">
                <div v-if="header.sortable" class="flex items-center gap-2 cursor-pointer select-none" role="button"
                  tabindex="0" @click="toggleSort(header)" @keydown.enter.prevent="toggleSort(header)"
                  @keydown.space.prevent="toggleSort(header)">
                  <span>{{ header.title }}</span>
                  <ChevronUp v-if="getSortIcon(header.key) === 'asc'" class="size-4" />
                  <ChevronDown v-else-if="getSortIcon(header.key) === 'desc'" class="size-4" />
                  <div v-else class="sortable-indicator">
                    <ChevronUp class="size-3 opacity-40" />
                    <ChevronDown class="size-3 opacity-40 -mt-1" />
                  </div>
                </div>
                <span v-else>{{ header.title }}</span>

                <button v-if="header.resizable" type="button"
                  class="resize-handle opacity-0 transition-opacity group-hover/head:opacity-100 hover:opacity-100 active:opacity-100"
                  :aria-label="`Resize column ${header.title}`" @mousedown="startResize($event, header.key)">
                  <GripVertical class="size-3.5 shrink-0 opacity-70" />
                </button>
              </TableHead>
            </TableRow>
          </TableHeader>

          <TableBody>
            <template v-if="loading">
              <TableRow v-for="row in 5" :key="`loading-${row}`" class="border-0">
                <TableCell v-for="header in sortedHeaders" :key="`loading-${header.key}`"
                  :class="headerAlignClass(header.align)">
                  <div class="h-4 rounded bg-foreground/10 animate-pulse" />
                </TableCell>
              </TableRow>
            </template>

            <template v-else-if="items.length">
              <TableRow v-for="(item, index) in items" :key="getItemKey(item, index)" class="border-0">
                <TableCell v-for="header in sortedHeaders" :key="`${getItemKey(item, index)}-${header.key}`" :class="cn(
                  headerAlignClass(header.align),
                  'overflow-hidden text-ellipsis whitespace-nowrap',
                )" :style="{ width: `${header.width}px`, minWidth: `${header.width}px` }">
                  <slot :name="`item.${header.key}`" :item="item" :index="index" :row-number="getRowNumber(index)"
                    :value="getCellDisplayValue(item, header, index)">
                    {{ getCellDisplayValue(item, header, index) }}
                  </slot>
                </TableCell>
              </TableRow>
            </template>

            <template v-else>
              <TableRow class="border-0">
                <TableCell :colspan="sortedHeaders.length" class="h-32 text-center">
                  <div class="flex flex-col items-center justify-center gap-3 text-muted-foreground">
                    <Search class="size-10 opacity-40" />
                    <div class="font-medium">Tiada rekod ditemui</div>
                    <div class="text-sm">Sila adjust kriteria pencarian</div>
                  </div>
                </TableCell>
              </TableRow>
            </template>
          </TableBody>
        </Table>
      </div>

      <div v-if="showPagination && pagination"
        class="flex flex-wrap items-center justify-between gap-4 border-t border-foreground/10 bg-foreground/2 px-4 py-3">
        <div class="flex items-center gap-2 text-sm text-muted-foreground">
          <span>Bilangan rekod per halaman:</span>
          <SelectRoot
            :key="perPageInitial[0]"
            class="w-auto min-w-20"
            :collection="perPageCollection"
            :default-value="perPageInitial"
            @value-change="setItemsPerPageValue"
          >
            <SelectControl>
              <SelectTrigger class="h-9 w-auto min-w-20">
                <SelectValueText />
              </SelectTrigger>
            </SelectControl>
            <SelectContent>
              <SelectItemGroup>
                <SelectItem
                  v-for="item in perPageCollection.items"
                  :key="item.label"
                  :item="item"
                >
                  <SelectItemText>{{ item.label }}</SelectItemText>
                </SelectItem>
              </SelectItemGroup>
            </SelectContent>
          </SelectRoot>
        </div>

        <div class="text-sm text-muted-foreground">
          <template v-if="pagination.from != null && pagination.to != null">
            {{ pagination.from }}-{{ pagination.to }} daripada {{ pagination.total }}
          </template>
          <template v-else>
            {{ pagination.total }} rekod
          </template>
        </div>

        <PaginationRoot class="flex items-center gap-2" :count="pagination.total" :pageSize="pagination.per_page"
          :page="currentPage" :siblingCount="1" :onPageChange="handlePageChange">
          <PaginationPrevTrigger asChild>
            <Button size="sm" look="outline" variant="secondary" :disabled="isFirstPage">
              <ArrowLeft class="size-4" />
            </Button>
          </PaginationPrevTrigger>
          <PaginationContext v-slot="{ pagination: paginationApi }">
            <template v-for="(pageItem, index) in paginationApi?.pages" :key="index">
              <PaginationItem v-if="pageItem.type === 'page'" v-bind="{ ...pageItem }" asChild>
                <Button size="sm" :look="pageItem.value === currentPage ? 'filled' : 'outline'"
                  :variant="pageItem.value === currentPage ? 'primary' : 'secondary'">
                  {{ pageItem.value }}
                </Button>
              </PaginationItem>
              <PaginationEllipsis v-else :index="index" />
            </template>
          </PaginationContext>
          <PaginationNextTrigger asChild>
            <Button size="sm" look="outline" variant="secondary" :disabled="isLastPage">
              <ArrowRight class="size-4" />
            </Button>
          </PaginationNextTrigger>
        </PaginationRoot>
      </div>
    </div>
  </div>
</template>

<style scoped>
.custom-data-table {
  overflow: hidden;
}

.sortable-indicator {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1;
}

.resize-handle {
  position: absolute;
  right: 0;
  top: 0;
  bottom: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 0.875rem;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  cursor: col-resize;
  touch-action: none;
}

.resize-handle:hover,
.resize-handle:active {
  background-color: rgba(255, 255, 255, 0.12);
}
</style>
