import clsx from 'clsx';
import { LocationDescriptor } from 'history';
import {
  ForwardedRef,
  default as React,
  useCallback,
  useEffect,
  useState,
} from 'react';
import { noop } from '../../helpers/utils';
import { Data } from '../../types/data';
import { ErrorTypeToStationError } from '../../utils/ErrorTypeToStationError';
import { isContextAction } from '../Actions/Action/Action';
import { ActionData } from '../Actions/Actions.models';
import { FilterType, FilterValues } from '../Filters/Filters.model';
import {
  Column,
  ItemSelectEventArgs,
  List,
  ListElement,
  ListSelectMode,
  SortData,
} from '../List';
import { PageHeader, PageHeaderActionProps } from '../PageHeader';
import { getState, storeState } from '../Utils/State/GlobalState';
import { ErrorType } from '../models';
import { useBulkEdit } from './BulkEdit/';
import { ConditionalSplit } from './ConditionalSplit/ConditionalSplit';
import {
  BulkEditRegistration,
  ExplorerBulkAction,
  ExplorerDataProvider,
  ExplorerDataProviderConnection,
  ExplorerStateOptions,
  ItemSelection,
  QuickEditConfig,
  QuickEditRegistration,
} from './Explorer.model';
import classes from './Explorer.scss';
import { useQuickEdit } from './QuickEdit/useQuickEdit';
import { useActions } from './helpers/useActions';
import { useDataProvider } from './helpers/useDataProvider';
import { useFilters } from './helpers/useFilters';
import { StationMessage, useStationMessage } from './helpers/useStationMessage';
import { useSubtitle } from './helpers/useSubtitle';

export interface ExplorerProps<T extends Data> {
  /** Title shown in page header */
  title?: string;

  /**
   * Column definitions
   * The order of this array determines the order of columns
   */
  columns: Column<T>[];

  /** The filters that should be available on the Explorer */
  filterOptions?: FilterType<T>[];

  /** Default values for filters */
  defaultFilterValues?: FilterValues<T>;

  /** Options for disabling states that persist such as filters and sorting. All states persist by default */
  persistExplorerStates?: ExplorerStateOptions;

  /** The name of the property on the data object that should be used to identify objects (default: 'id') */
  keyProperty?: keyof T;

  /** Minimum width of this component */
  minimumWidth?: string;

  /** Spacing between columns */
  columnGap?: string;

  /** Header row height */
  headerRowHeight?: string;

  /** List row height */
  listRowHeight?: string;

  /** List row action button and checkbox size */
  listRowActionSize?: string;

  /** Header checkbox size */
  headerRowActionSize?: string;

  /** Spacing between rows */
  rowGap?: string;

  /** Horizontal alignment of text */
  horizontalTextAlign?: 'left' | 'right' | 'center';

  /** Vertical alignment of text */
  verticalTextAlign?: 'start' | 'center' | 'end';

  /** If set to true, column text overflow will be wrapped to a new line. Otherwise, it will be truncated with an ellipsis (default: true) */
  textWrap?: boolean;

  /** Defines when the loading of the next page is triggered. The number represents the number of row left, before a load is triggered. (default: 10) */
  loadingTriggerOffset?: number;

  /** Array of actions to be rendered. */
  actions?: PageHeaderActionProps[];

  /** Array of Bulk Actions to be rendered. If populated, Bulk Actions will become available. */
  bulkActions?: ExplorerBulkAction<T>[];

  /** Whether or not bulk actions are shown by default. (default: false) */
  openBulkActionsOnStart?: boolean;

  /** Unique identifier used to store states related to explorer */
  stationKey: string;

  /** Determines which select mode the list is in.
   * If 'None' is selected, a right chevron will be rendered for each row of data
   * If 'Single' is selected, a check mark will be rendered for each row of data
   * If 'Multi' is selected or Bulk Actions is open, a checkbox will be rendered for the header and each row of data
   */
  selectionMode?: ListSelectMode;

  /** In multiselect mode, whether the Select All button will be displayed. When Select All button is clicked the onItemClicked button will return a filter query instead of an array of items. (default: true) */
  enableSelectAll?: boolean;

  /** Callback used to request new or additional data */
  dataProvider: ExplorerDataProvider<T>;

  /** If true, the explorer will leave some margin for the modal backdrop. */
  modalMode?: boolean;

  /** CSS Class name for additional styles */
  className?: string;

  /** Sets the default sort order for the Explorer. (default: undefined) */
  defaultSortOrder?: SortData<T>;

  /** Update the tab title using the 'title' field. (default: true) */
  setTabTitle?: boolean;

  /** Quick Edit Registrations */
  quickEditRegistrations?: QuickEditRegistration<T>[];

  /** Optional Quick Edit Configuration */
  quickEditConfig?: QuickEditConfig;

  /** Bulk Edit Registration
   * @experimental The feature Bulk Edit and it's underlying implementation is still in beta. */
  bulkEditRegistration?: BulkEditRegistration<T>;

  /**
   * When set, this function is used to generate the link that the user should be navigated to for each item.
   *
   * This property takes precedence over `onItemClicked`, which will not be executed in case `generateItemLink` is set!
   */
  generateItemLink?: (data: T) => LocationDescriptor<unknown>;

  /**
   * Raised when an data item is clicked
   * Row data is supplied as the first argument
   * List mode is supplied as the second argument. List mode may change depending on the bulk actions toggled state
   */
  onItemClicked?: (data: T, mode?: ListSelectMode) => void;

  /**
   * Callback to emit when Bulk Actions is toggled
   * The expanded state is supplied as an argument
   */
  onBulkActionsToggled?: (expanded: boolean) => void;

  /** Provide inline actions which are available through '...' context menu */
  inlineMenuActions?: (data: T) => ActionData[];
}

/**
 * Renders an Explorer that can show a list of assets and allows selection of single assets,
 * performing bulk operations on multiple assets, filtering, sorting as well as a button to create new assets.
 *
 * @example
 * <Explorer<DataInterface>
 *  title="Title"
 *  columns={[{propertyName: 'id', size: '1fr', label: 'Id'}]}
 *  stationKey="station-key"
 *  dataProvider={dataProvider}
 *  bulkActions={[{ label: 'Action One'}]}
 *  onItemClicked={itemClickedHandler}
 * />
 */
export const Explorer = React.forwardRef(function Explorer<T extends Data>(
  {
    title,

    minimumWidth,
    columnGap,
    rowGap,
    headerRowHeight,
    listRowHeight,
    listRowActionSize,
    headerRowActionSize,
    horizontalTextAlign,
    verticalTextAlign,
    textWrap,

    columns,
    filterOptions,
    defaultFilterValues,
    keyProperty,
    stationKey,
    persistExplorerStates,

    actions,
    selectionMode = ListSelectMode.None,
    bulkActions,
    openBulkActionsOnStart,

    dataProvider,
    loadingTriggerOffset,

    enableSelectAll = true,
    modalMode = false,
    className = '',

    defaultSortOrder,
    setTabTitle = true,

    quickEditConfig,
    quickEditRegistrations,
    bulkEditRegistration,

    onItemClicked,
    onBulkActionsToggled = noop,
    generateItemLink,
    inlineMenuActions,
  }: ExplorerProps<T>,
  explorerRef: ForwardedRef<ExplorerDataProviderConnection<T>>,
): JSX.Element {
  const { stationMessage, setStationMessage, StationMessage } =
    useStationMessage();

  const globalStateOptions: ExplorerStateOptions = {
    filters: true,
    sort: true,
    ...persistExplorerStates,
  };

  const [sortOrder, setSortOrder] = useState<SortData<T>>(
    getState<SortData<T>>(stationKey, 'sort') ?? defaultSortOrder,
  );

  const listRef = React.useRef<ListElement>(null);

  useEffect(() => {
    if (globalStateOptions.sort && sortOrder !== defaultSortOrder) {
      storeState(stationKey, 'sort', sortOrder);
    } else {
      storeState(stationKey, 'sort', undefined);
    }
  }, [defaultSortOrder, globalStateOptions.sort, sortOrder, stationKey]);

  const onFilterChangedHandler = useCallback(() => {
    listRef.current?.resetSelection();
  }, []);

  const {
    activeFilters,
    Filters,
    filtersVisible,
    toggleFiltersVisible,
    collapseFilters,
  } = useFilters<T>({
    stationKey,
    globalStateOptions,
    filterOptions,
    defaultFilterValues,
    onFiltersChange: onFilterChangedHandler,
  });

  const {
    data,
    isLoading,
    resultCount,
    hasMoreData,
    onReloadData,
    onRequestMoreData,
    onSortChanged,
  } = useDataProvider<T>({
    dataProvider,
    explorerRef,
    setStationMessage,
    defaultSortOrder: sortOrder,
    activeFilters,
    keyProperty,
  });

  const {
    isQuickEditMode,
    quickEditAction,
    QuickEditContextProvider,
    quickEditStation,
    changeSelectedItem,
  } = useQuickEdit({
    listRef,
    registrations: quickEditRegistrations,
    hasData: data.length > 0,
    defaultItem: data[0],
    quickEditConfig,
    collapseFilters,
    generateItemLink,
  });

  const { mode: listSelectionMode, setIsBulkOpen } = useListSelectionMode(
    selectionMode,
    openBulkActionsOnStart,
    isQuickEditMode,
  );

  // TODO: Put into hook?------------------
  const [itemSelection, setItemSelection] = useState<ItemSelectEventArgs<T>>({
    mode: 'SINGLE_ITEMS',
    items: [],
  });

  const itemSelectedHandler = useCallback(
    (itemSelectEventArgs: ItemSelectEventArgs<T>): void => {
      setItemSelection(itemSelectEventArgs);
    },
    [],
  );

  const getBulkActionSelection = useCallback(() => {
    let payload: ItemSelection<T>;

    const selection = itemSelection ?? { mode: 'SELECT_ALL' };

    switch (selection.mode) {
      case 'SELECT_ALL':
        payload = {
          mode: selection.mode,
          filters: activeFilters,
        };
        break;
      case 'SINGLE_ITEMS':
        payload = {
          mode: selection.mode,
          items: selection.items,
        };
        break;
      default:
        // eslint-disable-next-line no-console
        throw console.error(`Switch was not exhaustive`);
    }
    return payload;
  }, [itemSelection, activeFilters]);
  //------------------

  // Emits selected row data and current ListSelectMode state
  const onItemClickedHandler = useCallback(
    async (item: T) => {
      if (isQuickEditMode) {
        await changeSelectedItem(item);
      }

      if (onItemClicked !== undefined) {
        onItemClicked(item, listSelectionMode);
      }
    },
    [isQuickEditMode, onItemClicked, changeSelectedItem, listSelectionMode],
  );

  const {
    BulkEditContextProvider,
    BulkEditComponent,
    isBulkEditMode,
    bulkEditAction,
    closeBulkEdit,
  } = useBulkEdit({
    bulkEditRegistration,
    getBulkActionSelection,
    collapseFilters,
  });

  const { resultsTitle } = useSubtitle<T>(
    listSelectionMode,
    itemSelection,
    resultCount,
  );

  const errAction = 'An error occurred when trying to execute the operation.';

  const inlineMenuActionsHandler = useCallback(
    (data: T): ActionData[] =>
      (inlineMenuActions?.(data) ?? []).map((action) =>
        isContextAction(action)
          ? {
              ...action,
              onActionSelected: async () => {
                try {
                  const result = await action.onActionSelected();
                  if (result) {
                    setStationMessage(errMsg(result));
                  }
                } catch (error) {
                  setStationMessage(errMsg(error, errAction));
                }
              },
            }
          : action,
      ),
    [inlineMenuActions, setStationMessage],
  );

  const { actions: pageHeaderActions } = useActions<T>({
    actions,
    bulkActions,
    quickEditAction,
    bulkEditAction,
    openBulkActionsOnStart,
    filtersVisible,
    hasFilters: filterOptions !== undefined,
    resultCount,
    activeFilterCount: Object.keys(activeFilters).length,
    itemSelection,
    getBulkActionSelection,
    onReloadData,
    setStationMessage,
    onBulkActionsToggled,
    setIsBulkOpen,
    toggleFiltersVisible,
    closeBulkEdit,
  });

  useEffect(() => {
    if (listSelectionMode !== ListSelectMode.Multi) {
      setItemSelection({ mode: 'SINGLE_ITEMS', items: [] });
      listRef.current?.resetSelection();
    }
  }, [listSelectionMode]);

  return (
    <ConditionalSplit
      shouldSplit={isQuickEditMode || isBulkEditMode}
      minSecondarySize="500px"
    >
      <div
        className={clsx(
          classes.container,
          {
            [classes.hasStationError]: stationMessage,
            [classes.modalMode]: modalMode,
          },
          'explorer-container',
          className,
        )}
      >
        <PageHeader
          title={title}
          subtitle={resultsTitle}
          actions={pageHeaderActions}
          setTabTitle={setTabTitle}
        />
        {StationMessage}
        {Filters}
        <div
          className={clsx({
            [classes.filtersHidden]: !filtersVisible,
          })}
        >
          <List<T>
            ref={listRef}
            columns={columns}
            data={data}
            isLoading={isLoading}
            isError={Boolean(stationMessage?.type === 'error')}
            handleRetry={false}
            minimumWidth={minimumWidth}
            columnGap={columnGap}
            rowGap={rowGap}
            headerRowHeight={headerRowHeight}
            listRowHeight={listRowHeight}
            listRowActionSize={listRowActionSize}
            headerRowActionSize={headerRowActionSize}
            horizontalTextAlign={horizontalTextAlign}
            verticalTextAlign={verticalTextAlign}
            textWrap={textWrap}
            keyProperty={keyProperty}
            showActionButton={
              Boolean(generateItemLink) ||
              Boolean(onItemClicked) ||
              listSelectionMode !== ListSelectMode.None
            } // or hard code to `true`?
            selectionMode={listSelectionMode}
            enableSelectAll={enableSelectAll}
            enableSelectAllDeselect={enableSelectAll && !hasMoreData}
            loadingTriggerOffset={loadingTriggerOffset}
            defaultSortOrder={sortOrder}
            generateItemLink={!isQuickEditMode ? generateItemLink : undefined}
            onItemClicked={onItemClickedHandler}
            onItemSelected={itemSelectedHandler}
            onRequestMoreData={onRequestMoreData}
            onSortChanged={(sortData: SortData<T>) => {
              listRef.current?.resetSelection();
              onSortChanged(sortData);
              setSortOrder(sortData);
            }}
            inlineMenuActions={inlineMenuActions && inlineMenuActionsHandler}
          />
        </div>
      </div>
      {isQuickEditMode && (
        <QuickEditContextProvider>{quickEditStation}</QuickEditContextProvider>
      )}
      {isBulkEditMode && (
        <BulkEditContextProvider>{BulkEditComponent}</BulkEditContextProvider>
      )}
    </ConditionalSplit>
  );
});

/**
 * Informs what state ListSelectMode is in
 * @param selectionMode ListSelectMode from prop
 * @param openBulkActionsOnStart boolean from prop
 */
const useListSelectionMode = (
  selectionMode: ListSelectMode,
  openBulkActionsOnStart?: boolean,
  isQuickEditMode?: boolean,
): {
  readonly mode: ListSelectMode;
  readonly setIsBulkOpen: React.Dispatch<React.SetStateAction<boolean>>;
} => {
  const [isBulkOpen, setIsBulkOpen] = useState<boolean>(
    openBulkActionsOnStart ?? false,
  );

  let mode = selectionMode;

  if (isQuickEditMode) {
    mode = ListSelectMode.Single;
  } else if (isBulkOpen) {
    mode = ListSelectMode.Multi;
  }

  // If bulk actions is open, set ListSelectMode to Multi
  // const mode = isBulkOpen ? ListSelectMode.Multi : selectionMode;

  return { mode, setIsBulkOpen } as const;
};

const errMsg = (err: unknown | ErrorType, msg?: string): StationMessage => {
  return {
    ...ErrorTypeToStationError(err as ErrorType, msg),
    canClose: true,
    type: 'error',
  };
};
