import { useMemo, useRef, useCallback, useState, useEffect } from "react";
import { useAtom, useSetAtom } from "jotai";
import { useThrottledCallback } from "use-debounce";
import { useSnackbar } from "notistack";
import useMemoValue from "use-memo-value";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { isEmpty, isEqual } from "lodash-es";

import {
  createColumnHelper,
  flexRender,
  getCoreRowModel,
  useReactTable,
} from "@tanstack/react-table";

import { StyledTable } from "./Styled/StyledTable";
import { StyledRow } from "./Styled/StyledRow";
import { StyledResizer } from "./Styled/StyledResizer";
import ColumnHeaderComponent from "./Column";
import OutOfOrderIndicator from "./OutOfOrderIndicator";

import { IconButton } from "@mui/material";

import ColumnHeader, { COLUMN_HEADER_HEIGHT } from "./ColumnHeader";
import FinalColumnHeader from "./FinalColumnHeader";
import FinalColumn from "./formatters/FinalColumn";
// import TableRow from "./TableRow";
import EmptyState from "@src/components/EmptyState";
// import BulkActions from "./BulkActions";
import AddRow from "@src/components/TableToolbar/AddRow";
import { AddRow as AddRowIcon } from "@src/assets/icons";
import Loading from "@src/components/Loading";
import ContextMenu from "./ContextMenu";

import {
  projectScope,
  userRolesAtom,
  userSettingsAtom,
} from "@src/atoms/projectScope";
import {
  tableScope,
  tableIdAtom,
  tableSettingsAtom,
  tableSchemaAtom,
  tableColumnsOrderedAtom,
  tableRowsAtom,
  tableNextPageAtom,
  tablePageAtom,
  updateColumnAtom,
  updateFieldAtom,
  selectedCellAtom,
} from "@src/atoms/tableScope";
import { COLLECTION_PAGE_SIZE } from "@src/config/db";

import { getFieldType, getFieldProp } from "@src/components/fields";
import { FieldType } from "@src/constants/fields";
import { formatSubTableName } from "@src/utils/table";
import { TableRow, ColumnConfig } from "@src/types/table";
import { StyledCell } from "./Styled/StyledCell";
import { useKeyboardNavigation } from "./useKeyboardNavigation";
import { useSaveColumnSizing } from "./useSaveColumnSizing";
import useVirtualization from "./useVirtualization";

export const DEFAULT_ROW_HEIGHT = 41;
export const DEFAULT_COL_WIDTH = 150;
export const MIN_COL_WIDTH = 80;
export const TABLE_PADDING = 16;
export const OUT_OF_ORDER_MARGIN = 8;
export const DEBOUNCE_DELAY = 500;

declare module "@tanstack/table-core" {
  interface ColumnMeta<TData, TValue> extends ColumnConfig {}
}

const columnHelper = createColumnHelper<TableRow>();
const getRowId = (row: TableRow) => row._rowy_ref.path || row._rowy_ref.id;

export default function TableComponent() {
  const [userRoles] = useAtom(userRolesAtom, projectScope);
  const [userSettings] = useAtom(userSettingsAtom, projectScope);

  const [tableId] = useAtom(tableIdAtom, tableScope);
  const [tableSettings] = useAtom(tableSettingsAtom, tableScope);
  const [tableSchema] = useAtom(tableSchemaAtom, tableScope);
  const [tableColumnsOrdered] = useAtom(tableColumnsOrderedAtom, tableScope);
  const [tableRows] = useAtom(tableRowsAtom, tableScope);
  const [tableNextPage] = useAtom(tableNextPageAtom, tableScope);
  const [tablePage, setTablePage] = useAtom(tablePageAtom, tableScope);
  const [selectedCell, setSelectedCell] = useAtom(selectedCellAtom, tableScope);

  const updateColumn = useSetAtom(updateColumnAtom, tableScope);
  const updateField = useSetAtom(updateFieldAtom, tableScope);

  const containerRef = useRef<HTMLDivElement>(null);
  const gridRef = useRef<HTMLDivElement>(null);

  const canAddColumn = userRoles.includes("ADMIN");
  const canEditColumn = userRoles.includes("ADMIN");

  // Get column defs from table schema
  // Also add end column for admins
  const columns = useMemo(() => {
    const _columns = tableColumnsOrdered
      // Hide column for all users using table schema
      .filter((column) => !column.hidden)
      .map((columnConfig) =>
        columnHelper.accessor(columnConfig.fieldName, {
          id: columnConfig.fieldName,
          meta: columnConfig,
          size: columnConfig.width,
          enableResizing: columnConfig.resizable !== false,
          minSize: MIN_COL_WIDTH,
          // draggable: true,
          // resizable: true,
          // frozen: columnConfig.fixed,
          // headerRenderer: ColumnHeader,
          // formatter:
          //   getFieldProp("TableCell", getFieldType(columnConfig)) ??
          //   function InDev() {
          //     return null;
          //   },
          // editor:
          //   getFieldProp("TableEditor", getFieldType(columnConfig)) ??
          //   function InDev() {
          //     return null;
          //   },
          // ...columnConfig,
          // editable:
          //   tableSettings.readOnly && !userRoles.includes("ADMIN")
          //     ? false
          //     : columnConfig.editable ?? true,
          // width: columnConfig.width ?? DEFAULT_COL_WIDTH,
        })
      );

    if (canAddColumn || !tableSettings.readOnly) {
      _columns.push(
        columnHelper.display({
          id: "_rowy_column_actions",
          header: () => "Actions",
          // cell: () => (
          //   <>
          //     <IconButton>M</IconButton>
          //     <IconButton>D</IconButton>
          //     <IconButton>X</IconButton>
          //   </>
          // ),
        })
      );
    }

    return _columns;
  }, [tableColumnsOrdered, canAddColumn, tableSettings.readOnly]);

  // Get user’s hidden columns from user document
  const userDocHiddenFields =
    userSettings.tables?.[formatSubTableName(tableId)]?.hiddenFields;
  // Memoize into a VisibilityState
  const columnVisibility = useMemo(() => {
    if (!Array.isArray(userDocHiddenFields)) return {};
    return userDocHiddenFields.reduce((a, c) => ({ ...a, [c]: false }), {});
  }, [userDocHiddenFields]);

  // Get frozen columns
  const columnPinning = useMemo(
    () => ({
      left: columns.filter((c) => c.meta?.fixed && c.id).map((c) => c.id!),
    }),
    [columns]
  );
  const lastFrozen: string | undefined =
    columnPinning.left[columnPinning.left.length - 1];

  // Call TanStack Table
  const table = useReactTable({
    data: tableRows,
    columns,
    getCoreRowModel: getCoreRowModel(),
    getRowId,
    columnResizeMode: "onChange",
  });

  const [columnSizing, setColumnSizing] = useState(
    table.initialState.columnSizing
  );
  table.setOptions((prev) => ({
    ...prev,
    state: { ...prev.state, columnVisibility, columnPinning, columnSizing },
    onColumnSizingChange: setColumnSizing,
  }));

  const { rows } = table.getRowModel();
  const leafColumns = table.getVisibleLeafColumns();
  // console.log(table, selectedCell);

  const { handleKeyDown, focusInsideCell } = useKeyboardNavigation({
    gridRef,
    tableRows,
    leafColumns,
  });
  const {
    virtualRows,
    // totalHeight,
    // scrollToRowIndex,
    virtualCols,
    // totalWidth,
    // scrollToColIndex,
    paddingTop,
    paddingBottom,
    paddingLeft,
    paddingRight,
  } = useVirtualization(containerRef, leafColumns);
  useSaveColumnSizing(columnSizing, canEditColumn);

  const fetchMoreOnBottomReached = useThrottledCallback(
    (containerElement?: HTMLDivElement | null) => {
      if (!containerElement) return;

      const { scrollHeight, scrollTop, clientHeight } = containerElement;
      if (scrollHeight - scrollTop - clientHeight < 300) {
        setTablePage((p) => p + 1);
      }
    },
    DEBOUNCE_DELAY
  );
  // Check on mount and after fetch to see if the table is at the bottom
  // for large screen heights
  useEffect(() => {
    fetchMoreOnBottomReached(containerRef.current);
  }, [fetchMoreOnBottomReached, tablePage, tableNextPage.loading]);

  return (
    <div
      ref={containerRef}
      onScroll={(e) => fetchMoreOnBottomReached(e.target as HTMLDivElement)}
      style={{ overflow: "auto", width: "100%", height: "100%" }}
    >
      <StyledTable
        ref={gridRef}
        role="grid"
        aria-readonly={tableSettings.readOnly}
        aria-colcount={columns.length}
        aria-rowcount={tableRows.length + 1}
        style={
          {
            width: table.getTotalSize(),
            userSelect: "none",
            "--row-height": `${tableSchema.rowHeight || DEFAULT_ROW_HEIGHT}px`,
          } as any
        }
        onKeyDown={handleKeyDown}
      >
        <div
          className="thead"
          role="rowgroup"
          style={{
            position: "sticky",
            top: 0,
            zIndex: 10,
            padding: `0 ${TABLE_PADDING}px`,
          }}
        >
          {table.getHeaderGroups().map((headerGroup) => (
            <StyledRow
              key={headerGroup.id}
              role="row"
              aria-rowindex={1}
              style={{ height: DEFAULT_ROW_HEIGHT + 1 }}
            >
              {headerGroup.headers.map((header) => {
                const isSelectedCell =
                  (!selectedCell && header.index === 0) ||
                  (selectedCell?.path === "_rowy_header" &&
                    selectedCell?.columnKey === header.id);

                return (
                  <ColumnHeaderComponent
                    key={header.id}
                    data-row-id={"_rowy_header"}
                    data-col-id={header.id}
                    data-frozen={header.column.getIsPinned() || undefined}
                    data-frozen-last={lastFrozen === header.id || undefined}
                    role="columnheader"
                    tabIndex={isSelectedCell ? 0 : -1}
                    aria-colindex={header.index + 1}
                    aria-readonly={!canEditColumn}
                    // TODO: aria-sort={"none" | "ascending" | "descending" | "other" | undefined}
                    aria-selected={isSelectedCell}
                    label={header.column.columnDef.meta?.name || header.id}
                    type={header.column.columnDef.meta?.type}
                    style={{
                      width: header.getSize(),
                      left: header.column.getIsPinned()
                        ? virtualCols[header.index].start - TABLE_PADDING
                        : undefined,
                    }}
                    sx={{ "& + &": { borderLeft: "none" } }}
                    onClick={(e) => {
                      setSelectedCell({
                        path: "_rowy_header",
                        columnKey: header.id,
                      });
                      (e.target as HTMLDivElement).focus();
                    }}
                  >
                    {header.column.getCanResize() && (
                      <StyledResizer
                        isResizing={header.column.getIsResizing()}
                        onMouseDown={header.getResizeHandler()}
                        onTouchStart={header.getResizeHandler()}
                      />
                    )}
                  </ColumnHeaderComponent>
                );
              })}
            </StyledRow>
          ))}
        </div>

        <div className="tbody" role="rowgroup">
          {paddingTop > 0 && (
            <div role="presentation" style={{ height: `${paddingTop}px` }} />
          )}

          {virtualRows.map((virtualRow) => {
            const row = rows[virtualRow.index];
            const outOfOrder = row.original._rowy_outOfOrder;

            return (
              <StyledRow
                key={row.id}
                role="row"
                aria-rowindex={row.index + 2}
                style={{
                  height: "auto",
                  marginBottom: outOfOrder ? OUT_OF_ORDER_MARGIN : 0,
                }}
                data-out-of-order={outOfOrder || undefined}
              >
                {paddingLeft > 0 && (
                  <div
                    role="presentation"
                    style={{ width: `${paddingLeft}px` }}
                  />
                )}

                {outOfOrder && <OutOfOrderIndicator />}

                {virtualCols.map((virtualCell) => {
                  const cellIndex = virtualCell.index;
                  const cell = row.getVisibleCells()[cellIndex];

                  const isSelectedCell =
                    selectedCell?.path === row.original._rowy_ref.path &&
                    selectedCell?.columnKey === cell.column.id;

                  return (
                    <StyledCell
                      key={cell.id}
                      data-row-id={row.id}
                      data-col-id={cell.column.id}
                      data-frozen={cell.column.getIsPinned() || undefined}
                      data-frozen-last={
                        lastFrozen === cell.column.id || undefined
                      }
                      role="gridcell"
                      tabIndex={isSelectedCell && !focusInsideCell ? 0 : -1}
                      aria-colindex={cellIndex + 1}
                      aria-readonly={
                        cell.column.columnDef.meta?.editable === false
                      }
                      aria-selected={isSelectedCell}
                      style={{
                        width: cell.column.getSize(),
                        left: cell.column.getIsPinned()
                          ? virtualCell.start - TABLE_PADDING
                          : undefined,
                        backgroundColor:
                          cell.column.id === "_rowy_column_actions"
                            ? "transparent"
                            : undefined,
                        borderBottomWidth:
                          cell.column.id === "_rowy_column_actions"
                            ? 0
                            : undefined,
                        borderRightWidth:
                          cell.column.id === "_rowy_column_actions"
                            ? 0
                            : undefined,
                      }}
                      onClick={(e) => {
                        setSelectedCell({
                          path: row.original._rowy_ref.path,
                          columnKey: cell.column.id,
                        });
                        (e.target as HTMLDivElement).focus();
                      }}
                    >
                      <div
                        className="cell-contents"
                        style={{ height: tableSchema.rowHeight }}
                      >
                        {flexRender(
                          cell.column.columnDef.cell,
                          cell.getContext()
                        )}
                      </div>
                      {/* <button
                        tabIndex={isSelectedCell && focusInsideCell ? 0 : -1}
                      >
                        {isSelectedCell ? "f" : "x"}
                      </button> */}
                    </StyledCell>
                  );
                })}

                {paddingRight > 0 && (
                  <div
                    role="presentation"
                    style={{ width: `${paddingRight}px` }}
                  />
                )}
              </StyledRow>
            );
          })}

          {paddingBottom > 0 && (
            <div role="presentation" style={{ height: `${paddingBottom}px` }} />
          )}
        </div>
      </StyledTable>
    </div>
  );
}
