'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;
  tableFooterClassName?: string;
  footerRowClassName?: string;
  footerColumnClassName?: string;
  paginationClassName?: string;
}

export interface TableProps<TData extends object> {
  data: TData[];
  columns: ColumnDef<TData, unknown>[];
  pageSize?: number;
  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);
  renderFooter?: boolean;
  footerContent?: ReactNode;
  footerData?: TData[];
}

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 = <TData extends object>({
  data = [],
  columns = [],
  pageSize = 10,
  totalItems,
  page,
  onPageChange,
  isLoading = false,
  fuzzySearchValue,
  onFuzzySearchValueChange,
  className = {
    containerClassName: '',
    tableWrapperClassName: '',
    tableClassName: '',
    tableHeaderClassName: '',
    headerRowClassName: '',
    headerColumnClassName: '',
    tableBodyClassName: '',
    bodyRowClassName: '',
    bodyColumnClassName: '',
    tableFooterClassName: '',
    footerRowClassName: '',
    footerColumnClassName: '',
    paginationClassName: '',
  },
  emptyContent = emptyContentDefaultValue,
  sorting,
  setSorting,
  manualSorting = false,
  rowSelection,
  setRowSelection,
  enableRowSelection,
  renderFooter = false,
  footerContent,
  footerData = [],
}: TableProps<TData>) => {
  const isServerSideTable =
    totalItems !== undefined &&
    page !== undefined &&
    onPageChange !== undefined;

  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 footerTableOptions: TableOptions<TData> = {
    columns,
    data: footerData,
    getCoreRowModel: getCoreRowModel(),
  };

  const footerTable = useReactTable(footerTableOptions);

  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={className.containerClassName}>
      <div className={className.tableWrapperClassName}>
        <table className={className.tableClassName}>
          <thead className={className.tableHeaderClassName}>
            {table.getHeaderGroups().map((headerGroup) => (
              <tr key={headerGroup.id} className={className.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'
                        : '',
                      className.headerColumnClassName
                    )}
                  >
                    <div className='flex items-center gap-1'>
                      {flexRender(
                        header.column.columnDef.header,
                        header.getContext()
                      )}

                      {header.column.getCanSort() && (
                        <div className='flex items-center'>
                          <Icon
                            icon='lucide:arrow-up'
                            width={12}
                            height={12}
                            className={cn(
                              'transition-all ease-in-out duration-200',
                              header.column.getIsSorted() === 'asc'
                                ? 'text-black'
                                : 'text-black/30'
                            )}
                          />
                          <Icon
                            icon='lucide:arrow-down'
                            width={12}
                            height={12}
                            className={cn(
                              'transition-all ease-in-out duration-200',
                              header.column.getIsSorted() === 'desc'
                                ? 'text-black'
                                : 'text-black/30'
                            )}
                          />
                        </div>
                      )}
                    </div>
                  </th>
                ))}
              </tr>
            ))}
          </thead>

          <tbody className={className.tableBodyClassName}>
            {table.getRowModel().rows.map((row) => (
              <tr key={row.id} className={className.bodyRowClassName}>
                {row.getVisibleCells().map((cell) => (
                  <td key={cell.id} className={className.bodyColumnClassName}>
                    {!isLoading &&
                      flexRender(cell.column.columnDef.cell, cell.getContext())}

                    {isLoading && <div className='skeleton w-full h-4' />}
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
          <tfoot className={cn(className.tableFooterClassName)}>
            {renderFooter &&
              (footerData && footerData.length > 0
                ? footerTable.getRowModel().rows.map((row) => (
                    <tr key={row.id} className={className.footerRowClassName}>
                      {row.getVisibleCells().map((cell) => (
                        <td
                          key={cell.id}
                          className={className.footerColumnClassName}
                        >
                          {flexRender(
                            cell.column.columnDef.cell,
                            cell.getContext()
                          )}
                        </td>
                      ))}
                    </tr>
                  ))
                : footerContent)}
          </tfoot>
        </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', className.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}
          />
        </div>
      )}
    </div>
  );
};

export default Table;
