'use client';

import { Fragment, ReactNode, useCallback, useEffect, useState } from 'react';
import {
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  getExpandedRowModel,
  getSortedRowModel,
  TableOptions,
  useReactTable,
  ColumnDef,
  FilterFn,
  SortingState,
  OnChangeFn,
  Row,
  HeaderContext,
  ExpandedState,
} 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;
  selectedBodyRowClassName?: string;
  bodyColumnClassName?: string;
  bodySubRowClassName?: (depth: number) => string;
  selectedBodySubRowClassName?: (depth: number) => string;
  bodySubRowColumnClassName?: (depth: number) => string;
  tableFooterClassName?: string;
  footerRowClassName?: string;
  footerColumnClassName?: string;
  paginationClassName?: string;
  skeletonCellClassName?: 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);
  renderFooter?: boolean;
  withCheckbox?: boolean;
  withPagination?: boolean;
  rowOptions?: number[];
  /**
   * Custom row renderer. Should return a complete <tr> element or null.
   * This gives full control over the row structure including colspan.
   * Return null to render the default row.
   */
  renderCustomRow?: (row: Row<TData>) => ReactNode | null;
  getRowCanExpand?: (row: Row<TData>) => boolean;
  renderSubComponent?: (props: { row: Row<TData> }) => React.ReactElement;
  expanded?: ExpandedState;
  getSubRows?: (originalRow: TData, index: number) => TData[] | undefined;
}

const DUMMY_SKELETON_DATA = Array.from({ length: 10 }, (_, index) => ({
  id: index,
}));

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

export 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 border-base-content/10 text-base-content/50 text-sm font-medium',
  tableBodyClassName: '',
  bodyRowClassName:
    'transition-all duration-200 border-t border-base-content/10 bg-transparent',
  selectedBodyRowClassName: 'bg-primary/5',
  bodyColumnClassName: 'px-4 py-3 text-base-content font-medium',
  bodySubRowClassName: (depth: number) =>
    'transition-all duration-200 border-t border-base-content/10 bg-transparent',
  selectedBodySubRowClassName: (depth: number) => 'bg-primary/5',
  bodySubRowColumnClassName: (depth: number) =>
    'px-4 py-3 text-base-content font-medium',
  paginationClassName: 'px-3',
  tableFooterClassName: 'font-semibold border-base-content/10',
  footerRowClassName: 'bg-base-200 border-t-2 border-base-content/10',
  footerColumnClassName: 'p-4 text-base-content whitespace-nowrap',
};

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,
  renderFooter = false,
  withCheckbox = false,
  withPagination = true,
  rowOptions = [10, 20, 50, 100],
  renderCustomRow,
  getRowCanExpand,
  renderSubComponent,
  expanded = {},
  getSubRows,
}: 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
    defaultColumn: { sortDescFirst: false },
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    onPaginationChange: setPagination,
    getExpandedRowModel: getExpandedRowModel(),
    getRowCanExpand: getRowCanExpand ?? (getSubRows ? undefined : () => false),
    getSubRows,
    manualSorting,
    state: {
      pagination,
      globalFilter: fuzzySearchValue,
      expanded,
    },
    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={cn(
        TABLE_DEFAULT_STYLING.containerClassName,
        tableClassNames.containerClassName,
        {
          'mb-0': !withPagination,
        }
      )}
    >
      <div
        className={cn(
          TABLE_DEFAULT_STYLING.tableWrapperClassName,
          tableClassNames.tableWrapperClassName
        )}
      >
        <table
          className={cn(
            TABLE_DEFAULT_STYLING.tableClassName,
            tableClassNames.tableClassName
          )}
        >
          <thead
            className={cn(
              TABLE_DEFAULT_STYLING.tableHeaderClassName,
              tableClassNames.tableHeaderClassName
            )}
          >
            {table.getHeaderGroups().map((headerGroup) => (
              <tr
                key={headerGroup.id}
                className={cn(
                  TABLE_DEFAULT_STYLING.headerRowClassName,
                  tableClassNames.headerRowClassName
                )}
              >
                {headerGroup.headers.map((header) => {
                  const columnRelativeDepth =
                    header.depth - header.column.depth;
                  if (
                    !header.isPlaceholder &&
                    columnRelativeDepth > 1 &&
                    header.id === header.column.id
                  ) {
                    return null;
                  }
                  let rowSpan = 1;
                  if (header.isPlaceholder) {
                    const leafs = header.getLeafHeaders();
                    rowSpan = leafs[leafs.length - 1].depth - header.depth;
                  }
                  return (
                    <th
                      key={header.id}
                      colSpan={header.colSpan}
                      rowSpan={rowSpan}
                      onClick={header.column.getToggleSortingHandler()}
                      className={cn(
                        header.column.getCanSort()
                          ? 'cursor-pointer select-none'
                          : '',
                        {
                          'first:w-9 first:pr-0': withCheckbox,
                        },
                        {
                          'border-b': header.colSpan > 1,
                        },
                        TABLE_DEFAULT_STYLING.headerColumnClassName,
                        tableClassNames.headerColumnClassName
                      )}
                    >
                      <div
                        className={cn('flex items-center gap-1 min-h-full', {
                          'justify-center': header.colSpan > 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={cn(
              TABLE_DEFAULT_STYLING.tableBodyClassName,
              tableClassNames.tableBodyClassName
            )}
          >
            {table.getRowModel().rows.map((row) => {
              const customRowContent = renderCustomRow?.(row);

              if (customRowContent) {
                return renderCustomRow?.(row);
              }

              return (
                <Fragment key={row.id}>
                  <tr
                    data-depth={row.depth}
                    className={cn(
                      row.depth > 0
                        ? tableClassNames.bodySubRowClassName(row.depth)
                        : tableClassNames.bodyRowClassName,
                      {
                        [tableClassNames.selectedBodyRowClassName!]:
                          row.getIsSelected() && row.depth === 0,
                        [tableClassNames.selectedBodySubRowClassName(
                          row.depth
                        )!]: row.getIsSelected() && row.depth > 0,
                      }
                    )}
                  >
                    {row.getVisibleCells().map((cell) => (
                      <td
                        key={cell.id}
                        className={cn(
                          { 'first:w-9 first:pr-0': withCheckbox },
                          TABLE_DEFAULT_STYLING.bodyColumnClassName,
                          row.depth > 0
                            ? tableClassNames.bodySubRowColumnClassName(
                                row.depth
                              )
                            : tableClassNames.bodyColumnClassName
                        )}
                      >
                        {!isLoading &&
                          flexRender(
                            cell.column.columnDef.cell,
                            cell.getContext()
                          )}

                        {isLoading && (
                          <div
                            className={cn(
                              'skeleton w-full h-4',
                              tableClassNames.skeletonCellClassName
                            )}
                          />
                        )}
                      </td>
                    ))}
                  </tr>

                  {row.getIsExpanded() && (
                    <>
                      {renderSubComponent && (
                        <tr
                          className={cn(
                            TABLE_DEFAULT_STYLING.bodySubRowClassName(1),
                            tableClassNames.bodySubRowClassName(1),
                            {
                              [tableClassNames.selectedBodySubRowClassName(1)]:
                                row.getIsSelected(),
                            }
                          )}
                        >
                          <td colSpan={row.getVisibleCells().length}>
                            {renderSubComponent({ row })}
                          </td>
                        </tr>
                      )}
                    </>
                  )}
                </Fragment>
              );
            })}

            {(data.length === 0 || table.getRowModel().rows.length === 0) &&
              !isLoading && (
                <tr>
                  <td
                    colSpan={
                      table.getAllLeafColumns().length + (withCheckbox ? 1 : 0)
                    }
                    className='p-0'
                  >
                    {emptyContent}
                  </td>
                </tr>
              )}
          </tbody>
          <tfoot
            className={cn(
              TABLE_DEFAULT_STYLING.tableFooterClassName,
              tableClassNames.tableFooterClassName
            )}
          >
            {renderFooter && (
              <tr
                className={cn(
                  TABLE_DEFAULT_STYLING.footerRowClassName,
                  tableClassNames.footerRowClassName
                )}
              >
                {table.getAllLeafColumns().map((column) => (
                  <td
                    key={column.id}
                    className={cn(
                      { 'first:w-9 first:pr-0': withCheckbox },
                      TABLE_DEFAULT_STYLING.footerColumnClassName,
                      tableClassNames.footerColumnClassName
                    )}
                  >
                    {column.columnDef.footer &&
                      flexRender(column.columnDef.footer, {
                        column,
                        header: column.columnDef,
                        table,
                      } as HeaderContext<TData, unknown>)}
                  </td>
                ))}
              </tr>
            )}
          </tfoot>
        </table>
      </div>

      {data.length > 0 &&
        table.getRowModel().rows.length > 0 &&
        !isLoading &&
        withPagination && (
          <div
            className={cn(
              'mt-5',
              TABLE_DEFAULT_STYLING.paginationClassName,
              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;
