import { Nilable, Nullable } from 'tsdef';
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

import { GenericFileActionHandler } from '../types/action-handler.types';
import { FileActionMenuItem } from '../types/action-menus.types';
import { FileAction, FileActionMap } from '../types/action.types';
import { ContextMenuConfig } from '../types/context-menu.types';
import { FileViewConfig } from '../types/file-view.types';
import { FileArray, FileIdTrueMap, FileMap } from '../types/file.types';
import { OptionMap } from '../types/options.types';
import { RootState } from '../types/redux.types';
import { SortOrder } from '../types/sort.types';
import { CancelSearchCallback, SearchPredicate, SearchInputCallback } from '../types/search.types';
import { RenamingSanitizer } from '../types/rename.types';
import { FileHelper } from '../util/file-helper';
import { sanitizeInputArray } from './files-transforms';
import { initialRootState } from './state';

const reducers = {
  setExternalFileActionHandler(state: RootState, action: PayloadAction<Nilable<GenericFileActionHandler<FileAction>>>) {
    state.externalFileActionHandler = action.payload ?? null;
  },
  setRawFileActions(state: RootState, action: PayloadAction<FileAction[] | any>) {
    state.rawFileActions = action.payload;
  },
  setFileActionsErrorMessages(state: RootState, action: PayloadAction<string[]>) {
    state.fileActionsErrorMessages = action.payload;
  },
  setFileActions(state: RootState, action: PayloadAction<FileAction[]>) {
    const fileActionMap: FileActionMap = {};
    action.payload.map((a) => (fileActionMap[a.id] = a));
    const fileIds = action.payload.map((a) => a.id);

    state.fileActionMap = fileActionMap as FileMap;
    state.fileActionIds = fileIds;
  },
  updateFileActionMenuItems(state: RootState, action: PayloadAction<[FileActionMenuItem[], FileActionMenuItem[]]>) {
    [state.toolbarItems, state.contextMenuItems] = action.payload;
  },
  setRawFolderChain(state: RootState, action: PayloadAction<FileArray | any>) {
    const rawFolderChain = action.payload;
    const { sanitizedArray: folderChain, errorMessages } = sanitizeInputArray('folderChain', rawFolderChain);
    state.rawFolderChain = rawFolderChain;
    state.folderChain = folderChain;
    state.folderChainErrorMessages = errorMessages;
  },
  setRawFiles(state: RootState, action: PayloadAction<FileArray | any>) {
    const rawFiles = action.payload;
    const { sanitizedArray: files, errorMessages } = sanitizeInputArray('files', rawFiles);
    state.rawFiles = rawFiles;
    state.filesErrorMessages = errorMessages;

    const fileMap: FileMap = {};
    files.forEach((f) => {
      if (f) fileMap[f.id] = f;
    });
    const fileIds = files.map((f) => (f ? f.id : null));
    const cleanFileIds = fileIds.filter((f) => !!f) as string[];

    state.fileMap = fileMap;
    state.fileIds = fileIds;
    state.cleanFileIds = cleanFileIds;

    // Cleanup selection
    for (const selectedFileId of Object.keys(state.selectionMap)) {
      if (!fileMap[selectedFileId]) {
        delete state.selectionMap[selectedFileId];
      }
    }
  },
  setSortedFileIds(state: RootState, action: PayloadAction<Nullable<string>[]>) {
    state.sortedFileIds = action.payload;
  },
  setHiddenFileIds(state: RootState, action: PayloadAction<FileIdTrueMap>) {
    state.hiddenFileIdMap = action.payload;

    // Cleanup selection
    for (const selectedFileId of Object.keys(state.selectionMap)) {
      if (state.hiddenFileIdMap[selectedFileId]) {
        delete state.selectionMap[selectedFileId];
      }
    }
  },
  setFocusSearchInput(state: RootState, action: PayloadAction<Nullable<() => void>>) {
    state.focusSearchInput = action.payload;
  },
  setSearchString(state: RootState, action: PayloadAction<string>) {
    state.searchString = action.payload;
  },
  onSearchInput(state: RootState, action: PayloadAction<Nullable<SearchInputCallback>>) {
    state.onSearchInput = action.payload;
  },
  onCancelSearch(state: RootState, action: PayloadAction<Nullable<CancelSearchCallback>>) {
    state.onCancelSearch = action.payload;
  },
  searchPredicate(state: RootState, action: PayloadAction<Nullable<SearchPredicate>>) {
    state.searchPredicate = action.payload;
  },
  selectAllFiles(state: RootState) {
    state.fileIds
      .filter((id) => id && FileHelper.isSelectable(state.fileMap[id]))
      .map((id) => (id ? (state.selectionMap[id] = true) : null));
  },
  selectFiles(state: RootState, action: PayloadAction<{ fileIds: string[]; reset: boolean }>) {
    if (state.disableSelection) return;
    if (action.payload.reset) state.selectionMap = {};
    action.payload.fileIds
      .filter((id) => id && FileHelper.isSelectable(state.fileMap[id]))
      .map((id) => (state.selectionMap[id] = true));
  },
  selectFile(state: RootState, action: PayloadAction<{ fileId: string; exclusive: boolean; toggle: boolean }>) {
    if (state.disableSelection) return;
    const oldValue = action.payload.toggle && !!state.selectionMap[action.payload.fileId];
    if (action.payload.exclusive) state.selectionMap = {};
    if (oldValue) delete state.selectionMap[action.payload.fileId];
    else if (FileHelper.isSelectable(state.fileMap[action.payload.fileId])) {
      state.selectionMap[action.payload.fileId] = true;
    }
  },
  clearSelection(state: RootState) {
    if (state.disableSelection) return;
    if (Object.keys(state.selectionMap).length !== 0) state.selectionMap = {};
  },
  setSelectionDisabled(state: RootState, action: PayloadAction<boolean>) {
    state.disableSelection = action.payload;
    if (Object.keys(state.selectionMap).length !== 0) state.selectionMap = {};
  },
  setSimpleDeselectionDisabled(state: RootState, action: PayloadAction<boolean>) {
    state.disableSimpleDeselection = action.payload;
  },
  setFileViewConfig(state: RootState, action: PayloadAction<FileViewConfig>) {
    state.fileViewConfig = action.payload;
  },
  setSort(state: RootState, action: PayloadAction<{ actionId: string; order: SortOrder }>) {
    state.sortActionId = action.payload.actionId;
    state.sortOrder = action.payload.order;
  },
  setOptionDefaults(state: RootState, action: PayloadAction<OptionMap>) {
    for (const optionId of Object.keys(action.payload)) {
      if (optionId in state.optionMap) continue;
      state.optionMap[optionId] = action.payload[optionId];
    }
  },
  toggleOption(state: RootState, action: PayloadAction<string>) {
    state.optionMap[action.payload] = !state.optionMap[action.payload];
  },
  setSortCollator(state: RootState, action: PayloadAction<Nullable<Intl.Collator>>) {
    state.sortCollator = action.payload;
  },
  setDoubleClickDelay(state: RootState, action: PayloadAction<number>) {
    state.doubleClickDelay = action.payload;
  },
  setDisableDragAndDrop(state: RootState, action: PayloadAction<boolean>) {
    state.disableDragAndDrop = action.payload;
  },
  setForceEnableOpenParent(state: RootState, action: PayloadAction<boolean>) {
    state.forceEnableOpenParent = action.payload;
  },
  setHideToolbarInfo(state: RootState, action: PayloadAction<boolean>) {
    state.hideToolbarInfo = action.payload;
  },
  setClearSelectionOnOutsideClick(state: RootState, action: PayloadAction<boolean>) {
    state.clearSelectionOnOutsideClick = action.payload;
  },
  setLastClickIndex(state: RootState, action: PayloadAction<Nullable<{ index: number; fileId: string }>>) {
    state.lastClick = action.payload;
  },
  setContextMenuMounted(state: RootState, action: PayloadAction<boolean>) {
    state.contextMenuMounted = action.payload;
  },
  showContextMenu(state: RootState, action: PayloadAction<ContextMenuConfig>) {
    state.contextMenuConfig = action.payload;
  },
  hideContextMenu(state: RootState) {
    if (!state.contextMenuConfig) return;
    state.contextMenuConfig = null;
  },
  startRenaming(state: RootState, action: PayloadAction<string>) {
    if (state.disableRenaming) return;
    const fileId = action.payload;
    const file = state.fileMap[fileId];
    if (FileHelper.isRenamable(file)) {
      state.renamingFileId = fileId;
    }
  },
  endRenaming(state: RootState) {
    state.renamingFileId = null;
  },
  renamingSanitizer(state: RootState, action: PayloadAction<Nullable<RenamingSanitizer>>) {
    state.renamingSanitizer = action.payload;
  },
  setRenamingDisabled(state: RootState, action: PayloadAction<boolean>) {
    state.disableRenaming = action.payload;
    state.renamingFileId = null;
  },
};

export const { actions: reduxActions, reducer: rootReducer } = createSlice({
  name: 'root',
  initialState: initialRootState,
  reducers,
});
