'use client';

import { ReactNode, useCallback, useEffect, useState } from 'react';
import {
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  TableOptions,
  useReactTable,
  ColumnDef,
  FilterFn,
  SortingState,
  OnChangeFn,
  Row,
} from '@tanstack/react-table';
import { rankItem } from '@tanstack/match-sorter-utils';
import { Icon } from '@iconify/react';

import Pagination from '@/components/Pagination';
import { cn } from '@/lib/helper';

interface TableClassNames {
  containerClassName?: string;
  tableWrapperClassName?: string;
  tableClassName?: string;
  tableHeaderClassName?: string;
  headerRowClassName?: string;
  headerColumnClassName?: string;
  tableBodyClassName?: string;
  bodyRowClassName?: string;
  bodyColumnClassName?: string;
  paginationClassName?: string;
}

export interface TableProps<TData extends object> {
  data: TData[];
  columns: ColumnDef<TData, unknown>[];
  pageSize?: number;
  onPageSizeChange?: (pageSize: number) => void;
  totalItems?: number;
  page?: number;
  onPageChange?: (page: number) => void;
  isLoading?: boolean;
  fuzzySearchValue?: string | null;
  onFuzzySearchValueChange?: (value: string) => void;
  className?: TableClassNames;
  emptyContent?: ReactNode;
  sorting?: SortingState;
  setSorting?: OnChangeFn<SortingState>;
  manualSorting?: boolean;
  rowSelection?: Record<string, boolean>;
  setRowSelection?: OnChangeFn<Record<string, boolean>>;
  enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
  withCheckbox?: boolean;
  rowOptions?: number[];
}

const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}];

const emptyContentDefaultValue = (
  <div className='w-full p-5 text-center'>
    <span className='text-lg opacity-50'>
      Tidak ada data yang dapat ditampilkan...
    </span>
  </div>
);

const TABLE_DEFAULT_STYLING = {
  containerClassName: 'w-full mb-20',
  tableWrapperClassName:
    'overflow-x-auto border border-solid border-base-content/10 rounded-lg',
  tableClassName: 'font-inter w-full table-auto text-sm font-medium',
  tableHeaderClassName: '',
  headerRowClassName: '',
  headerColumnClassName: 'px-4 py-3 text-base-content/50',
  tableBodyClassName: '',
  bodyRowClassName: 'border-t border-t-base-content/10',
  bodyColumnClassName: 'px-4 py-3 text-base-content',
  paginationClassName: '',
};

const Table = <TData extends object>({
  data = [],
  columns = [],
  pageSize = 10,
  onPageSizeChange,
  totalItems,
  page,
  onPageChange,
  isLoading = false,
  fuzzySearchValue,
  onFuzzySearchValueChange,
  className = TABLE_DEFAULT_STYLING,
  emptyContent = emptyContentDefaultValue,
  sorting,
  setSorting,
  manualSorting = false,
  rowSelection,
  setRowSelection,
  enableRowSelection,
  withCheckbox = false,
  rowOptions = [10, 20, 50, 100],
}: TableProps<TData>) => {
  const isServerSideTable =
    totalItems !== undefined &&
    page !== undefined &&
    onPageChange !== undefined;

  const tableClassNames = {
    ...TABLE_DEFAULT_STYLING,
    ...className,
  };

  const [pagination, setPagination] = useState({
    pageIndex: 0,
    pageSize: pageSize,
  });

  const fuzzyFilter: FilterFn<TData> = useCallback(
    (row, columnId, value, addMeta) => {
      const itemRank = rankItem(row.getValue(columnId), value);
      addMeta({ itemRank });
      return itemRank.passed;
    },
    []
  );

  const tableOptions: TableOptions<TData> = {
    columns,
    data: isLoading ? (DUMMY_SKELETON_DATA as TData[]) : data, // Type assertion
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    onPaginationChange: setPagination,
    manualSorting,
    state: {
      pagination,
      globalFilter: fuzzySearchValue,
    },
    filterFns: {
      fuzzy: fuzzyFilter,
    },
    globalFilterFn: fuzzyFilter,
  };

  if (fuzzySearchValue !== null) {
    tableOptions.onGlobalFilterChange = onFuzzySearchValueChange;
    tableOptions.getFilteredRowModel = getFilteredRowModel();
  }

  if (sorting && setSorting) {
    tableOptions.onSortingChange = setSorting;
    tableOptions.state = {
      ...tableOptions.state,
      sorting,
    };
  }

  if (rowSelection && setRowSelection) {
    tableOptions.onRowSelectionChange = setRowSelection;
    tableOptions.state = {
      ...tableOptions.state,
      rowSelection,
    };
    tableOptions.getRowId = (row) => (row as { id: string }).id;
  }

  if (enableRowSelection !== undefined) {
    tableOptions.enableRowSelection = enableRowSelection;
  }

  const table = useReactTable(tableOptions);
  const { setPageSize } = table;

  const prevPageClickHandler = () => {
    table.previousPage();

    if (isServerSideTable) {
      onPageChange(page - 1);
    }
  };

  const nextPageClickHandler = () => {
    table.nextPage();

    if (isServerSideTable) {
      onPageChange(page + 1);
    }
  };

  const pageChangeHandler = (pageNumber: number) => {
    const currentPage = pageNumber - 1;

    table.setPageIndex(pageNumber ? currentPage : 0);

    if (isServerSideTable) {
      onPageChange(pageNumber);
    }
  };

  useEffect(() => {
    setPageSize(pageSize);
  }, [pageSize, setPageSize]);

  return (
    <div className={tableClassNames.containerClassName}>
      <div className={tableClassNames.tableWrapperClassName}>
        <table className={tableClassNames.tableClassName}>
          <thead className={tableClassNames.tableHeaderClassName}>
            {table.getHeaderGroups().map((headerGroup) => (
              <tr
                key={headerGroup.id}
                className={tableClassNames.headerRowClassName}
              >
                {headerGroup.headers.map((header) => (
                  <th
                    key={header.id}
                    colSpan={header.colSpan}
                    onClick={header.column.getToggleSortingHandler()}
                    className={cn(
                      header.column.getCanSort()
                        ? 'cursor-pointer select-none'
                        : '',
                      {
                        'first:w-9 first:pr-0': withCheckbox,
                      },
                      tableClassNames.headerColumnClassName
                    )}
                  >
                    <div className='flex items-center gap-1'>
                      {flexRender(
                        header.column.columnDef.header,
                        header.getContext()
                      )}

                      {header.column.getCanSort() && (
                        <div className='w-4 h-4 relative flex flex-col items-center'>
                          <Icon
                            icon='heroicons:chevron-up-16-solid'
                            width={18}
                            height={18}
                            className={cn(
                              'absolute -top-1',
                              'transition-all ease-in-out duration-200',
                              header.column.getIsSorted() === 'asc'
                                ? 'text-black'
                                : 'text-black/30'
                            )}
                          />
                          <Icon
                            icon='heroicons:chevron-down-16-solid'
                            width={18}
                            height={18}
                            className={cn(
                              'absolute -bottom-1.5',
                              'transition-all ease-in-out duration-200',
                              header.column.getIsSorted() === 'desc'
                                ? 'text-black'
                                : 'text-black/30'
                            )}
                          />
                        </div>
                      )}
                    </div>
                  </th>
                ))}
              </tr>
            ))}
          </thead>

          <tbody className={tableClassNames.tableBodyClassName}>
            {table.getRowModel().rows.map((row) => (
              <tr key={row.id} className={tableClassNames.bodyRowClassName}>
                {row.getVisibleCells().map((cell) => (
                  <td
                    key={cell.id}
                    className={cn(
                      { 'first:w-9 first:pr-0': withCheckbox },
                      tableClassNames.bodyColumnClassName
                    )}
                  >
                    {!isLoading &&
                      flexRender(cell.column.columnDef.cell, cell.getContext())}

                    {isLoading && <div className='skeleton w-full h-4' />}
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {(data.length === 0 || table.getRowModel().rows.length === 0) &&
        !isLoading &&
        emptyContent}

      {data.length > 0 && table.getRowModel().rows.length > 0 && !isLoading && (
        <div className={cn('mt-5', tableClassNames.paginationClassName)}>
          <Pagination
            totalItems={isServerSideTable ? totalItems : table.getRowCount()}
            itemsPerPage={table.getState().pagination.pageSize}
            currentPage={
              isServerSideTable
                ? page
                : table.getState().pagination.pageIndex + 1
            }
            onPrevPage={prevPageClickHandler}
            onNextPage={nextPageClickHandler}
            onPageChange={pageChangeHandler}
            rowOptions={rowOptions}
            onRowChange={onPageSizeChange}
          />
        </div>
      )}
    </div>
  );
};

export default Table;
