import uniq from "lodash/uniq";

import { Dict, LocaleResolver } from "./typing";
import { I18n } from "./I18n";

/**
 * The default locale resolver.
 *
 * This resolver will add `options.locale` if provided (this function receives
 * it inlined). In case nothing is set, then `i18n.locale` will be used.
 * Additionally, adds the default locale to the list if `i18n.enableFallback` is
 * set.
 *
 * Every locale added to the list will then be split apart; if `pt-BR` is the
 * locale, then the list will be returned as `["pt-BR", "pt"]`.
 *
 * The default in case nothing is defined is `["en"]`.
 *
 * @type {LocaleResolver}
 *
 * @param {I18n} i18n The I18n instance.
 *
 * @param {string} locale The locale that being analysed.
 *
 * @returns {string[]} The resolved locales.
 */
export const defaultLocaleResolver: LocaleResolver = (
  i18n: I18n,
  locale: string,
): string[] => {
  const locales = [];
  const list: string[] = [];

  // Handle the inline locale option that can be provided to
  // the `I18n.t` options.
  locales.push(locale);

  // Add the current locale to the list.
  if (!locale) {
    locales.push(i18n.locale);
  }

  // Add the default locale if fallback strategy is enabled.
  if (i18n.enableFallback) {
    locales.push(i18n.defaultLocale);
  }

  // Compute each locale with its country code.
  // So this will return an array containing both
  // `de-DE` and `de` locales.
  //
  // We also need to support locales with region code (e.g. zh-Hant-TW).
  // In that case, the list should be `["zh-hant-tw", "zh-hant", "zh"]`.
  locales
    .filter(Boolean)
    .map((entry) => entry.toString())
    .forEach(function (currentLocale: string) {
      if (!list.includes(currentLocale)) {
        list.push(currentLocale);
      }

      if (!i18n.enableFallback) {
        return;
      }

      const codes = currentLocale.split("-");

      if (codes.length === 3) {
        list.push(`${codes[0]}-${codes[1]}`);
      }

      list.push(codes[0]);
    });

  return uniq(list);
};

export class Locales {
  private i18n: I18n;
  private registry: Dict;

  constructor(i18n: I18n) {
    this.i18n = i18n;
    this.registry = {};

    this.register("default", defaultLocaleResolver);
  }

  /**
   * You can define custom rules for any locale. Just make sure you return an
   * array containing all locales.
   *
   * ```js
   * // Default the Wookie locale to English.
   * i18n.locales.register("wk",  (_i18n, locale) => {
   *   return ["en"];
   * });
   * ```
   *
   * @param {string} locale The locale's name.
   *
   * @param {LocaleResolver|string|string[]} localeResolver The locale resolver
   * strategy.
   *
   * @returns {void}
   */
  public register(
    locale: string,
    localeResolver: LocaleResolver | string | string[],
  ): void {
    if (typeof localeResolver !== "function") {
      const result = localeResolver;
      localeResolver = (() => result) as LocaleResolver;
    }

    this.registry[locale] = localeResolver;
  }

  /**
   * Return a list of all locales that must be tried before returning the
   * missing translation message. By default, this will consider the inline
   * option, current locale and fallback locale.
   *
   * ```js
   * i18n.locales.get("de-DE");
   * // ["de-DE", "de", "en"]
   * ```
   *
   * @param {string} locale The locale query.
   *
   * @returns {string[]} The list of locales.
   */
  public get(locale: string): string[] {
    let locales =
      this.registry[locale] ||
      this.registry[this.i18n.locale] ||
      this.registry.default;

    if (typeof locales === "function") {
      locales = locales(this.i18n, locale);
    }

    if (!(locales instanceof Array)) {
      locales = [locales];
    }
    return locales;
  }
}
