text: /**
   * Accessor class for compiled message functions generated by [messageformat](https://www.npmjs.com/package/messageformat)
   *
   * ```js
   * import Messages from 'messageformat-messages'
   * ```
   *
   * @module messageformat-messages
   */
  
  /**
   * @alias module:messageformat-messages
   *
   * @param {object} msgData A map of locale codes to their function objects
   * @param {string} [defaultLocale] If not defined, default and initial locale is the first entry of `locales`
   *
   * @example
   * ```js
   * // build.js
   * import fs from 'fs';
   * import MessageFormat from 'messageformat';
   *
   * const mf = new MessageFormat(['en', 'fi']);
   * const msgSet = {
   *   en: {
   *     a: 'A {TYPE} example.',
   *     b: 'This has {COUNT, plural, one{one user} other{# users}}.',
   *     c: {
   *       d: 'We have {P, number, percent} code coverage.'
   *     }
   *   },
   *   fi: {
   *     b: 'Tällä on {COUNT, plural, one{yksi käyttäjä} other{# käyttäjää}}.',
   *     e: 'Minä puhun vain suomea.'
   *   }
   * };
   * fs.writeFileSync('messages.js', String(mf.compile(msgSet)));
   * ```
   *
   * ```js
   * // runtime.js
   * import Messages from 'messageformat-messages';
   * import msgData from './messages';
   *
   * const messages = new Messages(msgData, 'en');
   *
   * messages.hasMessage('a')                // true
   * messages.hasObject('c')                 // true
   * messages.get('b', { COUNT: 3 })         // 'This has 3 users.'
   * messages.get(['c', 'd'], { P: 0.314 })  // 'We have 31% code coverage.'
   *
   * messages.get('e')                       // 'e'
   * messages.setFallback('en', ['foo', 'fi'])
   * messages.get('e')                       // 'Minä puhun vain suomea.'
   *
   * messages.locale = 'fi'
   * messages.hasMessage('a')                // false
   * messages.hasMessage('a', 'en')          // true
   * messages.hasMessage('a', null, true)    // true
   * messages.hasObject('c')                 // false
   * messages.get('b', { COUNT: 3 })         // 'Tällä on 3 käyttäjää.'
   * messages.get('c').d({ P: 0.628 })       // 'We have 63% code coverage.'
   * ```
   */
  class Messages {
    constructor(msgData, defaultLocale) {
      this._data = {};
      this._fallback = {};
      Object.keys(msgData).forEach(lc => {
        if (lc !== 'toString') {
          this._data[lc] = msgData[lc];
          if (defaultLocale === undefined) defaultLocale = lc;
        }
      });
      this.locale = defaultLocale;
      this._defaultLocale = this.locale;
    }
  
    /**
     * List of available locales
     * @readonly
     * @memberof module:messageformat-messages
     * @instance
     * @member {string[]} availableLocales
     */
    get availableLocales() {
      return Object.keys(this._data);
    }
  
    /**
     * Current locale
     *
     * One of Messages#availableLocales or `null`. Partial matches of language tags
     * are supported, so e.g. with an `en` locale defined, it will be selected by
     * `messages.locale = 'en-US'` and vice versa.
     *
     * @memberof module:messageformat-messages
     * @instance
     * @member {string|null} locale
     */
    get locale() {
      return this._locale;
    }
    set locale(lc) {
      this._locale = this.resolveLocale(lc);
    }
  
    /**
     * Default fallback locale
     *
     * One of Messages#availableLocales or `null`. Partial matches of language tags
     * are supported, so e.g. with an `en` locale defined, it will be selected by
     * `messages.defaultLocale = 'en-US'` and vice versa.
     *
     * @memberof module:messageformat-messages
     * @instance
     * @member {string|null} defaultLocale
     */
    get defaultLocale() {
      return this._defaultLocale;
    }
    set defaultLocale(lc) {
      this._defaultLocale = this.resolveLocale(lc);
    }
  
    /**
     * Add new messages to the accessor; useful if loading data dynamically
     *
     * The locale code `lc` should be an exact match for the locale being updated,
     * or empty to default to the current locale. Use {@link #resolveLocale} for
     * resolving partial locale strings.
     *
     * If `keypath` is empty, adds or sets the complete message object for the
     * corresponding locale. If any keys in `keypath` do not exist, a new object
     * will be created at that key.
     *
     * @param {function|object} data Hierarchical map of keys to functions, or a
     *   single message function
     * @param {string} [lc] If empty or undefined, defaults to `this.locale`
     * @param {string[]} [keypath] The keypath being added
     * @returns {Messages} The Messages instance, to allow for chaining
     */
    addMessages(data, lc, keypath) {
      if (!lc) lc = this.locale;
      if (typeof data !== 'function') {
        data = Object.keys(data).reduce((map, key) => {
          if (key !== 'toString') map[key] = data[key];
          return map;
        }, {});
      }
      if (Array.isArray(keypath) && keypath.length > 0) {
        let parent = this._data[lc];
        for (let i = 0; i < keypath.length - 1; ++i) {
          const key = keypath[i];
          if (!parent[key]) parent[key] = {};
          parent = parent[key];
        }
        parent[keypath[keypath.length - 1]] = data;
      } else {
        this._data[lc] = data;
      }
      return this;
    }
  
    /**
     * Resolve `lc` to the key of an available locale or `null`, allowing for
     * partial matches. For example, with an `en` locale defined, it will be
     * selected by `messages.defaultLocale = 'en-US'` and vice versa.
     *
     * @param {string} lc Locale code
     * @returns {string|null}
     */
    resolveLocale(lc) {
      if (this._data[lc]) return lc;
      if (lc) {
        let l = String(lc);
        while ((l = l.replace(/[-_]?[^-_]*$/, ''))) {
          if (this._data[l]) return l;
        }
        const ll = this.availableLocales;
        const re = new RegExp('^' + lc + '[-_]');
        for (let i = 0; i < ll.length; ++i) {
          if (re.test(ll[i])) return ll[i];
        }
      }
      return null;
    }
  
    /**
     * Get the list of fallback locales
     * @param {string} [lc] If empty or undefined, defaults to `this.locale`
     * @returns {string[]}
     */
    getFallback(lc) {
      if (!lc) lc = this.locale;
      return (
        this._fallback[lc] ||
        (lc === this.defaultLocale || !this.defaultLocale
          ? []
          : [this.defaultLocale])
      );
    }
  
    /**
     * Set the fallback locale or locales for `lc`
     *
     * To disable fallback for the locale, use `setFallback(lc, [])`.
     * To use the default fallback, use `setFallback(lc, null)`.
     *
     * @param {string} lc
     * @param {string[]|null} fallback
     * @returns {Messages} The Messages instance, to allow for chaining
     */
    setFallback(lc, fallback) {
      this._fallback[lc] = Array.isArray(fallback) ? fallback : null;
      return this;
    }
  
    /**
     * Check if `key` is a message function for the locale
     *
     * `key` may be a `string` for functions at the root level, or `string[]` for
     * accessing hierarchical objects. If an exact match is not found and `fallback`
     * is true, the fallback locales are checked for the first match.
     *
     * @param {string|string[]} key The key or keypath being sought
     * @param {string} [lc] If empty or undefined, defaults to `this.locale`
     * @param {boolean} [fallback=false] If true, also checks fallback locales
     * @returns {boolean}
     */
    hasMessage(key, lc, fallback) {
      if (!lc) lc = this.locale;
      const fb = fallback ? this.getFallback(lc) : null;
      return _has(this._data, lc, key, fb, 'function');
    }
  
    /**
     * Check if `key` is a message object for the locale
     *
     * `key` may be a `string` for functions at the root level, or `string[]` for
     * accessing hierarchical objects. If an exact match is not found and `fallback`
     * is true, the fallback locales are checked for the first match.
     *
     * @param {string|string[]} key The key or keypath being sought
     * @param {string} [lc] If empty or undefined, defaults to `this.locale`
     * @param {boolean} [fallback=false] If true, also checks fallback locales
     * @returns {boolean}
     */
    hasObject(key, lc, fallback) {
      if (!lc) lc = this.locale;
      const fb = fallback ? this.getFallback(lc) : null;
      return _has(this._data, lc, key, fb, 'object');
    }
  
    /**
     * Get the message or object corresponding to `key`
     *
     * `key` may be a `string` for functions at the root level, or `string[]` for
     * accessing hierarchical objects. If an exact match is not found, the fallback
     * locales are checked for the first match.
     *
     * If `key` maps to a message function, it will be called with `props`. If it
     * maps to an object, the object is returned directly.
     *
     * @param {string|string[]} key The key or keypath being sought
     * @param {object} [props] Optional properties passed to the function
     * @param {string} [lc] If empty or undefined, defaults to `this.locale`
     * @returns {string|Object<string,function|object>}
     */
    get(key, props, lc) {
      if (!lc) lc = this.locale;
      let msg = _get(this._data[lc], key);
      if (msg) return typeof msg == 'function' ? msg(props) : msg;
      const fb = this.getFallback(lc);
      for (let i = 0; i < fb.length; ++i) {
        msg = _get(this._data[fb[i]], key);
        if (msg) return typeof msg == 'function' ? msg(props) : msg;
      }
      return key;
    }
  }
  
  /** @private */
  function _get(obj, key) {
    if (!obj) return null;
    if (Array.isArray(key)) {
      for (let i = 0; i < key.length; ++i) {
        obj = obj[key[i]];
        if (!obj) return null;
      }
      return obj;
    }
    return obj[key];
  }
  
  /** @private */
  function _has(data, lc, key, fallback, type) {
    let msg = _get(data[lc], key);
    if (msg) return typeof msg === type;
    if (fallback) {
      for (let i = 0; i < fallback.length; ++i) {
        msg = _get(data[fallback[i]], key);
        if (msg) return typeof msg === type;
      }
    }
    return false;
  }
  
  module.exports = Messages;
  