var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _CoreManager = _interopRequireDefault(require("./CoreManager"));
var _ParseQuery = _interopRequireDefault(require("./ParseQuery"));
var _LocalDatastoreUtils = require("./LocalDatastoreUtils");
var LocalDatastore = {
  isEnabled: false,
  isSyncing: false,
  fromPinWithName: function (name) {
    var controller = _CoreManager.default.getLocalDatastoreController();
    return controller.fromPinWithName(name);
  },
  pinWithName: function (name, value) {
    var controller = _CoreManager.default.getLocalDatastoreController();
    return controller.pinWithName(name, value);
  },
  unPinWithName: function (name) {
    var controller = _CoreManager.default.getLocalDatastoreController();
    return controller.unPinWithName(name);
  },
  _getAllContents: function () {
    var controller = _CoreManager.default.getLocalDatastoreController();
    return controller.getAllContents();
  },
  _getRawStorage: function () {
    var controller = _CoreManager.default.getLocalDatastoreController();
    return controller.getRawStorage();
  },
  _clear: function () {
    var controller = _CoreManager.default.getLocalDatastoreController();
    return controller.clear();
  },
  _handlePinAllWithName: function () {
    var _handlePinAllWithName2 = (0, _asyncToGenerator2.default)(function* (name, objects) {
      var pinName = this.getPinName(name);
      var toPinPromises = [];
      var objectKeys = [];
      for (var parent of objects) {
        var children = this._getChildren(parent);
        var parentKey = this.getKeyForObject(parent);
        var json = parent._toFullJSON(undefined, true);
        if (parent._localId) {
          json._localId = parent._localId;
        }
        children[parentKey] = json;
        for (var objectKey in children) {
          objectKeys.push(objectKey);
          toPinPromises.push(this.pinWithName(objectKey, [children[objectKey]]));
        }
      }
      var fromPinPromise = this.fromPinWithName(pinName);
      var _yield$Promise$all = yield Promise.all([fromPinPromise, toPinPromises]),
        _yield$Promise$all2 = (0, _slicedToArray2.default)(_yield$Promise$all, 1),
        pinned = _yield$Promise$all2[0];
      var toPin = (0, _toConsumableArray2.default)(new Set([].concat((0, _toConsumableArray2.default)(pinned || []), objectKeys)));
      return this.pinWithName(pinName, toPin);
    });
    function _handlePinAllWithName() {
      return _handlePinAllWithName2.apply(this, arguments);
    }
    return _handlePinAllWithName;
  }(),
  _handleUnPinAllWithName: function () {
    var _handleUnPinAllWithName2 = (0, _asyncToGenerator2.default)(function* (name, objects) {
      var localDatastore = yield this._getAllContents();
      var pinName = this.getPinName(name);
      var promises = [];
      var objectKeys = [];
      for (var parent of objects) {
        var _objectKeys;
        var children = this._getChildren(parent);
        var parentKey = this.getKeyForObject(parent);
        (_objectKeys = objectKeys).push.apply(_objectKeys, [parentKey].concat((0, _toConsumableArray2.default)(Object.keys(children))));
      }
      objectKeys = (0, _toConsumableArray2.default)(new Set(objectKeys));
      var pinned = localDatastore[pinName] || [];
      pinned = pinned.filter(function (item) {
        return !objectKeys.includes(item);
      });
      if (pinned.length == 0) {
        promises.push(this.unPinWithName(pinName));
        delete localDatastore[pinName];
      } else {
        promises.push(this.pinWithName(pinName, pinned));
        localDatastore[pinName] = pinned;
      }
      for (var objectKey of objectKeys) {
        var hasReference = false;
        for (var key in localDatastore) {
          if (key === _LocalDatastoreUtils.DEFAULT_PIN || key.startsWith(_LocalDatastoreUtils.PIN_PREFIX)) {
            var pinnedObjects = localDatastore[key] || [];
            if (pinnedObjects.includes(objectKey)) {
              hasReference = true;
              break;
            }
          }
        }
        if (!hasReference) {
          promises.push(this.unPinWithName(objectKey));
        }
      }
      return Promise.all(promises);
    });
    function _handleUnPinAllWithName() {
      return _handleUnPinAllWithName2.apply(this, arguments);
    }
    return _handleUnPinAllWithName;
  }(),
  _getChildren: function (object) {
    var encountered = {};
    var json = object._toFullJSON(undefined, true);
    for (var key in json) {
      if (json[key] && json[key].__type && json[key].__type === 'Object') {
        this._traverse(json[key], encountered);
      }
    }
    return encountered;
  },
  _traverse: function (object, encountered) {
    if (!object.objectId) {
      return;
    } else {
      var objectKey = this.getKeyForObject(object);
      if (encountered[objectKey]) {
        return;
      }
      encountered[objectKey] = object;
    }
    for (var key in object) {
      var json = object[key];
      if (!object[key]) {
        json = object;
      }
      if (json.__type && json.__type === 'Object') {
        this._traverse(json, encountered);
      }
    }
  },
  _serializeObjectsFromPinName: function () {
    var _serializeObjectsFromPinName2 = (0, _asyncToGenerator2.default)(function* (name) {
      var _this = this,
        _ref;
      var localDatastore = yield this._getAllContents();
      var allObjects = [];
      for (var key in localDatastore) {
        if (key.startsWith(_LocalDatastoreUtils.OBJECT_PREFIX)) {
          allObjects.push(localDatastore[key][0]);
        }
      }
      if (!name) {
        return allObjects;
      }
      var pinName = this.getPinName(name);
      var pinned = localDatastore[pinName];
      if (!Array.isArray(pinned)) {
        return [];
      }
      var promises = pinned.map(function (objectKey) {
        return _this.fromPinWithName(objectKey);
      });
      var objects = yield Promise.all(promises);
      objects = (_ref = []).concat.apply(_ref, (0, _toConsumableArray2.default)(objects));
      return objects.filter(function (object) {
        return object != null;
      });
    });
    function _serializeObjectsFromPinName() {
      return _serializeObjectsFromPinName2.apply(this, arguments);
    }
    return _serializeObjectsFromPinName;
  }(),
  _serializeObject: function () {
    var _serializeObject2 = (0, _asyncToGenerator2.default)(function* (objectKey, localDatastore) {
      var LDS = localDatastore;
      if (!LDS) {
        LDS = yield this._getAllContents();
      }
      if (!LDS[objectKey] || LDS[objectKey].length === 0) {
        return null;
      }
      var root = LDS[objectKey][0];
      var queue = [];
      var meta = {};
      var uniqueId = 0;
      meta[uniqueId] = root;
      queue.push(uniqueId);
      while (queue.length !== 0) {
        var nodeId = queue.shift();
        var subTreeRoot = meta[nodeId];
        for (var field in subTreeRoot) {
          var value = subTreeRoot[field];
          if (value.__type && value.__type === 'Object') {
            var key = this.getKeyForObject(value);
            if (LDS[key] && LDS[key].length > 0) {
              var pointer = LDS[key][0];
              uniqueId++;
              meta[uniqueId] = pointer;
              subTreeRoot[field] = pointer;
              queue.push(uniqueId);
            }
          }
        }
      }
      return root;
    });
    function _serializeObject() {
      return _serializeObject2.apply(this, arguments);
    }
    return _serializeObject;
  }(),
  _updateObjectIfPinned: function () {
    var _updateObjectIfPinned2 = (0, _asyncToGenerator2.default)(function* (object) {
      if (!this.isEnabled) {
        return;
      }
      var objectKey = this.getKeyForObject(object);
      var pinned = yield this.fromPinWithName(objectKey);
      if (!pinned || pinned.length === 0) {
        return;
      }
      return this.pinWithName(objectKey, [object._toFullJSON()]);
    });
    function _updateObjectIfPinned() {
      return _updateObjectIfPinned2.apply(this, arguments);
    }
    return _updateObjectIfPinned;
  }(),
  _destroyObjectIfPinned: function () {
    var _destroyObjectIfPinned2 = (0, _asyncToGenerator2.default)(function* (object) {
      if (!this.isEnabled) {
        return;
      }
      var localDatastore = yield this._getAllContents();
      var objectKey = this.getKeyForObject(object);
      var pin = localDatastore[objectKey];
      if (!pin) {
        return;
      }
      var promises = [this.unPinWithName(objectKey)];
      delete localDatastore[objectKey];
      for (var key in localDatastore) {
        if (key === _LocalDatastoreUtils.DEFAULT_PIN || key.startsWith(_LocalDatastoreUtils.PIN_PREFIX)) {
          var pinned = localDatastore[key] || [];
          if (pinned.includes(objectKey)) {
            pinned = pinned.filter(function (item) {
              return item !== objectKey;
            });
            if (pinned.length == 0) {
              promises.push(this.unPinWithName(key));
              delete localDatastore[key];
            } else {
              promises.push(this.pinWithName(key, pinned));
              localDatastore[key] = pinned;
            }
          }
        }
      }
      return Promise.all(promises);
    });
    function _destroyObjectIfPinned() {
      return _destroyObjectIfPinned2.apply(this, arguments);
    }
    return _destroyObjectIfPinned;
  }(),
  _updateLocalIdForObject: function () {
    var _updateLocalIdForObject2 = (0, _asyncToGenerator2.default)(function* (localId, object) {
      if (!this.isEnabled) {
        return;
      }
      var localKey = `${_LocalDatastoreUtils.OBJECT_PREFIX}${object.className}_${localId}`;
      var objectKey = this.getKeyForObject(object);
      var unsaved = yield this.fromPinWithName(localKey);
      if (!unsaved || unsaved.length === 0) {
        return;
      }
      var promises = [this.unPinWithName(localKey), this.pinWithName(objectKey, unsaved)];
      var localDatastore = yield this._getAllContents();
      for (var key in localDatastore) {
        if (key === _LocalDatastoreUtils.DEFAULT_PIN || key.startsWith(_LocalDatastoreUtils.PIN_PREFIX)) {
          var pinned = localDatastore[key] || [];
          if (pinned.includes(localKey)) {
            pinned = pinned.filter(function (item) {
              return item !== localKey;
            });
            pinned.push(objectKey);
            promises.push(this.pinWithName(key, pinned));
            localDatastore[key] = pinned;
          }
        }
      }
      return Promise.all(promises);
    });
    function _updateLocalIdForObject() {
      return _updateLocalIdForObject2.apply(this, arguments);
    }
    return _updateLocalIdForObject;
  }(),
  updateFromServer: function () {
    var _updateFromServer = (0, _asyncToGenerator2.default)(function* () {
      var _this2 = this;
      if (!this.checkIfEnabled() || this.isSyncing) {
        return;
      }
      var localDatastore = yield this._getAllContents();
      var keys = [];
      for (var key in localDatastore) {
        if (key.startsWith(_LocalDatastoreUtils.OBJECT_PREFIX)) {
          keys.push(key);
        }
      }
      if (keys.length === 0) {
        return;
      }
      this.isSyncing = true;
      var pointersHash = {};
      for (var _key of keys) {
        var _key$split = _key.split('_'),
          _key$split2 = (0, _slicedToArray2.default)(_key$split, 4),
          className = _key$split2[2],
          objectId = _key$split2[3];
        if (_key.split('_').length === 5 && _key.split('_')[3] === 'User') {
          className = '_User';
          objectId = _key.split('_')[4];
        }
        if (objectId.startsWith('local')) {
          continue;
        }
        if (!(className in pointersHash)) {
          pointersHash[className] = new Set();
        }
        pointersHash[className].add(objectId);
      }
      var queryPromises = Object.keys(pointersHash).map(function (className) {
        var objectIds = Array.from(pointersHash[className]);
        var query = new _ParseQuery.default(className);
        query.limit(objectIds.length);
        if (objectIds.length === 1) {
          query.equalTo('objectId', objectIds[0]);
        } else {
          query.containedIn('objectId', objectIds);
        }
        return query.find();
      });
      try {
        var responses = yield Promise.all(queryPromises);
        var objects = [].concat.apply([], responses);
        var pinPromises = objects.map(function (object) {
          var objectKey = _this2.getKeyForObject(object);
          return _this2.pinWithName(objectKey, object._toFullJSON());
        });
        yield Promise.all(pinPromises);
        this.isSyncing = false;
      } catch (error) {
        console.error('Error syncing LocalDatastore: ', error);
        this.isSyncing = false;
      }
    });
    function updateFromServer() {
      return _updateFromServer.apply(this, arguments);
    }
    return updateFromServer;
  }(),
  getKeyForObject: function (object) {
    var objectId = object.objectId || object._getId();
    return `${_LocalDatastoreUtils.OBJECT_PREFIX}${object.className}_${objectId}`;
  },
  getPinName: function (pinName) {
    if (!pinName || pinName === _LocalDatastoreUtils.DEFAULT_PIN) {
      return _LocalDatastoreUtils.DEFAULT_PIN;
    }
    return _LocalDatastoreUtils.PIN_PREFIX + pinName;
  },
  checkIfEnabled: function () {
    if (!this.isEnabled) {
      console.error('Parse.enableLocalDatastore() must be called first');
    }
    return this.isEnabled;
  }
};
module.exports = LocalDatastore;
_CoreManager.default.setLocalDatastoreController(require('./LocalDatastoreController.react-native'));
_CoreManager.default.setLocalDatastore(LocalDatastore);