text: import * as utils from './utils.js';
  import baseLogger from './logger.js';
  import EventEmitter from './EventEmitter.js';
  
  function remove(arr, what) {
    let found = arr.indexOf(what);
  
    while (found !== -1) {
      arr.splice(found, 1);
      found = arr.indexOf(what);
    }
  }
  
  class Connector extends EventEmitter {
    constructor(backend, store, services, options = {}) {
      super();
      this.backend = backend;
      this.store = store;
      this.languageUtils = services.languageUtils;
      this.options = options;
      this.logger = baseLogger.create('backendConnector');
  
      this.state = {};
      this.queue = [];
  
      if (this.backend && this.backend.init) {
        this.backend.init(services, options.backend, options);
      }
    }
  
    queueLoad(languages, namespaces, options, callback) {
      // find what needs to be loaded
      const toLoad = [];
      const pending = [];
      const toLoadLanguages = [];
      const toLoadNamespaces = [];
  
      languages.forEach((lng) => {
        let hasAllNamespaces = true;
  
        namespaces.forEach((ns) => {
          const name = `${lng}|${ns}`;
  
          if (!options.reload && this.store.hasResourceBundle(lng, ns)) {
            this.state[name] = 2; // loaded
          } else if (this.state[name] < 0) {
            // nothing to do for err
          } else if (this.state[name] === 1) {
            if (pending.indexOf(name) < 0) pending.push(name);
          } else {
            this.state[name] = 1; // pending
  
            hasAllNamespaces = false;
  
            if (pending.indexOf(name) < 0) pending.push(name);
            if (toLoad.indexOf(name) < 0) toLoad.push(name);
            if (toLoadNamespaces.indexOf(ns) < 0) toLoadNamespaces.push(ns);
          }
        });
  
        if (!hasAllNamespaces) toLoadLanguages.push(lng);
      });
  
      if (toLoad.length || pending.length) {
        this.queue.push({
          pending,
          loaded: {},
          errors: [],
          callback
        });
      }
  
      return {
        toLoad,
        pending,
        toLoadLanguages,
        toLoadNamespaces
      };
    }
  
    loaded(name, err, data) {
      const [lng, ns] = name.split('|');
  
      if (err) this.emit('failedLoading', lng, ns, err);
  
      if (data) {
        this.store.addResourceBundle(lng, ns, data);
      }
  
      // set loaded
      this.state[name] = err ? -1 : 2;
  
      // consolidated loading done in this run - only emit once for a loaded namespace
      const loaded = {};
  
      // callback if ready
      this.queue.forEach((q) => {
        utils.pushPath(q.loaded, [lng], ns);
        remove(q.pending, name);
  
        if (err) q.errors.push(err);
  
        if (q.pending.length === 0 && !q.done) {
          // only do once per loaded -> this.emit('loaded', q.loaded);
          Object.keys(q.loaded).forEach(l => {
            if (!loaded[l]) loaded[l] = [];
            if (q.loaded[l].length) {
              q.loaded[l].forEach(ns => {
                if (loaded[l].indexOf(ns) < 0) loaded[l].push(ns);
              });
            }
          });
  
          /* eslint no-param-reassign: 0 */
          q.done = true;
          if (q.errors.length) {
            q.callback(q.errors);
          } else {
            q.callback();
          }
        }
      });
  
      // emit consolidated loaded event
      this.emit('loaded', loaded);
  
      // remove done load requests
      this.queue = this.queue.filter(q => !q.done);
    }
  
    read(lng, ns, fcName, tried = 0, wait = 250, callback) {
      if (!lng.length) return callback(null, {}); // noting to load
  
      return this.backend[fcName](lng, ns, (err, data) => {
        if (err && data /* = retryFlag */ && tried < 5) {
          setTimeout(() => {
            this.read.call(this, lng, ns, fcName, tried + 1, wait * 2, callback);
          }, wait);
          return;
        }
        callback(err, data);
      });
    }
  
    /* eslint consistent-return: 0 */
    prepareLoading(languages, namespaces, options = {}, callback) {
      if (!this.backend) {
        this.logger.warn('No backend was added via i18next.use. Will not load resources.');
        return callback && callback();
      }
  
      if (typeof languages === 'string') languages = this.languageUtils.toResolveHierarchy(languages);
      if (typeof namespaces === 'string') namespaces = [namespaces];
  
      const toLoad = this.queueLoad(languages, namespaces, options, callback);
      if (!toLoad.toLoad.length) {
        if (!toLoad.pending.length) callback(); // nothing to load and no pendings...callback now
        return null; // pendings will trigger callback
      }
  
      toLoad.toLoad.forEach((name) => {
        this.loadOne(name);
      });
    }
  
    load(languages, namespaces, callback) {
      this.prepareLoading(languages, namespaces, {}, callback)
    }
  
    reload(languages, namespaces, callback) {
      this.prepareLoading(languages, namespaces, { reload: true }, callback)
    }
  
    loadOne(name, prefix = '') {
      const [lng, ns] = name.split('|');
  
      this.read(lng, ns, 'read', null, null, (err, data) => {
        if (err) this.logger.warn(`${prefix}loading namespace ${ns} for language ${lng} failed`, err);
        if (!err && data) this.logger.log(`${prefix}loaded namespace ${ns} for language ${lng}`, data);
  
        this.loaded(name, err, data);
      });
    }
  
    saveMissing(languages, namespace, key, fallbackValue, isUpdate, options = {}) {
      if (this.backend && this.backend.create) {
        this.backend.create(languages, namespace, key, fallbackValue, null /* unused callback */, { ...options, isUpdate });
      }
  
      // write to store to avoid resending
      if (!languages || !languages[0]) return;
      this.store.addResource(languages[0], namespace, key, fallbackValue);
    }
  }
  
  export default Connector;
  