import { Grammar } from "./grammar.js";

/**
 * This class is intended as a placeholder for utility methods unrelated
 * to actual classes of the game system or of FoundryVTT
 */
export class Misc {
  static isFunction(v) {
    return v && {}.toString.call(v) === '[object Function]';
  }

  static upperFirst(text) {
    return text.charAt(0).toUpperCase() + text.slice(1);
  }

  static lowerFirst(text) {
    return text.charAt(0).toLowerCase() + text.slice(1);
  }

  static toSignedString(number) {
    const value = parseInt(number)
    const isPositiveNumber = value != NaN && value > 0;
    return isPositiveNumber ? "+" + number : number
  }

  static sum() {
    return (a, b) => a + b;
  }

  static ascending(orderFunction = x => x) {
    return (a, b) => Misc.sortingBy(orderFunction(a), orderFunction(b));
  }

  static descending(orderFunction = x => x) {
    return (a, b) => Misc.sortingBy(orderFunction(b), orderFunction(a));
  }

  static sortingBy(a, b) {
    if (a > b) return 1;
    if (a < b) return -1;
    return 0;
  }

  /**
   * Converts the value to an integer, or to 0 if undefined/null/not representing integer
   * @param {*} value value to convert to an integer using parseInt
   */
  static toInt(value) {
    if (value == undefined) {
      return 0;
    }
    const parsed = parseInt(value);
    return isNaN(parsed) ? 0 : parsed;
  }

  static keepDecimals(num, decimals) {
    if (decimals <= 0 || decimals > 6) return num;
    const decimal = Math.pow(10, parseInt(decimals));
    return Math.round(num * decimal) / decimal;
  }

  static getFractionHtml(diviseur) {
    if (!diviseur || diviseur <= 1) return undefined;
    switch (diviseur || 1) {
      case 2: return '&frac12;';
      case 4: return '&frac14;';
      default: return '1/' + diviseur;
    }
  }

  static classify(items, classifier = it => it.type) {
    let itemsBy = {};
    Misc.classifyInto(itemsBy, items, classifier);
    return itemsBy;
  }

  static classifyFirst(items, classifier) {
    let itemsBy = {};
    for (const item of items) {
      const classification = classifier(item);
      if (!itemsBy[classification]) {
        itemsBy[classification] = item;
      }
    }
    return itemsBy;
  }

  static classifyInto(itemsBy, items, classifier = it => it.type) {
    for (const item of items) {
      const classification = classifier(item);
      let list = itemsBy[classification];
      if (!list) {
        list = [];
        itemsBy[classification] = list;
      }
      list.push(item);
    }
  }

  static distinct(array) {
    return [...new Set(array)];
  }

  static data(it) {
    if (it instanceof Actor || it instanceof Item || it instanceof Combatant) {
      return it.data;
    }
    return it;
  }

  static templateData(it) {
    return Misc.data(it)?.data ?? {}
  }

  static getEntityTypeLabel(entity) {
    const documentName = entity?.documentName;
    const type = entity?.data.type;
    if (documentName === 'Actor' || documentName === 'Item') {
      const label = CONFIG[documentName]?.typeLabels?.[type] ?? type;
      return game.i18n.has(label) ? game.i18n.localize(label) : t;
    }
    return type;
  }

  static connectedGMOrUser(ownerId = undefined) {
    if (ownerId && game.user.id == ownerId) {
      return ownerId;
    }
    return (game.user.isGM ? game.user.id : game.users.entities.find(u => u.isGM && u.active)?.id) ?? game.user.id;
  }
  static isElectedUser() {
    return game.user.id == Misc.connectedGMOrUser();
  }

  /* -------------------------------------------- */
  static findPlayer(name) {
    return Misc.findFirstLike(name, game.users, { description: 'joueur' });
  }

  /* -------------------------------------------- */
  static findActor(name, actors = game.actors) {
    return Misc.findFirstLike(name, actors, { description: 'acteur' });
  }

  /* -------------------------------------------- */
  static findFirstLike(value, elements, options = {}) {
    options = mergeObject({
      mapper: it => it.name,
      preFilter: it => true,
      description: 'valeur',
      onMessage: m => ui.notifications.info(m)
    }, options);

    const subset = this.findAllLike(value, elements, options);
    if (subset.length == 0) {
      return undefined
    }
    if (subset.length == 1) {
      return subset[0]
    }
    let single = subset.find(it => Grammar.toLowerCaseNoAccent(options.mapper(it)) == Grammar.toLowerCaseNoAccent(value));
    if (!single) {
      single = subset[0];
      const choices = subset.map(it => options.mapper(it)).reduce((a, b) => `${a}<br>${b}`);
      options.info(`Plusieurs choix de ${options.description}s possibles:<br>${choices}<br>Le premier sera choisi: ${mapToValue(single)}`);
    }
    return single;
  }

  static findAllLike(value, elements, options = {}) {
    options = mergeObject({
      mapper: it => it.name,
      preFilter: it => true,
      description: 'valeur',
      onMessage: m => ui.notifications.info(m)
    }, options);

    if (!value) {
      options.onMessage(`Pas de ${options.description} correspondant à une valeur vide`);
      return [];
    }
    value = Grammar.toLowerCaseNoAccent(value);
    const subset = elements.filter(options.preFilter)
      .filter(it => Grammar.toLowerCaseNoAccent(options.mapper(it)).includes(value));
    if (subset.length == 0) {
      options.onMessage(`Pas de ${options.description} correspondant à ${value}`);
    }
    return subset;
  }
}