/*
This file is part of the Notesnook project (https://notesnook.com/)

Copyright (C) 2022 Streetwriters (Private) Limited

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

import "../types";
import { getWeekGroupFromTimestamp, MONTHS_FULL } from "./date";

const MILLISECONDS_IN_DAY = 1000 * 60 * 60 * 24;
const MILLISECONDS_IN_WEEK = MILLISECONDS_IN_DAY * 7;

const comparators = {
  dateEdited: {
    asc: (a, b) => a.dateEdited - b.dateEdited,
    desc: (a, b) => b.dateEdited - a.dateEdited
  },
  dateCreated: {
    asc: (a, b) => a.dateCreated - b.dateCreated,
    desc: (a, b) => b.dateCreated - a.dateCreated
  },
  dateDeleted: {
    asc: (a, b) => a.dateDeleted - b.dateDeleted,
    desc: (a, b) => b.dateDeleted - a.dateDeleted
  },
  title: {
    asc: (a, b) => getTitle(a).localeCompare(getTitle(b)),
    desc: (a, b) => getTitle(b).localeCompare(getTitle(a))
  }
};

function getTitle(item) {
  return item.alias || item.title;
}

const KEY_SELECTORS = {
  abc: (item) => getFirstCharacter(item.alias || item.title),
  month: (item, groupBy, dateNow) => {
    dateNow.setTime(item[groupBy]);
    return MONTHS_FULL[dateNow.getMonth()];
  },
  week: (item, groupBy) => getWeekGroupFromTimestamp(item[groupBy]),
  year: (item, groupBy, dateNow) => {
    dateNow.setTime(item[groupBy]);
    return dateNow.getFullYear();
  },
  default: (item, groupBy, dateNow) => {
    const date = item[groupBy];
    return date > dateNow.getTime() - MILLISECONDS_IN_WEEK
      ? "Recent"
      : date > dateNow.getTime() - MILLISECONDS_IN_WEEK * 2
      ? "Last week"
      : "Older";
  }
};

/**
 * @param {any[]} array
 * @param {GroupOptions} options
 * @returns Grouped array
 */
export function groupArray(
  array,
  options = {
    groupBy: "default",
    sortBy: "dateEdited",
    sortDirection: "desc"
  }
) {
  const cachedDate = new Date();

  if (options.sortBy && options.sortDirection) {
    const selector = comparators[options.sortBy][options.sortDirection];
    array.sort(selector);
  }

  if (options.groupBy === "none") {
    const conflicted = [];
    const pinned = [];
    const others = [];
    for (const item of array) {
      if (item.pinned) {
        pinned.push(item);
        continue;
      } else if (item.conflicted) {
        conflicted.push(item);
        continue;
      } else others.push(item);
    }
    const groups = [];
    if (conflicted.length > 0)
      groups.push({ title: "Conflicted", type: "header" }, ...conflicted);
    if (pinned.length > 0)
      groups.push({ title: "Pinned", type: "header" }, ...pinned);
    if (others.length > 0)
      groups.push({ title: "All", type: "header" }, ...others);
    return groups;
  }

  const groups = new Map([
    ["Conflicted", []],
    ["Pinned", []]
  ]);

  const keySelector = KEY_SELECTORS[options.groupBy || "default"];
  array.forEach((item) => {
    if (item.pinned) {
      return addToGroup(groups, "Pinned", item);
    } else if (item.conflicted) return addToGroup(groups, "Conflicted", item);

    const groupTitle = keySelector(item, options.sortBy, cachedDate);
    addToGroup(groups, groupTitle, item);
  });

  let items = [];
  for (let [groupTitle, groupItems] of groups.entries()) {
    if (!groupItems.length) continue;

    let group = { title: groupTitle, type: "header" };
    items.push(group);
    groupItems.forEach((item) => items.push(item));
  }

  return items;
}

function addToGroup(groups, groupTitle, item) {
  const group = groups.get(groupTitle) || [];
  group.push(item);
  groups.set(groupTitle, group);
}

const REGEX = /\S/;
function getFirstCharacter(str) {
  if (!str) return "-";
  return REGEX.exec(str)[0].toUpperCase();
}
