import {
  Flex,
  Paper,
  Stack,
  Table,
  type MantineStyleProp,
} from "@mantine/core";
import { useDebouncedValue } from "@mantine/hooks";
import "@mantine/core/styles.css";
import "@mantine/dates/styles.css";
import { IconSearch } from "@tabler/icons-react";
import type { MRT_ColumnDef, MRT_RowData } from "mantine-react-table";
import {
  flexRender,
  MRT_GlobalFilterTextInput,
  MRT_TableBodyCellValue,
  MRT_TablePagination,
  MRT_ToolbarAlertBanner,
  useMantineReactTable,
} from "mantine-react-table";
import { useEffect, useState } from "react";
import { useLocation, useNavigate } from "react-router";
import "./table.module.css";

interface Props<TData extends MRT_RowData> {
  data: TData[];
  totalRows: number;
  columns: MRT_ColumnDef<TData, unknown>[];
  onRowClick?: (row: MRT_RowData) => void;
  needPagination?: boolean;
  bodyRowStyle?: (row: MRT_RowData) => MantineStyleProp;
  enableSearch?: boolean;
  /**
   * Whether the search box is answered by the server.
   *
   * Off by default because most callers of this table hand it a full result set and
   * rely on mantine-react-table's own filtering. Callers whose loader reads the
   * `search` param must turn it on: otherwise MRT filters the server's already
   * filtered page a second time, against only the columns the table renders — so a
   * row matched on a column that isn't displayed (a username, say) is fetched and
   * then hidden again, and the search looks broken.
   */
  manualFiltering?: boolean;
  enablePinning?: boolean;
  enableRowSelection?: boolean;
  rowSelection?: any;
  onRowSelectionChange?: any;
}
export function BuildTable<TData extends MRT_RowData>({
  data,
  totalRows,
  columns,
  onRowClick,
  needPagination = true,
  bodyRowStyle,
  enableSearch = true,
  manualFiltering = false,
  enablePinning = false,
  enableRowSelection = true,
  rowSelection,
  onRowSelectionChange,
}: Props<TData>) {
  const { search } = useLocation();
  const getQueryParams = () => new URLSearchParams(search);
  const [pagination, setPagination] = useState(() => {
    const q = getQueryParams();
    return {
      pageIndex: (Number(q.get("page")) || 1) - 1,
      pageSize: Number(q.get("pageSize")) || 50,
    };
  });
  // Seeded from the URL so a reload or a shared link shows the term it filtered by,
  // rather than an empty box over filtered rows.
  const [globalFilter, setGlobalFilter] = useState(
    () => getQueryParams().get("search") ?? "",
  );
  // The term goes into the URL, and the URL drives a loader refetch — so without a
  // debounce every keystroke was a navigation and a round trip.
  const [debouncedFilter] = useDebouncedValue(globalFilter ?? "", 400);
  const navigate = useNavigate();

  useEffect(() => {
    if (!needPagination) return;
    const q = getQueryParams();
    const term = (debouncedFilter ?? "").trim();
    const termChanged = term !== (q.get("search") ?? "");

    /*
     * A new term means a different result set, so the current page number no longer
     * refers to anything. Searching from page 3 used to keep `page=3` and land on an
     * empty page — the rows were found and then paged past.
     */
    const pageIndex = termChanged ? 0 : pagination.pageIndex;
    if (termChanged && pagination.pageIndex !== 0) {
      setPagination((prev) => ({ ...prev, pageIndex: 0 }));
    }

    q.set("page", String(pageIndex + 1));
    q.set("pageSize", String(pagination.pageSize));
    if (term) q.set("search", term);
    else q.delete("search");

    // Nothing to navigate to. Without this the mount pass pushes the params it just
    // read back out again.
    if (q.toString() === new URLSearchParams(search).toString()) return;

    // `replace` because typing is not navigation: otherwise Back walks the search box
    // backwards one debounce at a time.
    void navigate({ search: q.toString() }, { replace: true });
  }, [pagination.pageIndex, pagination.pageSize, debouncedFilter]);

  const table = useMantineReactTable({
    columns,
    data,
    enableSorting: true,
    enableRowSelection,
    getRowId: (row: any) => row.id,
    initialState: {
      showGlobalFilter: true,
      columnOrder: [
        ...(enableRowSelection ? ["mrt-row-select"] : []),
        ...columns.map((c) => c.id ?? c.accessorKey ?? ""),
      ],
    },
    mantinePaginationProps: {
      /*
       * Smaller sizes first, because 50 was the minimum and several of these tables
       * hold fewer rows than that — console users is 47 — so there was only ever one
       * page and the pager looked inert.
       */
      rowsPerPageOptions: ["10", "25", "50", "100", "150"],
      p: 0,
    },
    paginationDisplayMode: "pages",
    manualPagination: true,
    manualFiltering,
    rowCount: totalRows,
    onPaginationChange: setPagination,
    onRowSelectionChange: onRowSelectionChange,
    onGlobalFilterChange: setGlobalFilter,
    state: { pagination, globalFilter, rowSelection: rowSelection ?? {} },
    mantineSelectCheckboxProps: { color: "blue", size: "xs" },
    mantineSelectAllCheckboxProps: { color: "blue", size: "xs" },
    mantineSearchTextInputProps: {
      size: "xs",
      fz: "sm",
      ...(!enableSearch ? { display: "none" } : {}),
    },
    enablePinning: enablePinning,
  });


  return (
    <Stack>
      <Flex justify="space-between" align="center">
        <MRT_GlobalFilterTextInput
          table={table}
          variant="default"
          size="md"
          leftSection={<IconSearch size={18} />}
          c={"dark.8"}
          p={8}
        />
      </Flex>
      <Paper
        bdrs={4}
        style={{
          overflow: "hidden",
          border: "1px solid var(--mantine-color-white-2)",
        }}
      >
        <Table
          captionSide="top"
          fz={11}
          highlightOnHover
          horizontalSpacing="xl"
          verticalSpacing="xs"
          withColumnBorders
          m="0"
        >
          <Table.Thead>
            {table.getHeaderGroups().map((headerGroup) => (
              <Table.Tr key={headerGroup.id} c={"dark.4"} bg={"gray.1"}>
                {headerGroup.headers.map((header) => (
                  <Table.Th key={header.id} px={12}>
                    {header.isPlaceholder
                      ? null
                      : flexRender(
                        header.column.columnDef.Header ??
                        header.column.columnDef.header,
                        header.getContext(),
                      )}
                  </Table.Th>
                ))}
              </Table.Tr>
            ))}
          </Table.Thead>
          <Table.Tbody>
            {table.getRowModel().rows.map((row) => (
              <Table.Tr
                key={row.id}
                style={{
                  cursor: onRowClick ? "pointer" : "default",
                  ...(bodyRowStyle ? bodyRowStyle(row.original) : {}),
                }}
              >
                {row.getVisibleCells().map((cell) => (
                  <Table.Td
                    key={cell.id}
                    px={12}
                    onClick={() =>
                      onRowClick ? onRowClick(row.original) : null
                    }
                  >
                    <MRT_TableBodyCellValue cell={cell} table={table} />
                  </Table.Td>
                ))}
              </Table.Tr>
            ))}
          </Table.Tbody>
        </Table>
      </Paper>
      {needPagination && <MRT_TablePagination table={table} />}
      <MRT_ToolbarAlertBanner stackAlertBanner table={table} />
    </Stack>
  );
}
