/* eslint-disable react/prop-types */
// disable this rule as it was throwing an error in Header and Cell component
// definitions for the selection row for some reason when we dont really need it.
import React, { useMemo, useEffect, useCallback, useContext } from "react";
import { TableContext } from "context/table";
import classnames from "classnames";
import {
  useTable,
  useSortBy,
  useRowSelect,
  Row,
  usePagination,
  useFilters,
  HeaderGroup,
  Column,
} from "react-table";
import { isString, kebabCase, noop } from "lodash";
import { useDebouncedCallback } from "use-debounce/lib";

import { useDeepEffect } from "utilities/hooks";
import sort from "utilities/sort";
import { AppContext } from "context/app";

import Button from "components/buttons/Button";
// @ts-ignore
import FleetIcon from "components/icons/FleetIcon";
import Spinner from "components/Spinner";
import { ButtonVariant } from "components/buttons/Button/Button";
// @ts-ignore
import ActionButton, { IActionButtonProps } from "./ActionButton";

const baseClass = "data-table-container";

interface IDataTableProps {
  columns: Column[];
  data: any;
  filters?: Record<string, string | number | boolean>;
  isLoading: boolean;
  manualSortBy?: boolean;
  sortHeader: any;
  sortDirection: any;
  onSort: any; // TODO: an event type
  disableMultiRowSelect: boolean;
  showMarkAllPages: boolean;
  isAllPagesSelected: boolean; // TODO: make dependent on showMarkAllPages
  toggleAllPagesSelected?: any; // TODO: an event type and make it dependent on showMarkAllPages
  resultsTitle: string;
  defaultPageSize: number;
  primarySelectActionButtonVariant?: ButtonVariant;
  primarySelectActionButtonIcon?: string;
  primarySelectActionButtonText?: string | ((targetIds: number[]) => string);
  onPrimarySelectActionClick: any; // figure out type
  secondarySelectActions?: IActionButtonProps[];
  isClientSidePagination?: boolean;
  isClientSideFilter?: boolean;
  highlightOnHover?: boolean;
  searchQuery?: string;
  searchQueryColumn?: string;
  selectedDropdownFilter?: string;
  onSelectSingleRow?: (value: Row) => void;
  onResultsCountChange?: (value: number) => void;
  renderFooter?: () => JSX.Element | null;
  renderPagination?: () => JSX.Element | null;
}

const CLIENT_SIDE_DEFAULT_PAGE_SIZE = 20;

// This data table uses react-table for implementation. The relevant documentation of the library
// can be found here https://react-table.tanstack.com/docs/api/useTable
const DataTable = ({
  columns: tableColumns,
  data: tableData,
  filters: tableFilters,
  isLoading,
  manualSortBy = false,
  sortHeader,
  sortDirection,
  onSort,
  disableMultiRowSelect,
  showMarkAllPages,
  isAllPagesSelected,
  toggleAllPagesSelected,
  resultsTitle,
  defaultPageSize,
  primarySelectActionButtonIcon,
  primarySelectActionButtonVariant,
  onPrimarySelectActionClick,
  primarySelectActionButtonText,
  secondarySelectActions,
  isClientSidePagination,
  isClientSideFilter,
  highlightOnHover,
  searchQuery,
  searchQueryColumn,
  selectedDropdownFilter,
  onSelectSingleRow,
  onResultsCountChange,
  renderFooter,
  renderPagination,
}: IDataTableProps): JSX.Element => {
  const { resetSelectedRows } = useContext(TableContext);
  const { isOnlyObserver } = useContext(AppContext);

  const columns = useMemo(() => {
    return tableColumns;
  }, [tableColumns]);

  // The table data needs to be ordered by the order we received from the API.
  const data = useMemo(() => {
    return tableData;
  }, [tableData]);

  const {
    headerGroups,
    rows,
    prepareRow,
    selectedFlatRows,
    toggleAllRowsSelected,
    isAllRowsSelected,
    state: tableState,
    page, // Instead of using 'rows', we'll use page,
    // which has only the rows for the active page

    // The rest of these things are super handy, too ;)
    canPreviousPage,
    canNextPage,
    // pageOptions,
    // pageCount,
    // gotoPage,
    nextPage,
    previousPage,
    setPageSize,
    setFilter,
    setAllFilters,
  } = useTable(
    {
      columns,
      data,
      initialState: {
        sortBy: useMemo(() => {
          return [{ id: sortHeader, desc: sortDirection === "desc" }];
        }, [sortHeader, sortDirection]),
      },
      disableMultiSort: true,
      disableSortRemove: true,
      manualSortBy,
      // Initializes as false, but changes briefly to true on successful notification
      autoResetSelectedRows: resetSelectedRows,
      // Expands the enumerated `filterTypes` for react-table
      // (see https://github.com/tannerlinsley/react-table/blob/master/src/filterTypes.js)
      // with custom `filterTypes` defined for this `useTable` instance
      filterTypes: React.useMemo(
        () => ({
          hasLength: (
            // eslint-disable-next-line @typescript-eslint/no-shadow
            rows: Row[],
            columnIds: string[],
            filterValue: boolean
          ) => {
            return !filterValue
              ? rows
              : rows?.filter((row) => {
                  return columnIds?.some((id) => row?.values?.[id]?.length);
                });
          },
        }),
        []
      ),
      // Expands the enumerated `sortTypes` for react-table
      // (see https://github.com/tannerlinsley/react-table/blob/master/src/sortTypes.js)
      // with custom `sortTypes` defined for this `useTable` instance
      sortTypes: React.useMemo(
        () => ({
          caseInsensitive: (a: any, b: any, id: any) => {
            let valueA = a.values[id];
            let valueB = b.values[id];

            valueA = isString(valueA) ? valueA.toLowerCase() : valueA;
            valueB = isString(valueB) ? valueB.toLowerCase() : valueB;

            if (valueB > valueA) {
              return -1;
            }
            if (valueB < valueA) {
              return 1;
            }
            return 0;
          },
          dateStrings: (a: any, b: any, id: any) =>
            sort.dateStringsAsc(a.values[id], b.values[id]),
        }),
        []
      ),
    },
    useFilters,
    useSortBy,
    usePagination,
    useRowSelect
  );

  const { sortBy, selectedRowIds } = tableState;

  useEffect(() => {
    if (tableFilters) {
      const allFilters = Object.entries(tableFilters).map(([id, value]) => ({
        id,
        value,
      }));
      !!allFilters.length && setAllFilters(allFilters);
    }
  }, [tableFilters]);

  // Listen for changes to filters if clientSideFilter is enabled

  const setDebouncedClientFilter = useDebouncedCallback(
    (column: string, query: string) => {
      setFilter(column, query);
    },
    300
  );

  useEffect(() => {
    if (isClientSideFilter && onResultsCountChange) {
      onResultsCountChange(rows.length);
    }
  }, [isClientSideFilter, onResultsCountChange, rows.length]);

  useEffect(() => {
    if (isClientSideFilter && searchQueryColumn) {
      setDebouncedClientFilter(searchQueryColumn, searchQuery || "");
    }
  }, [searchQuery, searchQueryColumn]);

  useEffect(() => {
    if (isClientSideFilter && selectedDropdownFilter) {
      selectedDropdownFilter === "all"
        ? setDebouncedClientFilter("platforms", "")
        : setDebouncedClientFilter("platforms", selectedDropdownFilter);
    }
  }, [selectedDropdownFilter]);

  // This is used to listen for changes to sort. If there is a change
  // Then the sortHandler change is fired.
  useEffect(() => {
    const column = sortBy[0];
    if (column !== undefined) {
      if (
        column.id !== sortHeader ||
        column.desc !== (sortDirection === "desc")
      ) {
        onSort(column.id, column.desc);
      }
    } else {
      onSort(undefined);
    }
  }, [sortBy, sortHeader, onSort, sortDirection]);

  useEffect(() => {
    if (isAllPagesSelected) {
      toggleAllRowsSelected(true);
    }
  }, [isAllPagesSelected, toggleAllRowsSelected]);

  useEffect(() => {
    setPageSize(CLIENT_SIDE_DEFAULT_PAGE_SIZE);
  }, [setPageSize]);

  useDeepEffect(() => {
    if (
      Object.keys(selectedRowIds).length < rows.length &&
      toggleAllPagesSelected
    ) {
      toggleAllPagesSelected(false);
    }
  }, [tableState.selectedRowIds, toggleAllPagesSelected]);

  const onToggleAllPagesClick = useCallback(() => {
    toggleAllPagesSelected();
  }, [toggleAllPagesSelected]);

  const onClearSelectionClick = useCallback(() => {
    toggleAllRowsSelected(false);
    toggleAllPagesSelected(false);
  }, [toggleAllPagesSelected, toggleAllRowsSelected]);

  const onSingleRowClick = useCallback(
    (row) => {
      if (disableMultiRowSelect) {
        row.toggleRowSelected();
        onSelectSingleRow && onSelectSingleRow(row);
        toggleAllRowsSelected(false);
      }
    },
    [disableMultiRowSelect, onSelectSingleRow, toggleAllRowsSelected]
  );

  const renderColumnHeader = (column: HeaderGroup) => {
    return (
      <div className="column-header">
        {column.render("Header")}
        {column.Filter && column.render("Filter")}
      </div>
    );
  };

  const renderSelectedCount = (): JSX.Element => {
    return (
      <p>
        <span>
          {selectedFlatRows.length}
          {isAllPagesSelected && "+"}
        </span>{" "}
        selected
      </p>
    );
  };

  const renderAreAllSelected = (): JSX.Element | null => {
    if (isAllPagesSelected) {
      return <p>All matching {resultsTitle} are selected</p>;
    }

    if (isAllRowsSelected) {
      return <p>All {resultsTitle} on this page are selected</p>;
    }
    return null;
  };

  const renderActionButton = (
    actionButtonProps: IActionButtonProps
  ): JSX.Element => {
    const {
      name,
      onActionButtonClick,
      buttonText,
      targetIds,
      variant,
      hideButton,
      icon,
      iconPosition,
    } = actionButtonProps;
    return (
      <div className={`${baseClass}__${kebabCase(name)}`}>
        <ActionButton
          key={kebabCase(name)}
          name={name}
          buttonText={buttonText}
          onActionButtonClick={onActionButtonClick || noop}
          targetIds={targetIds}
          variant={variant}
          hideButton={hideButton}
          icon={icon}
          iconPosition={iconPosition}
        />
      </div>
    );
  };

  const renderPrimarySelectAction = (): JSX.Element | null => {
    const targetIds = selectedFlatRows.map((row: any) => row.original.id);
    const buttonText =
      typeof primarySelectActionButtonText === "function"
        ? primarySelectActionButtonText(targetIds)
        : primarySelectActionButtonText;
    const name = buttonText ? kebabCase(buttonText) : "primary-select-action";

    const actionProps = {
      name,
      buttonText: buttonText || "",
      onActionButtonClick: onPrimarySelectActionClick,
      targetIds,
      variant: primarySelectActionButtonVariant,
      icon: primarySelectActionButtonIcon,
    };

    return !buttonText ? null : renderActionButton(actionProps);
  };

  const renderSecondarySelectActions = (): JSX.Element[] | null => {
    if (secondarySelectActions) {
      const targetIds = selectedFlatRows.map((row: any) => row.original.id);
      const buttons = secondarySelectActions.map((actionProps) => {
        actionProps = { ...actionProps, targetIds };
        return renderActionButton(actionProps);
      });
      return buttons;
    }
    return null;
  };

  const shouldRenderToggleAllPages =
    Object.keys(selectedRowIds).length >= defaultPageSize &&
    showMarkAllPages &&
    !isAllPagesSelected;

  const pageOrRows = isClientSidePagination ? page : rows;

  const previousButton = (
    <>
      <FleetIcon name="chevronleft" /> Previous
    </>
  );
  const nextButton = (
    <>
      Next <FleetIcon name="chevronright" />
    </>
  );

  const tableStyles = classnames({
    "data-table__table": true,
    "is-observer": isOnlyObserver,
  });

  return (
    <div className={baseClass}>
      {isLoading && (
        <div className={"loading-overlay"}>
          <Spinner />
        </div>
      )}
      <div className={"data-table data-table__wrapper"}>
        <table className={tableStyles}>
          {Object.keys(selectedRowIds).length !== 0 && (
            <thead className={"active-selection"}>
              <tr {...headerGroups[0].getHeaderGroupProps()}>
                <th
                  {...headerGroups[0].headers[0].getHeaderProps(
                    headerGroups[0].headers[0].getSortByToggleProps()
                  )}
                >
                  {headerGroups[0].headers[0].render("Header")}
                </th>
                <th className={"active-selection__container"}>
                  <div className={"active-selection__inner"}>
                    {renderSelectedCount()}
                    <div className={"active-selection__inner-left"}>
                      {secondarySelectActions && renderSecondarySelectActions()}
                    </div>
                    <div className={"active-selection__inner-right"}>
                      {primarySelectActionButtonText &&
                        renderPrimarySelectAction()}
                    </div>
                    {toggleAllPagesSelected && renderAreAllSelected()}
                    {shouldRenderToggleAllPages && (
                      <Button
                        onClick={onToggleAllPagesClick}
                        variant={"text-link"}
                        className={"light-text"}
                      >
                        <>Select all matching {resultsTitle}</>
                      </Button>
                    )}
                    <Button
                      onClick={onClearSelectionClick}
                      variant={"text-link"}
                    >
                      Clear selection
                    </Button>
                  </div>
                </th>
              </tr>
            </thead>
          )}
          <thead>
            {headerGroups.map((headerGroup) => (
              <tr {...headerGroup.getHeaderGroupProps()}>
                {headerGroup.headers.map((column) => (
                  <th
                    className={column.id ? `${column.id}__header` : ""}
                    {...column.getHeaderProps(column.getSortByToggleProps())}
                  >
                    {renderColumnHeader(column)}
                  </th>
                ))}
              </tr>
            ))}
          </thead>
          <tbody>
            {pageOrRows.map((row: any) => {
              prepareRow(row);

              const rowStyles = classnames({
                "single-row": disableMultiRowSelect,
                "highlight-on-hover": highlightOnHover,
              });
              return (
                <tr
                  className={rowStyles}
                  {...row.getRowProps({
                    // @ts-ignore // TS complains about prop not existing
                    onClick: () => {
                      disableMultiRowSelect && onSingleRowClick(row);
                    },
                  })}
                >
                  {row.cells.map((cell: any) => {
                    return (
                      <td
                        className={
                          cell.column.id ? `${cell.column.id}__cell` : ""
                        }
                        {...cell.getCellProps()}
                      >
                        {cell.render("Cell")}
                      </td>
                    );
                  })}
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
      <div className={`${baseClass}__footer`}>
        {renderFooter && (
          <div className={`${baseClass}__footer-text`}>{renderFooter()}</div>
        )}
        {isClientSidePagination ? (
          <div className={`${baseClass}__pagination`}>
            <Button
              variant="unstyled"
              onClick={() => previousPage()}
              disabled={!canPreviousPage}
            >
              {previousButton}
            </Button>
            <Button
              variant="unstyled"
              onClick={() => nextPage()}
              disabled={!canNextPage}
            >
              {nextButton}
            </Button>
          </div>
        ) : (
          renderPagination && renderPagination()
        )}
      </div>
    </div>
  );
};

export default DataTable;
