text: import * as utils from './utils.js';
  import baseLogger from './logger.js';
  
  class Interpolator {
    constructor(options = {}) {
      this.logger = baseLogger.create('interpolator');
  
      this.options = options;
      this.format = (options.interpolation && options.interpolation.format) || (value => value);
      this.init(options);
    }
  
    /* eslint no-param-reassign: 0 */
    init(options = {}) {
      if (!options.interpolation) options.interpolation = { escapeValue: true };
  
      const iOpts = options.interpolation;
  
      this.escape = iOpts.escape !== undefined ? iOpts.escape : utils.escape;
      this.escapeValue = iOpts.escapeValue !== undefined ? iOpts.escapeValue : true;
      this.useRawValueToEscape =
        iOpts.useRawValueToEscape !== undefined ? iOpts.useRawValueToEscape : false;
  
      this.prefix = iOpts.prefix ? utils.regexEscape(iOpts.prefix) : iOpts.prefixEscaped || '{{';
      this.suffix = iOpts.suffix ? utils.regexEscape(iOpts.suffix) : iOpts.suffixEscaped || '}}';
  
      this.formatSeparator = iOpts.formatSeparator
        ? iOpts.formatSeparator
        : iOpts.formatSeparator || ',';
  
      this.unescapePrefix = iOpts.unescapeSuffix ? '' : iOpts.unescapePrefix || '-';
      this.unescapeSuffix = this.unescapePrefix ? '' : iOpts.unescapeSuffix || '';
  
      this.nestingPrefix = iOpts.nestingPrefix
        ? utils.regexEscape(iOpts.nestingPrefix)
        : iOpts.nestingPrefixEscaped || utils.regexEscape('$t(');
      this.nestingSuffix = iOpts.nestingSuffix
        ? utils.regexEscape(iOpts.nestingSuffix)
        : iOpts.nestingSuffixEscaped || utils.regexEscape(')');
  
      this.nestingOptionsSeparator = iOpts.nestingOptionsSeparator
        ? iOpts.nestingOptionsSeparator
        : iOpts.nestingOptionsSeparator || ',';
  
      this.maxReplaces = iOpts.maxReplaces ? iOpts.maxReplaces : 1000;
  
      this.alwaysFormat = iOpts.alwaysFormat !== undefined ? iOpts.alwaysFormat : false;
  
      // the regexp
      this.resetRegExp();
    }
  
    reset() {
      if (this.options) this.init(this.options);
    }
  
    resetRegExp() {
      // the regexp
      const regexpStr = `${this.prefix}(.+?)${this.suffix}`;
      this.regexp = new RegExp(regexpStr, 'g');
  
      const regexpUnescapeStr = `${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${
        this.suffix
      }`;
      this.regexpUnescape = new RegExp(regexpUnescapeStr, 'g');
  
      const nestingRegexpStr = `${this.nestingPrefix}(.+?)${this.nestingSuffix}`;
      this.nestingRegexp = new RegExp(nestingRegexpStr, 'g');
    }
  
    interpolate(str, data, lng, options) {
      let match;
      let value;
      let replaces;
  
      const defaultData =
        (this.options && this.options.interpolation && this.options.interpolation.defaultVariables) ||
        {};
  
      function regexSafe(val) {
        return val.replace(/\$/g, '$$$$');
      }
  
      const handleFormat = key => {
        if (key.indexOf(this.formatSeparator) < 0) {
          const path = utils.getPathWithDefaults(data, defaultData, key);
          return this.alwaysFormat ? this.format(path, undefined, lng) : path;
        }
  
        const p = key.split(this.formatSeparator);
        const k = p.shift().trim();
        const f = p.join(this.formatSeparator).trim();
  
        return this.format(utils.getPathWithDefaults(data, defaultData, k), f, lng, options);
      };
  
      this.resetRegExp();
  
      const missingInterpolationHandler =
        (options && options.missingInterpolationHandler) || this.options.missingInterpolationHandler;
  
      replaces = 0;
      // unescape if has unescapePrefix/Suffix
      /* eslint no-cond-assign: 0 */
      while ((match = this.regexpUnescape.exec(str))) {
        value = handleFormat(match[1].trim());
        if (value === undefined) {
          if (typeof missingInterpolationHandler === 'function') {
            const temp = missingInterpolationHandler(str, match, options);
            value = typeof temp === 'string' ? temp : '';
          } else {
            this.logger.warn(`missed to pass in variable ${match[1]} for interpolating ${str}`);
            value = '';
          }
        } else if (typeof value !== 'string' && !this.useRawValueToEscape) {
          value = utils.makeString(value);
        }
        str = str.replace(match[0], regexSafe(value));
        this.regexpUnescape.lastIndex = 0;
        replaces++;
        if (replaces >= this.maxReplaces) {
          break;
        }
      }
  
      replaces = 0;
      // regular escape on demand
      while ((match = this.regexp.exec(str))) {
        value = handleFormat(match[1].trim());
        if (value === undefined) {
          if (typeof missingInterpolationHandler === 'function') {
            const temp = missingInterpolationHandler(str, match, options);
            value = typeof temp === 'string' ? temp : '';
          } else {
            this.logger.warn(`missed to pass in variable ${match[1]} for interpolating ${str}`);
            value = '';
          }
        } else if (typeof value !== 'string' && !this.useRawValueToEscape) {
          value = utils.makeString(value);
        }
        value = this.escapeValue ? regexSafe(this.escape(value)) : regexSafe(value);
        str = str.replace(match[0], value);
        this.regexp.lastIndex = 0;
        replaces++;
        if (replaces >= this.maxReplaces) {
          break;
        }
      }
      return str;
    }
  
    nest(str, fc, options = {}) {
      let match;
      let value;
  
      let clonedOptions = { ...options };
      clonedOptions.applyPostProcessor = false; // avoid post processing on nested lookup
      delete clonedOptions.defaultValue; // assert we do not get a endless loop on interpolating defaultValue again and again
  
      // if value is something like "myKey": "lorem $(anotherKey, { "count": {{aValueInOptions}} })"
      function handleHasOptions(key, inheritedOptions) {
        const sep = this.nestingOptionsSeparator;
        if (key.indexOf(sep) < 0) return key;
  
        const c = key.split(new RegExp(`${sep}[ ]*{`));
  
        let optionsString = `{${c[1]}`;
        key = c[0];
        optionsString = this.interpolate(optionsString, clonedOptions);
        optionsString = optionsString.replace(/'/g, '"');
  
        try {
          clonedOptions = JSON.parse(optionsString);
  
          if (inheritedOptions) clonedOptions = { ...inheritedOptions, ...clonedOptions };
        } catch (e) {
          this.logger.warn(`failed parsing options string in nesting for key ${key}`, e);
          return `${key}${sep}${optionsString}`;
        }
  
        // assert we do not get a endless loop on interpolating defaultValue again and again
        delete clonedOptions.defaultValue;
        return key;
      }
  
      // regular escape on demand
      while ((match = this.nestingRegexp.exec(str))) {
        let formatters = [];
  
        /**
         * If there is more than one parameter (contains the format separator). E.g.:
         *   - t(a, b)
         *   - t(a, b, c)
         *
         * And those parameters are not dynamic values (parameters do not include curly braces). E.g.:
         *   - Not t(a, { "key": "{{variable}}" })
         *   - Not t(a, b, {"keyA": "valueA", "keyB": "valueB"})
         */
        let doReduce = false;
        if (match[0].includes(this.formatSeparator) && !/{.*}/.test(match[1])) {
          [match[1], ...formatters] = match[1].split(this.formatSeparator).map(elem => elem.trim());
          doReduce = true;
        }
  
        value = fc(handleHasOptions.call(this, match[1].trim(), clonedOptions), clonedOptions);
  
        // is only the nesting key (key1 = '$(key2)') return the value without stringify
        if (value && match[0] === str && typeof value !== 'string') return value;
  
        // no string to include or empty
        if (typeof value !== 'string') value = utils.makeString(value);
        if (!value) {
          this.logger.warn(`missed to resolve ${match[1]} for nesting ${str}`);
          value = '';
        }
  
        if (doReduce) {
          value = formatters.reduce((v, f) => this.format(v, f, options.lng, options), value.trim());
        }
  
        // Nested keys should not be escaped by default #854
        // value = this.escapeValue ? regexSafe(utils.escape(value)) : regexSafe(value);
        str = str.replace(match[0], value);
        this.regexp.lastIndex = 0;
      }
      return str;
    }
  }
  
  export default Interpolator;
  