import {
  type CellContext,
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  type HeaderContext,
  useReactTable,
  type ColumnDef,
  type FilterFn,
  type PaginationState,
  type RowSelectionState,
  type SortingState
} from "@tanstack/react-table";
import {
  forwardRef,
  useEffect,
  useState,
  type ComponentPropsWithoutRef,
  type CSSProperties,
  type ForwardedRef,
  type JSX,
  type ReactNode
} from "react";

import { Button } from "./button";
import { Checkbox } from "./checkbox";
import {
  EmptyState,
  EmptyStateActions,
  EmptyStateDescription,
  EmptyStateHeader,
  EmptyStateTitle
} from "./empty-state";
import { Input, type InputProps } from "./input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select";
import { Skeleton } from "./skeleton";
import { Spinner } from "./spinner";
import { cn } from "../lib/cn";
import type { VariantProps } from "../lib/cva";
import { createDataAttributes, createSlot } from "../lib/contracts";
import {
  SortAscendingIcon,
  SortDescendingIcon,
  SortUnsortedIcon
} from "../lib/icons";
import {
  dataTableBodyVariants,
  dataTableCellVariants,
  dataTableContentVariants,
  dataTableHeaderCellVariants,
  dataTableHeaderVariants,
  dataTablePaginationVariants,
  dataTableRootVariants,
  dataTableRowVariants,
  dataTableSearchContainerVariants,
  dataTableSelectionBarVariants,
  dataTableStatusVariants,
  dataTableTableVariants,
  dataTableToolbarVariants
} from "./data-table.variants";

export type DataTableAlignment = "start" | "center" | "end";

export type DataTableSort = {
  desc?: boolean;
  id: string;
};

export type DataTableColumnContext<TData> = {
  column: DataTableColumn<TData>;
  sorted: "asc" | "desc" | false;
};

export type DataTableColumn<TData> = {
  accessor?: keyof TData | ((row: TData) => unknown);
  align?: DataTableAlignment;
  cell?: (row: TData) => ReactNode;
  header: ReactNode | ((column: DataTableColumnContext<TData>) => ReactNode);
  id: string;
  searchValue?: (row: TData) => string;
  searchable?: boolean;
  sortable?: boolean;
  width?: number | string;
};

export type DataTableProps<TData> = Omit<ComponentPropsWithoutRef<"div">, "children"> & {
  columns: DataTableColumn<TData>[];
  defaultPageIndex?: number;
  defaultPageSize?: number;
  defaultSearchValue?: string;
  defaultSelection?: Record<string, boolean>;
  defaultSorting?: DataTableSort[];
  empty?: ReactNode;
  enableSelection?: boolean;
  getRowId?: (row: TData, index: number) => string;
  loading?: boolean;
  loadingRowCount?: number;
  onPageIndexChange?: (pageIndex: number) => void;
  onPageSizeChange?: (pageSize: number) => void;
  onSearchValueChange?: (searchValue: string) => void;
  onSelectionChange?: (selection: Record<string, boolean>) => void;
  onSortingChange?: (sorting: DataTableSort[]) => void;
  pageIndex?: number;
  pageSize?: number;
  pageSizeOptions?: number[];
  renderRowActions?: (row: TData) => ReactNode;
  rows: TData[];
  searchLabel?: string;
  searchPlaceholder?: string;
  searchValue?: string;
  selection?: Record<string, boolean>;
  selectionLabel?: ReactNode | ((rows: TData[]) => ReactNode);
  selectionActions?: (rows: TData[]) => ReactNode;
  sorting?: DataTableSort[];
  tableLabel?: string;
  toolbarActions?: ReactNode;
};

type InternalColumnMeta<TData> = {
  align: DataTableAlignment;
  sourceColumn?: DataTableColumn<TData>;
  width?: number | string;
};

function useControllableState<T>({
  controlledValue,
  defaultValue,
  onChange
}: {
  controlledValue: T | undefined;
  defaultValue: T;
  onChange?: (value: T) => void;
}) {
  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
  const value = controlledValue ?? uncontrolledValue;

  const setValue = (nextValue: T | ((currentValue: T) => T)) => {
    const resolvedValue =
      typeof nextValue === "function"
        ? (nextValue as (currentValue: T) => T)(value)
        : nextValue;

    if (controlledValue === undefined) {
      setUncontrolledValue(resolvedValue);
    }

    onChange?.(resolvedValue);
  };

  return [value, setValue] as const;
}

function stringifySearchValue(value: unknown): string {
  if (value === null || value === undefined) {
    return "";
  }

  if (Array.isArray(value)) {
    return value.map((item) => stringifySearchValue(item)).join(" ");
  }

  if (value instanceof Date) {
    return value.toISOString();
  }

  if (typeof value === "object") {
    return Object.values(value as Record<string, unknown>)
      .map((item) => stringifySearchValue(item))
      .join(" ");
  }

  return String(value);
}

function getColumnAccessorValue<TData>(row: TData, column: DataTableColumn<TData>) {
  if (typeof column.accessor === "function") {
    return column.accessor(row);
  }

  if (typeof column.accessor === "string") {
    return row[column.accessor];
  }

  return undefined;
}

function getColumnSearchText<TData>(row: TData, column: DataTableColumn<TData>) {
  if (column.searchValue) {
    return column.searchValue(row);
  }

  return stringifySearchValue(getColumnAccessorValue(row, column));
}

function getColumnWidthStyle(width?: number | string): CSSProperties | undefined {
  if (width === undefined) {
    return undefined;
  }

  return {
    width: typeof width === "number" ? `${width}px` : width
  };
}

function getHeaderLabel<TData>(column: DataTableColumn<TData>) {
  return typeof column.header === "string" ? column.header : column.id;
}

function DataTableInner<TData>(
  {
    className,
    columns,
    defaultPageIndex = 0,
    defaultPageSize = 5,
    defaultSearchValue = "",
    defaultSelection = {},
    defaultSorting = [],
    empty,
    enableSelection = false,
    getRowId,
    loading = false,
    loadingRowCount = 5,
    onPageIndexChange,
    onPageSizeChange,
    onSearchValueChange,
    onSelectionChange,
    onSortingChange,
    pageIndex,
    pageSize,
    pageSizeOptions = [5, 10, 20],
    renderRowActions,
    rows,
    searchLabel = "Search rows",
    searchPlaceholder = "Search rows",
    searchValue,
    selection,
    selectionLabel,
    selectionActions,
    sorting,
    tableLabel = "Data table",
    toolbarActions,
    ...props
  }: DataTableProps<TData>,
  ref: ForwardedRef<HTMLDivElement>
) {
  const selectionEnabled = enableSelection || selection !== undefined || onSelectionChange !== undefined;
  const searchableColumns = columns.filter(
    (column) => (column.searchable ?? false) || column.searchValue || column.accessor
  );

  const [currentSearchValue, setCurrentSearchValue] = useControllableState({
    controlledValue: searchValue,
    defaultValue: defaultSearchValue,
    onChange: onSearchValueChange
  });
  const [currentSorting, setCurrentSorting] = useControllableState<DataTableSort[]>({
    controlledValue: sorting,
    defaultValue: defaultSorting,
    onChange: onSortingChange
  });
  const [currentSelection, setCurrentSelection] = useControllableState<Record<string, boolean>>({
    controlledValue: selection,
    defaultValue: defaultSelection,
    onChange: onSelectionChange
  });
  const [currentPageIndex, setCurrentPageIndex] = useControllableState({
    controlledValue: pageIndex,
    defaultValue: defaultPageIndex,
    onChange: onPageIndexChange
  });
  const [currentPageSize, setCurrentPageSize] = useControllableState({
    controlledValue: pageSize,
    defaultValue: defaultPageSize,
    onChange: onPageSizeChange
  });

  const resolvedPageSizeOptions = [...new Set([...pageSizeOptions, currentPageSize])].sort(
    (left, right) => left - right
  );

  const globalFilterFn: FilterFn<TData> = (row, _columnId, filterValue) => {
    const query = String(filterValue ?? "").trim().toLowerCase();

    if (query.length === 0) {
      return true;
    }

    return searchableColumns.some((column) =>
      getColumnSearchText(row.original, column).toLowerCase().includes(query)
    );
  };

  const tableColumns: ColumnDef<TData>[] = [
    ...(selectionEnabled
      ? [
          {
            cell: ({ row }) => (
              <Checkbox
                aria-label={`Select row ${row.index + 1}`}
                checked={row.getIsSelected()}
                onCheckedChange={(checked) => {
                  row.toggleSelected(Boolean(checked));
                }}
              />
            ),
            enableGlobalFilter: false,
            enableHiding: false,
            enableSorting: false,
            header: ({ table }) => (
              <Checkbox
                aria-label="Select all rows"
                checked={table.getIsAllPageRowsSelected()}
                onCheckedChange={(checked) => {
                  table.toggleAllPageRowsSelected(Boolean(checked));
                }}
              />
            ),
            id: "__select",
            meta: {
              align: "center",
              width: 56
            } satisfies InternalColumnMeta<TData>
          } satisfies ColumnDef<TData>
        ]
      : []),
    ...columns.map((column) => ({
      accessorFn: column.accessor
        ? (row: TData) => getColumnAccessorValue(row, column)
        : undefined,
      cell: ({ row }: CellContext<TData, unknown>) =>
        column.cell
          ? column.cell(row.original)
          : stringifySearchValue(getColumnAccessorValue(row.original, column)),
      enableGlobalFilter: searchableColumns.includes(column),
      enableSorting: column.sortable ?? false,
      header: ({ column: tanstackColumn }: HeaderContext<TData, unknown>) =>
        typeof column.header === "function"
          ? column.header({
              column,
              sorted: tanstackColumn.getIsSorted()
            })
          : column.header,
      id: column.id,
      meta: {
        align: column.align ?? "start",
        sourceColumn: column,
        width: column.width
      } satisfies InternalColumnMeta<TData>,
      sortDescFirst: false
    })),
    ...(renderRowActions
      ? [
          {
            cell: ({ row }) => (
              <div {...createSlot("actions")} className="flex items-center justify-end">
                {renderRowActions(row.original)}
              </div>
            ),
            enableGlobalFilter: false,
            enableHiding: false,
            enableSorting: false,
            header: () => <span className="sr-only">Row actions</span>,
            id: "__actions",
            meta: {
              align: "end",
              width: 88
            } satisfies InternalColumnMeta<TData>
          } satisfies ColumnDef<TData>
        ]
      : [])
  ];

  // eslint-disable-next-line react-hooks/incompatible-library -- TanStack Table is the intentional row-model engine behind this source-owned component.
  const table = useReactTable({
    autoResetPageIndex: true,
    columns: tableColumns,
    data: rows,
    enableRowSelection: selectionEnabled,
    getCoreRowModel: getCoreRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    getRowId: (row, index) => {
      if (getRowId) {
        return getRowId(row, index);
      }

      const candidateId = (row as { id?: unknown }).id;

      if (typeof candidateId === "string" || typeof candidateId === "number") {
        return String(candidateId);
      }

      return String(index);
    },
    getSortedRowModel: getSortedRowModel(),
    globalFilterFn,
    onPaginationChange: (updater) => {
      const nextValue =
        typeof updater === "function"
          ? updater({
              pageIndex: currentPageIndex,
              pageSize: currentPageSize
            })
          : updater;

      setCurrentPageIndex(nextValue.pageIndex);
      setCurrentPageSize(nextValue.pageSize);
    },
    onRowSelectionChange: (updater) => {
      const nextValue =
        typeof updater === "function"
          ? updater(currentSelection as RowSelectionState)
          : updater;

      setCurrentSelection(nextValue);
    },
    onSortingChange: (updater) => {
      const nextValue =
        typeof updater === "function"
          ? updater(currentSorting as SortingState)
          : updater;

      setCurrentSorting(nextValue.map((item) => ({ desc: item.desc, id: item.id })));
    },
    state: {
      globalFilter: currentSearchValue,
      pagination: {
        pageIndex: currentPageIndex,
        pageSize: currentPageSize
      } satisfies PaginationState,
      rowSelection: currentSelection,
      sorting: currentSorting as SortingState
    }
  });

  const totalColumns = table.getAllLeafColumns().length;
  const filteredRowCount = table.getFilteredRowModel().rows.length;
  const selectedRows = table.getSelectedRowModel().rows.map((row) => row.original);
  const pageCount = table.getPageCount();
  const isEmpty = !loading && filteredRowCount === 0;
  const shouldRenderSearch = searchableColumns.length > 0;
  const shouldRenderToolbar = shouldRenderSearch || toolbarActions !== undefined;
  const pageStart =
    filteredRowCount === 0 ? 0 : currentPageIndex * currentPageSize + 1;
  const pageEnd = Math.min((currentPageIndex + 1) * currentPageSize, filteredRowCount);

  useEffect(() => {
    const maxPageIndex = Math.max(pageCount - 1, 0);

    if (currentPageIndex > maxPageIndex) {
      setCurrentPageIndex(maxPageIndex);
    }
  }, [currentPageIndex, pageCount, setCurrentPageIndex]);

  return (
    <div
      {...props}
      {...createSlot("root")}
      {...createDataAttributes({
        empty: isEmpty,
        loading,
        selected: selectedRows.length > 0
      })}
      className={cn(dataTableRootVariants(), className)}
      ref={ref}
    >
      {shouldRenderToolbar ? (
        <DataTableToolbar>
          <div className="flex flex-1 flex-wrap items-center gap-3">
            {shouldRenderSearch ? (
              <div className={dataTableSearchContainerVariants()}>
                <DataTableSearch
                  aria-label={searchLabel}
                  onChange={(event) => {
                    setCurrentSearchValue(event.currentTarget.value);
                    setCurrentPageIndex(0);
                  }}
                  placeholder={searchPlaceholder}
                  value={currentSearchValue}
                />
              </div>
            ) : null}
          </div>
          {toolbarActions ? <DataTableFilters>{toolbarActions}</DataTableFilters> : null}
        </DataTableToolbar>
      ) : null}

      {selectionEnabled && selectedRows.length > 0 ? (
        <DataTableSelectionBar>
          <div className="text-sm font-medium text-[var(--color-foreground)]">
            {typeof selectionLabel === "function"
              ? selectionLabel(selectedRows)
              : selectionLabel ?? `${selectedRows.length} selected`}
          </div>
          <div {...createSlot("actions")} className="flex flex-wrap items-center gap-2">
            {selectionActions?.(selectedRows)}
            <Button
              size="sm"
              variant="ghost"
              onClick={() => {
                table.resetRowSelection();
              }}
            >
              Clear selection
            </Button>
          </div>
        </DataTableSelectionBar>
      ) : null}

      <DataTableContent loading={loading}>
        {loading ? <DataTableLoading className="sr-only min-h-0 px-0 py-0" /> : null}
        <div className="overflow-x-auto">
          <DataTableTable aria-label={tableLabel}>
            <DataTableHeader>
              {table.getHeaderGroups().map((headerGroup) => (
                <tr key={headerGroup.id}>
                  {headerGroup.headers.map((header) => {
                    const meta = header.column.columnDef.meta as InternalColumnMeta<TData> | undefined;
                    const sortState = header.column.getIsSorted();
                    const align: DataTableAlignment = meta?.align ?? "start";

                    return (
                      <DataTableHeaderCell
                        key={header.id}
                        align={align}
                        aria-sort={
                          sortState === "asc"
                            ? "ascending"
                            : sortState === "desc"
                              ? "descending"
                              : undefined
                        }
                        scope="col"
                        sortable={header.column.getCanSort()}
                        sort={sortState}
                        style={getColumnWidthStyle(meta?.width)}
                      >
                        {header.isPlaceholder ? null : header.column.getCanSort() ? (
                          <button
                            className={[
                              "inline-flex w-full items-center gap-2 rounded-[var(--radius-sm)] px-2 py-1.5",
                              "outline-none transition-colors duration-200 hover:bg-[var(--color-surface)]",
                              "focus-visible:ring-2 focus-visible:ring-[var(--color-ring)]",
                              align === "end" ? "justify-end" : align === "center" ? "justify-center" : "justify-start"
                            ].join(" ")}
                            onClick={header.column.getToggleSortingHandler()}
                            type="button"
                          >
                            <span>{flexRender(header.column.columnDef.header, header.getContext())}</span>
                            <span
                              aria-hidden="true"
                              className="inline-flex items-center justify-center text-[var(--color-muted-foreground)]"
                            >
                              {sortState === "asc" ? (
                                <SortAscendingIcon className="size-3.5" />
                              ) : sortState === "desc" ? (
                                <SortDescendingIcon className="size-3.5" />
                              ) : (
                                <SortUnsortedIcon className="size-3.5" />
                              )}
                            </span>
                            <span className="sr-only">
                              Sort by{" "}
                              {meta?.sourceColumn ? getHeaderLabel(meta.sourceColumn) : header.column.id}
                            </span>
                          </button>
                        ) : (
                          flexRender(header.column.columnDef.header, header.getContext())
                        )}
                      </DataTableHeaderCell>
                    );
                  })}
                </tr>
              ))}
            </DataTableHeader>

            <DataTableBody>
              {loading
                ? Array.from({ length: loadingRowCount }, (_, index) => (
                    <DataTableRow
                      interactive={false}
                      key={`loading-row-${index}`}
                    >
                      {Array.from({ length: totalColumns }, (_, cellIndex) => (
                        <DataTableCell key={`loading-cell-${cellIndex}`}>
                          <Skeleton
                            className={cellIndex === totalColumns - 1 ? "ml-auto w-12" : undefined}
                          />
                        </DataTableCell>
                      ))}
                    </DataTableRow>
                  ))
                : isEmpty
                  ? (
                      <tr>
                        <td colSpan={totalColumns}>
                          {empty ?? (
                            <DataTableEmpty
                              description="Try another search, clear filters, or create a new record to repopulate this view."
                              title="No matching rows"
                            />
                          )}
                        </td>
                      </tr>
                    )
                  : table.getRowModel().rows.map((row) => (
                      <DataTableRow
                        data-selected={row.getIsSelected() ? "" : undefined}
                        key={row.id}
                        selected={row.getIsSelected()}
                      >
                        {row.getVisibleCells().map((cell) => {
                          const meta = cell.column.columnDef.meta as InternalColumnMeta<TData> | undefined;
                          const align: DataTableAlignment = meta?.align ?? "start";

                          return (
                            <DataTableCell
                              align={align}
                              key={cell.id}
                              style={getColumnWidthStyle(meta?.width)}
                            >
                              {flexRender(cell.column.columnDef.cell, cell.getContext())}
                            </DataTableCell>
                          );
                        })}
                      </DataTableRow>
                    ))}
            </DataTableBody>
          </DataTableTable>
        </div>

        <DataTablePagination>
          <div className="text-sm text-[var(--color-muted-foreground)]">
            {pageStart}-{pageEnd} of {filteredRowCount}
          </div>

          <div className="flex flex-wrap items-center gap-3">
            {resolvedPageSizeOptions.length > 1 ? (
              <div className="flex items-center gap-2">
                <span className="text-sm text-[var(--color-muted-foreground)]">Rows</span>
                <Select
                  value={String(currentPageSize)}
                  onValueChange={(value) => {
                    setCurrentPageSize(Number(value));
                    setCurrentPageIndex(0);
                  }}
                >
                  <SelectTrigger aria-label="Rows per page" className="w-[5.5rem]">
                    <SelectValue placeholder={String(currentPageSize)} />
                  </SelectTrigger>
                  <SelectContent>
                    {resolvedPageSizeOptions.map((option) => (
                      <SelectItem key={option} value={String(option)}>
                        {option}
                      </SelectItem>
                    ))}
                  </SelectContent>
                </Select>
              </div>
            ) : null}

            <div className="text-sm text-[var(--color-muted-foreground)]">
              Page {Math.min(currentPageIndex + 1, Math.max(pageCount, 1))} of {Math.max(pageCount, 1)}
            </div>

            <div className="flex items-center gap-2">
              <Button
                disabled={!table.getCanPreviousPage()}
                size="sm"
                variant="ghost"
                onClick={() => {
                  table.previousPage();
                }}
              >
                Previous
              </Button>
              <Button
                disabled={!table.getCanNextPage()}
                size="sm"
                variant="ghost"
                onClick={() => {
                  table.nextPage();
                }}
              >
                Next
              </Button>
            </div>
          </div>
        </DataTablePagination>
      </DataTableContent>
    </div>
  );
}

type DataTableComponent = <TData>(
  props: DataTableProps<TData> & { ref?: ForwardedRef<HTMLDivElement> }
) => JSX.Element;

export const DataTable = forwardRef(DataTableInner) as DataTableComponent;

export type DataTableToolbarProps = ComponentPropsWithoutRef<"div">;

export const DataTableToolbar = forwardRef<HTMLDivElement, DataTableToolbarProps>(
  function DataTableToolbar({ className, ...props }, ref) {
    return (
      <div
        {...props}
        {...createSlot("toolbar")}
        className={cn(dataTableToolbarVariants(), className)}
        ref={ref}
      />
    );
  }
);

export type DataTableFiltersProps = ComponentPropsWithoutRef<"div">;

export const DataTableFilters = forwardRef<HTMLDivElement, DataTableFiltersProps>(
  function DataTableFilters({ className, ...props }, ref) {
    return (
      <div
        {...props}
        {...createSlot("actions")}
        className={cn("flex flex-wrap items-center gap-2", className)}
        ref={ref}
      />
    );
  }
);

export type DataTableSearchProps = Omit<InputProps, "size">;

export const DataTableSearch = forwardRef<HTMLInputElement, DataTableSearchProps>(
  function DataTableSearch({ className, type = "search", ...props }, ref) {
    return <Input {...props} className={className} ref={ref} type={type} />;
  }
);

export type DataTableContentProps = ComponentPropsWithoutRef<"div"> &
  VariantProps<typeof dataTableContentVariants>;

export const DataTableContent = forwardRef<HTMLDivElement, DataTableContentProps>(
  function DataTableContent({ className, loading, ...props }, ref) {
    return (
      <div
        {...props}
        {...createSlot("content")}
        {...createDataAttributes({ loading })}
        className={cn(dataTableContentVariants({ loading }), className)}
        ref={ref}
      />
    );
  }
);

export type DataTableTableProps = ComponentPropsWithoutRef<"table">;

export const DataTableTable = forwardRef<HTMLTableElement, DataTableTableProps>(
  function DataTableTable({ className, ...props }, ref) {
    return (
      <table
        {...props}
        {...createSlot("table")}
        className={cn(dataTableTableVariants(), className)}
        ref={ref}
      />
    );
  }
);

export type DataTableHeaderProps = ComponentPropsWithoutRef<"thead">;

export const DataTableHeader = forwardRef<HTMLTableSectionElement, DataTableHeaderProps>(
  function DataTableHeader({ className, ...props }, ref) {
    return (
      <thead
        {...props}
        {...createSlot("header")}
        className={cn(dataTableHeaderVariants(), className)}
        ref={ref}
      />
    );
  }
);

export type DataTableHeaderCellProps = Omit<ComponentPropsWithoutRef<"th">, "align"> &
  VariantProps<typeof dataTableHeaderCellVariants> & {
    sort?: "asc" | "desc" | false;
  };

export const DataTableHeaderCell = forwardRef<HTMLTableCellElement, DataTableHeaderCellProps>(
  function DataTableHeaderCell(
    { align = "start", className, sort = false, sortable = false, ...props },
    ref
  ) {
    return (
      <th
        {...props}
        {...createSlot("header")}
        {...createDataAttributes({
          sort: sort || undefined
        })}
        className={cn(dataTableHeaderCellVariants({ align, sortable }), className)}
        ref={ref}
      />
    );
  }
);

export type DataTableBodyProps = ComponentPropsWithoutRef<"tbody">;

export const DataTableBody = forwardRef<HTMLTableSectionElement, DataTableBodyProps>(
  function DataTableBody({ className, ...props }, ref) {
    return (
      <tbody
        {...props}
        className={cn(dataTableBodyVariants(), className)}
        ref={ref}
      />
    );
  }
);

export type DataTableRowProps = ComponentPropsWithoutRef<"tr"> &
  VariantProps<typeof dataTableRowVariants>;

export const DataTableRow = forwardRef<HTMLTableRowElement, DataTableRowProps>(
  function DataTableRow(
    { className, interactive = true, selected = false, ...props },
    ref
  ) {
    return (
      <tr
        {...props}
        {...createSlot("row")}
        {...createDataAttributes({
          selected
        })}
        className={cn(dataTableRowVariants({ interactive, selected }), className)}
        ref={ref}
      />
    );
  }
);

export type DataTableCellProps = Omit<ComponentPropsWithoutRef<"td">, "align"> &
  VariantProps<typeof dataTableCellVariants>;

export const DataTableCell = forwardRef<HTMLTableCellElement, DataTableCellProps>(
  function DataTableCell({ align = "start", className, ...props }, ref) {
    return (
      <td
        {...props}
        {...createSlot("cell")}
        className={cn(dataTableCellVariants({ align }), className)}
        ref={ref}
      />
    );
  }
);

export type DataTableEmptyProps = ComponentPropsWithoutRef<"div"> & {
  actions?: ReactNode;
  description?: ReactNode;
  title?: ReactNode;
};

export const DataTableEmpty = forwardRef<HTMLDivElement, DataTableEmptyProps>(
  function DataTableEmpty(
    {
      actions,
      className,
      description = "No rows match the current search or filter state.",
      title = "Nothing to show",
      ...props
    },
    ref
  ) {
    return (
      <div
        {...props}
        {...createSlot("empty")}
        className={cn(dataTableStatusVariants(), className)}
        ref={ref}
      >
        <EmptyState className="w-full border-none bg-transparent p-0 shadow-none">
          <EmptyStateHeader>
            <EmptyStateTitle>{title}</EmptyStateTitle>
            <EmptyStateDescription>{description}</EmptyStateDescription>
          </EmptyStateHeader>
          {actions ? <EmptyStateActions>{actions}</EmptyStateActions> : null}
        </EmptyState>
      </div>
    );
  }
);

export type DataTableLoadingProps = ComponentPropsWithoutRef<"div"> & {
  description?: ReactNode;
  label?: ReactNode;
};

export const DataTableLoading = forwardRef<HTMLDivElement, DataTableLoadingProps>(
  function DataTableLoading(
    {
      className,
      description = "Preparing the next set of rows and controls.",
      label = "Loading rows",
      ...props
    },
    ref
  ) {
    return (
      <div
        {...props}
        className={cn(dataTableStatusVariants(), className)}
        ref={ref}
        role="status"
      >
        <div className="grid justify-items-center gap-3 text-center">
          <Spinner aria-label={String(label)} size="lg" tone="primary" />
          <div className="space-y-1">
            <p className="text-sm font-medium text-[var(--color-foreground)]">{label}</p>
            <p className="text-sm leading-6 text-[var(--color-muted-foreground)]">
              {description}
            </p>
          </div>
        </div>
      </div>
    );
  }
);

export type DataTablePaginationProps = ComponentPropsWithoutRef<"div">;

export const DataTablePagination = forwardRef<HTMLDivElement, DataTablePaginationProps>(
  function DataTablePagination({ className, ...props }, ref) {
    return (
      <div
        {...props}
        {...createSlot("pagination")}
        className={cn(dataTablePaginationVariants(), className)}
        ref={ref}
      />
    );
  }
);

export type DataTableSelectionBarProps = ComponentPropsWithoutRef<"div">;

export const DataTableSelectionBar = forwardRef<HTMLDivElement, DataTableSelectionBarProps>(
  function DataTableSelectionBar({ className, ...props }, ref) {
    return (
      <div
        {...props}
        className={cn(dataTableSelectionBarVariants(), className)}
        ref={ref}
      />
    );
  }
);
