import {
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  useReactTable,
  type ColumnDef,
  type ColumnFiltersState,
  type FilterFn,
  type RowSelectionState,
  type SortingState,
  type Table,
} from "@tanstack/react-table";
import clsx from "clsx";
import {
  Checkbox as CheckboxPrimitive,
  Select as SelectPrimative,
} from "radix-ui";
import * as React from "react";
import { SuffixContext, useGetKey, useGetSet, useSuffixContext } from "../..";
import { CheckboxIcon, IndeterminateIcon } from "../../icons";
import * as checkboxStyles from "../Checkbox/styles.module.css";
import * as selectStyles from "../Select/styles.module.css";
import * as styles from "./styles.module.css";

import { rankItem, type RankingInfo } from "@tanstack/match-sorter-utils";

const Table = "table";
const TableBody = "tbody";
const TableCell = "td";
const TableHead = "th";
const TableHeader = "thead";
const TableRow = "tr";

interface DataTableProps<TData, TValue> extends React.ComponentProps<"div"> {
  "data-id": string;
  columns: ColumnDef<TData, TValue>[];
  data: TData[];
  selectable?: boolean;
  type?: string;
  initialPageSize?: number;
  searchTerm?: string;
}

const INITIAL_VALUE: { selectedRows: any[] } = { selectedRows: [] };
export function DataTable<TData, TValue>({
  columns = [],
  data = [],
  selectable = false,
  type: _type,
  children: _children,
  searchTerm,
  initialPageSize = 10,
  ...props
}: DataTableProps<TData, TValue>) {
  const key = useGetKey(props);
  const suffix = useSuffixContext();
  const [_, setState] = useGetSet<{ selectedRows: TData[] }>(
    key,
    INITIAL_VALUE,
  );
  const [sorting, setSorting] = React.useState<SortingState>([]);
  const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
  const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
    [],
  );
  const [globalFilter, setGlobalFilter] = React.useState("");
  const table = useReactTable({
    data,
    columns,
    globalFilterFn: "fuzzy",
    filterFns: {
      fuzzy: fuzzyFilter,
    },
    getCoreRowModel: getCoreRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    onSortingChange: setSorting,
    getSortedRowModel: getSortedRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    onRowSelectionChange: (rowSelection) => {
      setRowSelection(rowSelection);
    },
    onColumnFiltersChange: setColumnFilters,
    onGlobalFilterChange: setGlobalFilter,
    initialState: {
      pagination: {
        pageIndex: 0,
        pageSize: initialPageSize,
      },
    },
    state: {
      sorting,
      rowSelection,
      columnFilters,
      globalFilter,
    },
    autoResetPageIndex: false,
  });

  React.useEffect(() => {
    const selectedRows = table
      .getFilteredSelectedRowModel()
      .rows.map((row) => row.original);
    setState({ selectedRows });
  }, [rowSelection, setState, table]);

  React.useEffect(() => {
    const handler = setTimeout(() => {
      if (searchTerm) {
        setGlobalFilter(searchTerm);
        table.setPageIndex(0);
      } else {
        setGlobalFilter("");
        table.setPageIndex(0);
      }
    }, 72); // about 4 frames at 60fps
    return () => clearTimeout(handler);
  }, [searchTerm]);

  return (
    <div {...props}>
      <div className={clsx("__table-container", styles.container)}>
        <Table className={clsx("__table-table", styles.table)}>
          <TableHeader className={clsx("__table-thead", styles.thead)}>
            {table.getHeaderGroups().map((headerGroup) => (
              <TableRow
                key={headerGroup.id}
                className={clsx("__table-tr", styles.tr)}
              >
                {headerGroup.headers.map((header) => {
                  return (
                    <TableHead
                      key={header.id}
                      className={clsx(
                        "__table-th",
                        header.column.columnDef.meta?.className,
                        styles.th,
                      )}
                    >
                      {header.isPlaceholder
                        ? null
                        : flexRender(
                            header.column.columnDef.header,
                            header.getContext(),
                          )}
                    </TableHead>
                  );
                })}
              </TableRow>
            ))}
          </TableHeader>
          <TableBody className={clsx("__table-tbody", styles.tbody)}>
            {table.getRowModel().rows?.length ? (
              table.getRowModel().rows.map((row) => {
                const key = suffix ? `${row.id}-${suffix}` : row.id;
                return (
                  <SuffixContext.Provider value={key} key={key}>
                    <TableRow
                      data-state={row.getIsSelected() && "selected"}
                      className={clsx("__table-tr", styles.tr)}
                    >
                      {row.getVisibleCells().map((cell) => (
                        <TableCell
                          key={cell.id}
                          className={clsx(
                            "__table-td",
                            cell.column.columnDef.meta?.className,
                            styles.td,
                          )}
                        >
                          {flexRender(
                            cell.column.columnDef.cell,
                            cell.getContext(),
                          )}
                        </TableCell>
                      ))}
                    </TableRow>
                  </SuffixContext.Provider>
                );
              })
            ) : (
              <TableRow className={clsx("__table-tr", styles.tr)}>
                <TableCell
                  colSpan={columns.length}
                  className={clsx("__table-empty", styles.empty)}
                >
                  No results.
                </TableCell>
              </TableRow>
            )}
          </TableBody>
        </Table>
      </div>
      <DataTablePagination table={table} selectable={selectable} />
    </div>
  );
}

interface DataTablePaginationProps<TData> {
  table: Table<TData>;
  selectable?: boolean;
}

function DataTablePagination<TData>({
  table,
  selectable = false,
}: DataTablePaginationProps<TData>) {
  return (
    <div className={clsx("__table-pagination", styles.pagination)}>
      {selectable ? (
        <div
          className={clsx("__table-pagination__left", styles.pagination__left)}
        >
          {table.getFilteredSelectedRowModel().rows.length} of{" "}
          {table.getFilteredRowModel().rows.length} row(s) selected.
        </div>
      ) : (
        <div
          className={clsx("__table-pagination__left", styles.pagination__left)}
        />
      )}
      <div
        className={clsx("__table-pagination__right", styles.pagination__right)}
      >
        <div
          className={clsx(
            "__table-pagination__controls_left",
            styles.pagination_controls_left,
          )}
        >
          <p>Rows per page</p>
          <div
            className={clsx("__table-pagination__select", selectStyles.root)}
          >
            <SelectPrimative.Root
              value={`${table.getState().pagination.pageSize}`}
              onValueChange={(value) => {
                table.setPageSize(Number(value));
              }}
            >
              <SelectPrimative.Trigger
                className={clsx(
                  "__table-pagination__select_trigger",
                  selectStyles.trigger,
                )}
                data-component="Select$Brevity"
              >
                <SelectPrimative.Value
                  placeholder={table.getState().pagination.pageSize}
                />
                <SelectPrimative.Icon asChild>
                  <svg
                    className={clsx(
                      "__table-pagination__select_svg",
                      selectStyles.svg,
                    )}
                    viewBox="0 0 24 24"
                    role="presentation"
                    focusable="false"
                    aria-hidden="true"
                  >
                    <path
                      fill="currentColor"
                      d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"
                    />
                  </svg>
                </SelectPrimative.Icon>
              </SelectPrimative.Trigger>
              <SelectPrimative.Content
                side="top"
                className={clsx(
                  "__table-pagination__select_content",
                  selectStyles.content,
                )}
              >
                <SelectPrimative.Viewport
                  className={clsx(
                    "__table-pagination__select_viewport",
                    selectStyles.viewport,
                  )}
                >
                  {[10, 20, 30, 40, 50, 100, 500].map((pageSize) => (
                    <SelectPrimative.Item
                      key={pageSize}
                      value={`${pageSize}`}
                      className={clsx(
                        "__table-pagination__select_item",
                        selectStyles.item,
                      )}
                    >
                      <SelectPrimative.ItemIndicator>
                        <svg
                          className={clsx(
                            "__table-pagination__select_indictator",
                            selectStyles.indicator,
                          )}
                          xmlns="http://www.w3.org/2000/svg"
                          width="24"
                          height="24"
                          viewBox="0 -960 960 960"
                        >
                          <path d="M382-354l339-339q12-12 28.5-12t28.5 12q12 12 12 28.5T778-636L410-268q-12 12-28 12t-28-12L182-440q-12-12-11.5-28.5T183-497q12-12 28.5-12t28.5 12l142 143z"></path>
                        </svg>
                      </SelectPrimative.ItemIndicator>
                      <SelectPrimative.ItemText>
                        {pageSize}
                      </SelectPrimative.ItemText>
                    </SelectPrimative.Item>
                  ))}
                </SelectPrimative.Viewport>
              </SelectPrimative.Content>
            </SelectPrimative.Root>
          </div>
        </div>
        <div
          className={clsx(
            "__table-pagination__controls_middle",
            styles.pagination_controls_middle,
          )}
        >
          Page {table.getState().pagination.pageIndex + 1} of{" "}
          {table.getPageCount()}
        </div>
        <div
          className={clsx(
            "__table-pagination__contols_right",
            styles.pagination_controls_right,
          )}
        >
          <button
            className={clsx(
              "__table-pagination__button",
              styles.pagination__button,
            )}
            onClick={() => table.setPageIndex(0)}
            disabled={!table.getCanPreviousPage()}
          >
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              viewBox="0 -960 960 960"
            >
              <path d="M440-240L200-480l240-240 56 56-183 184 183 184-56 56zm264 0L464-480l240-240 56 56-183 184 183 184-56 56z"></path>
            </svg>
            <span className={styles.sr}>Go to first page</span>
          </button>
          <button
            className={clsx(
              "__table-pagination__button",
              styles.pagination__button,
            )}
            onClick={() => table.previousPage()}
            disabled={!table.getCanPreviousPage()}
          >
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              viewBox="0 -960 960 960"
            >
              <path d="M561-240L320-481l241-241 43 43-198 198 198 198-43 43z"></path>
            </svg>
            <span className={styles.sr}>Go to previous page</span>
          </button>
          <button
            className={clsx(
              "__table-pagination__button",
              styles.pagination__button,
            )}
            onClick={() => table.nextPage()}
            disabled={!table.getCanNextPage()}
          >
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              viewBox="0 -960 960 960"
            >
              <path d="M530-481L332-679l43-43 241 241-241 241-43-43 198-198z"></path>
            </svg>
            <span className={styles.sr}>Go to next page</span>
          </button>
          <button
            className={clsx(
              "__table-pagination__button",
              styles.pagination__button,
            )}
            onClick={() => table.setPageIndex(table.getPageCount() - 1)}
            disabled={!table.getCanNextPage()}
          >
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              viewBox="0 -960 960 960"
            >
              <path d="M383-480L200-664l56-56 240 240-240 240-56-56 183-184zm264 0L464-664l56-56 240 240-240 240-56-56 183-184z"></path>
            </svg>
            <span className={styles.sr}>Go to last page</span>
          </button>
        </div>
      </div>
    </div>
  );
}

export const Checkbox = React.forwardRef<
  React.ElementRef<typeof CheckboxPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(function Checkbox({ children, checked, disabled, ...props }, ref) {
  const id = React.useId();
  return (
    <CheckboxPrimitive.Root
      ref={ref}
      data-component="Checkbox$Brevity"
      id={id}
      data-size="md"
      data-variant="solid"
      disabled={disabled}
      checked={checked}
      {...props}
    >
      <CheckboxPrimitive.Indicator data-checkbox="indicator">
        {checked === "indeterminate" && (
          <IndeterminateIcon className={checkboxStyles.icon} />
        )}
        {checked === true ? (
          <CheckboxIcon className={checkboxStyles.icon} />
        ) : null}
      </CheckboxPrimitive.Indicator>
    </CheckboxPrimitive.Root>
  );
});

declare module "@tanstack/react-table" {
  //add fuzzy filter to the filterFns
  interface FilterFns {
    fuzzy: FilterFn<unknown>;
  }
  interface FilterMeta {
    itemRank: RankingInfo;
  }
}

// Define a custom fuzzy filter function that will apply ranking info to rows (using match-sorter utils)
const fuzzyFilter: FilterFn<any> = (row, columnId, value, addMeta) => {
  // Rank the item
  const itemRank = rankItem(row.getValue(columnId), value);

  // Store the itemRank info
  addMeta({
    itemRank,
  });

  // Return if the item should be filtered in/out
  return itemRank.passed;
};
