import { DateUtils } from '../date';
import {
  CellType, DATE_COLUMN_OPTIONS, MULTIPLE_CELL_VALUE_COLUMN_TYPE_MAP, SINGLE_CELL_VALUE_COLUMN_TYPE_MAP, MAX_GROUP_LEVEL,
  SORT_COLUMN_OPTIONS, SORT_TYPE, TEXT_SORTER_COLUMN_TYPES, NUMBER_SORTER_COLUMN_TYPES,
} from '../../constants';
import { deleteInvalidGroupby } from './core';
import { isValidCellValue, getCellValueByColumn, getCollaboratorsNames, getGeolocationByGranularity } from '../cell';
import { sortDate, sortText, sortNumber, sortCheckbox, sortCollaborator, sortSingleSelect, sortMultipleSelect } from '../sort';

const _getFormattedCellValue = (cellValue, groupby) => {
  const { column, count_type: countType } = groupby;
  const { type: columnType } = column;
  switch (columnType) {
    case CellType.TEXT:
    case CellType.LAST_MODIFIER:
    case CellType.CREATOR: {
      return cellValue || null;
    }
    case CellType.DATE:
    case CellType.CTIME:
    case CellType.MTIME: {
      return DateUtils.getDateByGranularity(cellValue, countType) || null;
    }
    case CellType.NUMBER:
    case CellType.RATE: {
      return (cellValue || cellValue === 0) ? cellValue : null;
    }
    case CellType.CHECKBOX: {
      return !!cellValue;
    }
    case CellType.SINGLE_SELECT: {
      return cellValue || null;
    }
    case CellType.MULTIPLE_SELECT: {
      return Array.isArray(cellValue) ? cellValue : [];
    }
    case CellType.COLLABORATOR: {
      return Array.isArray(cellValue) ? cellValue : [];
    }
    case CellType.GEOLOCATION: {
      return getGeolocationByGranularity(cellValue, countType);
    }
    default: {
      return null;
    }
  }
};

const _getStrCellValue = (cellValue, columnType) => {
  let sCellValue = null;
  if (SINGLE_CELL_VALUE_COLUMN_TYPE_MAP[columnType]) {
    sCellValue = typeof cellValue === 'string' ? cellValue : String(cellValue);
  } else if (MULTIPLE_CELL_VALUE_COLUMN_TYPE_MAP[columnType]) {
    sCellValue = [...cellValue].sort().toString();
  }
  return sCellValue;
};

const _findGroupIndexWithMultipleGroupbys = (sCellValue, cellValue2GroupIndexMap, groupsLength) => {
  const target = cellValue2GroupIndexMap[sCellValue];
  if (target && target.index > -1) {
    return target.index;
  }

  // eslint-disable-next-line
  cellValue2GroupIndexMap[sCellValue] = {};

  // eslint-disable-next-line
  cellValue2GroupIndexMap[sCellValue].subgroups = {};

  // eslint-disable-next-line
  cellValue2GroupIndexMap[sCellValue].index = groupsLength;
  return -1;
};

const _findGroupIndex = (sCellValue, cellValue2GroupIndexMap, groupsLength) => {
  const index = cellValue2GroupIndexMap[sCellValue];
  if (index > -1) {
    return index;
  }

  // eslint-disable-next-line
  cellValue2GroupIndexMap[sCellValue] = groupsLength;
  return -1;
};

const getSortedGroups = (groups, groupbys, level, collaborators = []) => {
  const sortFlag = 0;
  const { column, sort_type } = groupbys[level];
  const { type: columnType, data: columnData } = column;
  const normalizedSortType = sort_type || SORT_TYPE.UP;
  let option_id_index_map = {};
  if (columnType === CellType.SINGLE_SELECT || columnType === CellType.MULTIPLE_SELECT) {
    const { options } = columnData || {};
    if (Array.isArray(options)) {
      options.forEach((option, index) => {
        option_id_index_map[option.id] = index;
      });
    }
  }

  groups.sort((currGroupRow, nextGroupRow) => {
    let { cell_value: currCellVal } = currGroupRow;
    let { cell_value: nextCellVal } = nextGroupRow;
    if (SORT_COLUMN_OPTIONS.includes(columnType)) {
      let sortResult;
      if (TEXT_SORTER_COLUMN_TYPES.includes(columnType)) {
        sortResult = sortText(currCellVal, nextCellVal, normalizedSortType);
      } else if (DATE_COLUMN_OPTIONS.includes(columnType)) {
        sortResult = sortDate(currCellVal, nextCellVal, normalizedSortType);
      } else if (NUMBER_SORTER_COLUMN_TYPES.includes(columnType)) {
        sortResult = sortNumber(currCellVal, nextCellVal, normalizedSortType);
      } else if (columnType === CellType.CHECKBOX) {
        sortResult = sortCheckbox(currCellVal, nextCellVal, normalizedSortType);
      } else if (columnType === CellType.COLLABORATOR) {
        let currCollaborators = currCellVal;
        let nextCollaborators = nextCellVal;
        if (collaborators) {
          currCollaborators = getCollaboratorsNames(currCollaborators, collaborators);
          nextCollaborators = getCollaboratorsNames(nextCollaborators, collaborators);
        }
        sortResult = sortCollaborator(currCollaborators, nextCollaborators, normalizedSortType);
      } else if (columnType === CellType.SINGLE_SELECT) {
        sortResult = sortSingleSelect(currCellVal, nextCellVal, { sort_type: normalizedSortType, option_id_index_map });
      } else if (columnType === CellType.MULTIPLE_SELECT) {
        sortResult = sortMultipleSelect(currCellVal, nextCellVal, { sort_type: normalizedSortType, option_id_index_map });
      }
      return sortFlag || sortResult;
    }

    const isValidCurrCellVal = isValidCellValue(currCellVal);
    const isValidNextCellVal = isValidCellValue(nextCellVal);
    if (!isValidCurrCellVal && !isValidNextCellVal) return 0;
    if (!isValidCurrCellVal) return 1;
    if (!isValidNextCellVal) return -1;
    return 0;
  });

  // for nested group.
  const isNestedGroup = Array.isArray(groups[0].subgroups) && groups[0].subgroups.length > 0;
  if (isNestedGroup) {
    const nextLevel = level + 1;

    // eslint-disable-next-line
    groups = groups.map((group) => {
      const sortedSubgroups = getSortedGroups(group.subgroups, groupbys, nextLevel, collaborators);
      return {
        ...group,
        subgroups: sortedSubgroups,
      };
    });
  }
  return groups;
};

const groupRowsWithMultipleGroupbys = (groupbys, rows, collaborators) => {
  const validGroupbys = groupbys.length > MAX_GROUP_LEVEL
    ? groupbys.slice(0, MAX_GROUP_LEVEL)
    : [...groupbys];
  let groups = [];
  let cellValue2GroupIndexMap = {};
  rows.forEach((row) => {
    const rowId = row._id;
    let updatedGroup;
    let updateCellValue2GroupIndexMap;
    for (let level = 0; level < validGroupbys.length; level++) {
      const currentGroupby = validGroupbys[level];
      const { column, column_key } = currentGroupby;
      const { type: columnType } = column;
      const cellValue = getCellValueByColumn(row, column);
      const formattedValue = _getFormattedCellValue(cellValue, currentGroupby);
      const sCellValue = _getStrCellValue(formattedValue, columnType);
      const group = {
        cell_value: formattedValue,
        original_cell_value: cellValue,
        row_ids: null,
        column_key,
        subgroups: [],
        summaries: {},
      };
      if (level === 0) {
        let groupedRowIndex = _findGroupIndexWithMultipleGroupbys(sCellValue, cellValue2GroupIndexMap, groups.length);
        updateCellValue2GroupIndexMap = cellValue2GroupIndexMap[sCellValue].subgroups;
        if (groupedRowIndex < 0) {
          groups.push(group);
          updatedGroup = groups[groups.length - 1];
        } else {
          updatedGroup = groups[groupedRowIndex];
        }
      } else {
        let groupedRowIndex = _findGroupIndexWithMultipleGroupbys(sCellValue, updateCellValue2GroupIndexMap, updatedGroup.subgroups.length);
        updateCellValue2GroupIndexMap = updateCellValue2GroupIndexMap[sCellValue].subgroups;
        if (groupedRowIndex < 0) {
          updatedGroup.subgroups.push(group);
          updatedGroup = updatedGroup.subgroups[updatedGroup.subgroups.length - 1];
        } else {
          updatedGroup = updatedGroup.subgroups[groupedRowIndex];
        }

        // update row_ids in the deepest group.
        if (level === validGroupbys.length - 1) {
          if (!updatedGroup.row_ids) {
            updatedGroup.row_ids = [rowId];
          } else {
            updatedGroup.row_ids.push(rowId);
          }
        }
      }
    }
  });

  groups = getSortedGroups(groups, validGroupbys, 0, collaborators);

  return groups;
};

/**
 * Group table rows
 * @param {array} groupbys e.g. [{ column_key, count_type, column, ... }, ...]
 * @param {array} rows e.g. [{ _id, ... }, ...]
 * @param {array} collaborators e.g. [ collaborator, ... ]
 * @returns groups: [{
 *    cell_value, original_cell_value, column_key,
      row_ids, subgroups, summaries, ...}, ...], array
 */
const groupTableRows = (groupbys, rows, collaborators = []) => {
  if (groupbys.length === 0) {
    return [];
  }
  if (groupbys.length > 1) {
    return groupRowsWithMultipleGroupbys(groupbys, rows, collaborators);
  }
  const groupby = groupbys[0];
  const { column_key, column } = groupby;
  const { type: columnType } = column;
  let groups = [];
  let cellValue2GroupIndexMap = {};
  rows.forEach((r) => {
    const cellValue = getCellValueByColumn(r, column);
    const formattedValue = _getFormattedCellValue(cellValue, groupby);
    const cellValueStr = _getStrCellValue(formattedValue, columnType);
    let groupedRowIndex = _findGroupIndex(cellValueStr, cellValue2GroupIndexMap, groups.length);
    if (groupedRowIndex > -1) {
      groups[groupedRowIndex].row_ids.push(r._id);
    } else {
      groups.push({
        cell_value: formattedValue,
        original_cell_value: cellValue,
        column_key,
        row_ids: [r._id],
        subgroups: null,
        summaries: {},
      });
    }
  });

  // sort groups
  groups = getSortedGroups(groups, groupbys, 0, collaborators);

  return groups;
};

/**
 * Group view rows
 * @param {array} groupbys e.g. [{ column_key, count_type, column, ... }, ...]
 * @param {object} table e.g. { id_row_map, ... }
 * @param {array} rowsIds e.g. [ row._id, ...]
 * @param {array} value e.g. { collaborators }
 * @returns groups: [{
 *    cell_value, original_cell_value, column_key,
      row_ids, subgroups, summaries, ...}, ...], array
 */
const getGroupRows = (table, rows, groupbys, { collaborators }) => {
  if (rows.length === 0) return [];
  if (groupbys.length === 0) return rows;
  let validGroupbys = [];
  try {
    validGroupbys = deleteInvalidGroupby(groupbys, table.columns);
  } catch (err) {
    validGroupbys = [];
  }
  return groupTableRows(validGroupbys, rows, collaborators);
};

export {
  groupTableRows,
  getGroupRows,
};
