import { useCallback, useMemo } from 'react';

import { useTranslation } from '../../../core/hooks/useTranslation';
import { useToast } from '../../../core/managers/ToastManager/hooks/useToast';
import { assertUnreachable } from '../../../utils/assertUnreachable';
import { ToastAppearance } from '../../Toast/constants';
import { TableSelectionType } from '../constants';
import { useTablePropsContext } from '../contexts/TablePropsContext';
import { useTableSelectionContext } from '../contexts/TableSelectionContext';

/** Result of {@link useAllPageRowsSelection} hook. */
export interface UseAllPageRowsSelectionResult {
  /** Flag indicating whether all page rows are selected. */
  isAllPageRowsSelected: boolean | null;
  /** Function that will toggle all page rows selection. */
  onAllPageRowsSelectionChange: (selected: boolean) => void;
}

/** Hook that returns information about all page rows selection. */
export function useAllPageRowsSelection(): UseAllPageRowsSelectionResult {
  const { t } = useTranslation();
  const showToast = useToast();
  const { selection, onSelectionChanged } = useTableSelectionContext();
  const { rowSelectionName, rows, rowIdProvider, rowSelectionIncludeLimit } = useTablePropsContext();

  const onAllPageRowsSelectionChange = useCallback(
    (selected: boolean) => {
      if (!onSelectionChanged) {
        return;
      }

      if (selection?.type === TableSelectionType.Exclude) {
        const unselectedIds = new Set(selection.unselectedIds);
        if (selected) {
          rows.forEach((row) => {
            const rowId = rowIdProvider(row);
            unselectedIds.delete(rowId);
          });
        } else {
          rows.forEach((row) => {
            const rowId = rowIdProvider(row);
            unselectedIds.add(rowId);
          });
        }

        onSelectionChanged({
          type: TableSelectionType.Exclude,
          unselectedIds: Array.from(unselectedIds),
        });
      } else {
        const selectedIds = new Set(selection?.selectedIds);
        if (selected) {
          rows.forEach((row) => {
            const rowId = rowIdProvider(row);
            selectedIds.add(rowId);
          });
        } else {
          rows.forEach((row) => {
            const rowId = rowIdProvider(row);
            selectedIds.delete(rowId);
          });
        }

        if (rowSelectionIncludeLimit && selectedIds.size > rowSelectionIncludeLimit) {
          const message = t('ui.table.selectionIncludeLimit', {
            count: rowSelectionIncludeLimit,
            actionText: t('ui.table.selectAll'),
            name: rowSelectionName,
          });
          showToast(message, { appearance: ToastAppearance.Warning });
        }

        onSelectionChanged({
          type: TableSelectionType.Include,
          selectedIds: Array.from(selectedIds).slice(0, rowSelectionIncludeLimit),
        });
      }
    },
    [
      onSelectionChanged,
      rowIdProvider,
      rowSelectionIncludeLimit,
      rowSelectionName,
      rows,
      selection,
      showToast,
      t,
    ],
  );

  const isAllPageRowsSelected = useMemo(() => {
    if (!selection) {
      return false;
    }

    let allRowsSelected = false;
    let someRowSelected = false;

    let countIncluded = 0;
    let countExcluded = 0;
    rows.forEach((row) => {
      const rowId = rowIdProvider(row);

      switch (selection.type) {
        case TableSelectionType.Include:
          if (selection.selectedIds.has(rowId)) {
            someRowSelected = true;
            countIncluded++;
            if (rows.length === countIncluded || selection.selectedIds.size === rowSelectionIncludeLimit) {
              allRowsSelected = true;
            }
          }
          break;
        case TableSelectionType.Exclude:
          if (!selection.unselectedIds.has(rowId)) {
            someRowSelected = true;
            countExcluded++;
            if (rows.length === countExcluded) {
              allRowsSelected = true;
            }
          }
          break;
        /* istanbul ignore next */
        default:
          assertUnreachable(selection);
      }
    });

    if (allRowsSelected) {
      return true;
    }

    return someRowSelected ? null : false;
  }, [rowIdProvider, rowSelectionIncludeLimit, rows, selection]);

  return {
    onAllPageRowsSelectionChange,
    isAllPageRowsSelected,
  };
}
