import { PropsWithChildren, createContext, useContext, useMemo } from 'react';

import { assertUnreachable } from '../../../utils/assertUnreachable';
import { TableSelectionType } from '../constants';
import { TableSelection } from '../types';

import { useTablePropsContext } from './TablePropsContext';

/**
 * Like {@link TableSelection}, but with `Set` in {@link FastTableSelection.selectedIds} and
 * {@link FastTableSelection.unselectedIds} for performance
 */
export type FastTableSelection =
  | {
      /** Selection mode type */
      type: TableSelectionType.Include;
      /** Only rows with given IDs will be selected */
      selectedIds: Set<string>;
    }
  | {
      /** Selection mode type */
      type: TableSelectionType.Exclude;
      /** All rows will be selected expect rows with given IDs */
      unselectedIds: Set<string>;
    };

/** Value of {@link TableSelectionContext} */
export interface TableSelectionContextValue {
  /** Current table selection */
  selection?: FastTableSelection;
  /** Function that should be called when selection is changed */
  onSelectionChanged?: (selection: TableSelection) => void;
}

const TableSelectionContext = createContext<TableSelectionContextValue | undefined>(undefined);

/**
 * Provider that provides {@link TableProps.selection} with `Set` in {@link FastTableSelection.selectedIds} and
 * {@link FastTableSelection.unselectedIds} for performance.
 */
export function TableSelectionContextProvider({ children }: PropsWithChildren<{}>) {
  const { selection, onSelectionChanged } = useTablePropsContext();

  const fastTableSelection: FastTableSelection | undefined = useMemo(() => {
    if (!selection) {
      return undefined;
    }

    switch (selection.type) {
      case TableSelectionType.Include:
        return {
          type: TableSelectionType.Include,
          selectedIds: new Set(selection.selectedIds),
        };
      case TableSelectionType.Exclude:
        return {
          type: TableSelectionType.Exclude,
          unselectedIds: new Set(selection.unselectedIds),
        };
      /* istanbul ignore next */
      default:
        assertUnreachable(selection);
    }
  }, [selection]);

  const value: TableSelectionContextValue = useMemo(
    () => ({ selection: fastTableSelection, onSelectionChanged }),
    [fastTableSelection, onSelectionChanged],
  );

  return <TableSelectionContext.Provider value={value}>{children}</TableSelectionContext.Provider>;
}

/** Returns current table selection */
export function useTableSelectionContext(): TableSelectionContextValue {
  const value = useContext(TableSelectionContext);
  if (!value) {
    throw new Error('useTableSelectionContext should be used only inside TableSelectionContextProvider');
  }

  return value;
}
