define('ember-cli-mirage/orm/model', ['exports', 'ember-cli-mirage/orm/associations/belongs-to', 'ember-cli-mirage/orm/associations/has-many', 'ember-cli-mirage/utils/normalize-name', 'ember-cli-mirage/utils/extend', 'ember-cli-mirage/assert', 'ember-cli-mirage/orm/collection', 'ember-cli-mirage/orm/polymorphic-collection', 'lodash/values', 'lodash/compact', 'lodash/assign'], function (exports, _belongsTo, _hasMany, _normalizeName, _extend, _assert, _collection, _polymorphicCollection, _values2, _compact2, _assign2) {
  'use strict';

  Object.defineProperty(exports, "__esModule", {
    value: true
  });

  function _defineProperty(obj, key, value) {
    if (key in obj) {
      Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
      });
    } else {
      obj[key] = value;
    }

    return obj;
  }

  var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
    return typeof obj;
  } : function (obj) {
    return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
  };

  function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function");
    }
  }

  var _createClass = function () {
    function defineProperties(target, props) {
      for (var i = 0; i < props.length; i++) {
        var descriptor = props[i];
        descriptor.enumerable = descriptor.enumerable || false;
        descriptor.configurable = true;
        if ("value" in descriptor) descriptor.writable = true;
        Object.defineProperty(target, descriptor.key, descriptor);
      }
    }

    return function (Constructor, protoProps, staticProps) {
      if (protoProps) defineProperties(Constructor.prototype, protoProps);
      if (staticProps) defineProperties(Constructor, staticProps);
      return Constructor;
    };
  }();

  var Model = function () {

    // TODO: schema and modelName now set statically at registration, need to remove
    function Model(schema, modelName, attrs, fks) {
      _classCallCheck(this, Model);

      (0, _assert.default)(schema, 'A model requires a schema');
      (0, _assert.default)(modelName, 'A model requires a modelName');

      this.schema = schema;
      this.modelName = modelName;
      this.fks = fks || [];
      attrs = attrs || {};

      this._setupAttrs(attrs);
      this._setupRelationships(attrs);

      return this;
    }

    /**
     * Creates or saves the model.
     * @method save
     * @return this
     * @public
     */


    _createClass(Model, [{
      key: 'save',
      value: function save() {
        var collection = (0, _normalizeName.toInternalCollectionName)(this.modelName);

        if (this.isNew()) {
          // Update the attrs with the db response
          this.attrs = this.schema.db[collection].insert(this.attrs);

          // Ensure the id getter/setter is set
          this._definePlainAttribute('id');
        } else {
          this.schema.isSaving[this.toString()] = true;
          this.schema.db[collection].update(this.attrs.id, this.attrs);
        }

        this._saveAssociations();

        this.schema.isSaving[this.toString()] = false;
        return this;
      }
    }, {
      key: 'update',
      value: function update(key, val) {
        var attrs = void 0;
        if (key == null) {
          return this;
        }

        if ((typeof key === 'undefined' ? 'undefined' : _typeof(key)) === 'object') {
          attrs = key;
        } else {
          (attrs = {})[key] = val;
        }

        Object.keys(attrs).forEach(function (attr) {
          if (this.associationKeys.indexOf(attr) === -1 && this.associationIdKeys.indexOf(attr) === -1) {
            this._definePlainAttribute(attr);
          }
          this[attr] = attrs[attr];
        }, this);

        this.save();

        return this;
      }
    }, {
      key: 'destroy',
      value: function destroy() {
        if (this.isSaved()) {
          this._disassociateFromDependents();

          var collection = (0, _normalizeName.toInternalCollectionName)(this.modelName);
          this.schema.db[collection].remove(this.attrs.id);
        }
      }
    }, {
      key: 'isNew',
      value: function isNew() {
        var hasDbRecord = false;
        var hasId = this.attrs.id !== undefined && this.attrs.id !== null;

        if (hasId) {
          var collectionName = (0, _normalizeName.toInternalCollectionName)(this.modelName);
          var record = this.schema.db[collectionName].find(this.attrs.id);

          if (record) {
            hasDbRecord = true;
          }
        }

        return !hasDbRecord;
      }
    }, {
      key: 'isSaved',
      value: function isSaved() {
        return !this.isNew();
      }
    }, {
      key: 'reload',
      value: function reload() {
        if (this.id) {
          var collection = (0, _normalizeName.toInternalCollectionName)(this.modelName);
          var attrs = this.schema.db[collection].find(this.id);

          Object.keys(attrs).filter(function (attr) {
            return attr !== 'id';
          }).forEach(function (attr) {
            this.attrs[attr] = attrs[attr];
          }, this);
        }

        // Clear temp associations
        this._tempAssociations = {};

        return this;
      }
    }, {
      key: 'toJSON',
      value: function toJSON() {
        return this.attrs;
      }
    }, {
      key: 'associationFor',
      value: function associationFor(key) {
        return this.schema.associationsFor(this.modelName)[key];
      }
    }, {
      key: 'inverseFor',
      value: function inverseFor(association) {
        return this._explicitInverseFor(association) || this._implicitInverseFor(association);
      }
    }, {
      key: '_explicitInverseFor',
      value: function _explicitInverseFor(association) {
        this._checkForMultipleExplicitInverses(association);

        var associations = this.schema.associationsFor(this.modelName);
        var inverse = association.opts.inverse;
        var candidate = inverse ? associations[inverse] : null;
        var matchingPolymorphic = candidate && candidate.isPolymorphic;
        var matchingInverse = candidate && candidate.modelName === association.ownerModelName;
        var candidateInverse = candidate && candidate.opts.inverse;

        if (candidateInverse && candidate.opts.inverse !== association.key) {
          (0, _assert.default)(false, 'You specified an inverse of ' + inverse + ' for ' + association.key + ', but it does not match ' + candidate.modelName + ' ' + candidate.key + '\'s inverse');
        }

        return matchingPolymorphic || matchingInverse ? candidate : null;
      }
    }, {
      key: '_checkForMultipleExplicitInverses',
      value: function _checkForMultipleExplicitInverses(association) {
        var associations = this.schema.associationsFor(this.modelName);
        var matchingExplicitInverses = Object.keys(associations).filter(function (key) {
          var candidate = associations[key];
          var modelMatches = association.ownerModelName === candidate.modelName;
          var inverseKeyMatches = association.key === candidate.opts.inverse;

          return modelMatches && inverseKeyMatches;
        });
        (0, _assert.default)(matchingExplicitInverses.length <= 1, 'The ' + this.modelName + ' model has defined multiple explicit inverse associations for the ' + association.ownerModelName + '.' + association.key + ' association.');
      }
    }, {
      key: '_implicitInverseFor',
      value: function _implicitInverseFor(association) {
        var _this = this;

        var associations = this.schema.associationsFor(this.modelName);
        var modelName = association.ownerModelName;

        return (0, _values2.default)(associations).filter(function (candidate) {
          return candidate.modelName === modelName;
        }).reduce(function (inverse, candidate) {
          var candidateInverse = candidate.opts.inverse;
          var candidateIsImplicitInverse = candidateInverse === undefined;
          var candidateIsExplicitInverse = candidateInverse === association.key;

          var candidateMatches = candidateIsImplicitInverse || candidateIsExplicitInverse;

          if (candidateMatches) {
            // Need to move this check to compile-time init
            (0, _assert.default)(!inverse, 'The ' + _this.modelName + ' model has multiple possible inverse associations for the ' + association.ownerModelName + '.' + association.key + ' association.');
            inverse = candidate;
          }

          return inverse;
        }, null);
      }
    }, {
      key: 'hasInverseFor',
      value: function hasInverseFor(association) {
        return !!this.inverseFor(association);
      }
    }, {
      key: 'alreadyAssociatedWith',
      value: function alreadyAssociatedWith(model, association) {
        var key = association.key;

        var associatedModelOrCollection = this[key];

        if (associatedModelOrCollection && model) {
          if (associatedModelOrCollection instanceof Model) {
            if (associatedModelOrCollection.isSaved() && model.isSaved()) {
              return associatedModelOrCollection.toString() === model.toString();
            } else {
              return associatedModelOrCollection === model;
            }
          } else {
            return associatedModelOrCollection.includes(model);
          }
        }
      }
    }, {
      key: 'associate',
      value: function associate(model, association) {
        if (this.alreadyAssociatedWith(model, association)) {
          return;
        }

        var key = association.key;


        if (association instanceof _hasMany.default) {
          if (!this[key].includes(model)) {
            this[key].add(model);
          }
        } else {
          this[key] = model;
        }
      }
    }, {
      key: 'disassociate',
      value: function disassociate(model, association) {
        var fk = association.getForeignKey();

        if (association instanceof _hasMany.default) {
          var i = void 0;
          if (association.isPolymorphic) {
            var found = this[fk].find(function (_ref) {
              var type = _ref.type,
                  id = _ref.id;
              return type === model.modelName && id === model.id;
            });
            i = found && this[fk].indexOf(found);
          } else {
            i = this[fk].map(function (key) {
              return key.toString();
            }).indexOf(model.id.toString());
          }

          if (i > -1) {
            this.attrs[fk].splice(i, 1);
          }
        } else {
          this.attrs[fk] = null;
        }
      }
    }, {
      key: '_setupAttrs',
      value: function _setupAttrs(attrs) {
        var _this2 = this;

        // Verify no undefined associations are passed in
        Object.keys(attrs).filter(function (key) {
          var value = attrs[key];
          var isModelOrCollection = value instanceof Model || value instanceof _collection.default || value instanceof _polymorphicCollection.default;
          var isArrayOfModels = Array.isArray(value) && value.length && value.every(function (item) {
            return item instanceof Model;
          });

          return isModelOrCollection || isArrayOfModels;
        }).forEach(function (key) {
          var modelOrCollection = attrs[key];

          (0, _assert.default)(_this2.associationKeys.indexOf(key) > -1, 'You\'re trying to create a ' + _this2.modelName + ' model and you passed in a ' + modelOrCollection.toString() + ' under the ' + key + ' key, but you haven\'t defined that key as an association on your model.');
        });

        // Filter out association keys
        var hash = Object.keys(attrs).reduce(function (memo, key) {
          if (_this2.associationKeys.indexOf(key) === -1 && _this2.associationIdKeys.indexOf(key) === -1) {
            memo[key] = attrs[key];
          }
          return memo;
        }, {});

        // Ensure fks are there
        this.fks.map(function (fk) {
          hash[fk] = attrs[fk] !== undefined ? attrs[fk] : null;
        });

        this.attrs = hash;

        // define plain getter/setters for non-association keys
        Object.keys(hash).forEach(function (attr) {
          if (this.associationKeys.indexOf(attr) === -1 && this.associationIdKeys.indexOf(attr) === -1) {
            this._definePlainAttribute(attr);
          }
        }, this);
      }
    }, {
      key: '_definePlainAttribute',
      value: function _definePlainAttribute(attr) {

        // Ensure the property hasn't already been defined
        var existingProperty = Object.getOwnPropertyDescriptor(this, attr);
        if (existingProperty && existingProperty.get) {
          return;
        }

        // Ensure the attribute is on the attrs hash
        if (!this.attrs.hasOwnProperty(attr)) {
          this.attrs[attr] = null;
        }

        // Define the getter/setter
        Object.defineProperty(this, attr, {
          get: function get() {
            return this.attrs[attr];
          },
          set: function set(val) {
            this.attrs[attr] = val;
            return this;
          }
        });
      }
    }, {
      key: '_setupRelationships',
      value: function _setupRelationships(attrs) {
        var _this3 = this;

        var foreignKeysHash = Object.keys(attrs).reduce(function (memo, attr) {
          if (_this3.associationIdKeys.indexOf(attr) > -1 || _this3.fks.indexOf(attr) > -1) {
            memo[attr] = attrs[attr];
          }
          return memo;
        }, {});

        Object.keys(foreignKeysHash).forEach(function (attr) {
          var fk = foreignKeysHash[attr];
          if (fk !== undefined && fk !== null) {
            this._validateForeignKeyExistsInDatabase(attr, fk);
          }

          this.attrs[attr] = fk;
        }, this);

        var associationKeysHash = Object.keys(attrs).reduce(function (memo, attr) {
          if (_this3.associationKeys.indexOf(attr) > -1) {
            memo[attr] = attrs[attr];
          }
          return memo;
        }, {});
        Object.keys(associationKeysHash).forEach(function (attr) {
          this[attr] = associationKeysHash[attr];
        }, this);
      }
    }, {
      key: '_validateForeignKeyExistsInDatabase',
      value: function _validateForeignKeyExistsInDatabase(foreignKeyName, foreignKeys) {
        var _this4 = this;

        if (Array.isArray(foreignKeys)) {
          var association = Object.keys(this.hasManyAssociations).map(function (key) {
            return _this4.hasManyAssociations[key];
          }).filter(function (association) {
            return association.getForeignKey() === foreignKeyName;
          })[0];

          var found = void 0;
          if (association.isPolymorphic) {
            found = foreignKeys.map(function (_ref2) {
              var type = _ref2.type,
                  id = _ref2.id;

              return _this4.schema.db[(0, _normalizeName.toInternalCollectionName)(type)].find(id);
            });
            found = (0, _compact2.default)(found);
          } else {
            found = this.schema.db[(0, _normalizeName.toInternalCollectionName)(association.modelName)].find(foreignKeys);
          }

          var foreignKeyLabel = association.isPolymorphic ? foreignKeys.map(function (fk) {
            return fk.type + ':' + fk.id;
          }).join(',') : foreignKeys;
          (0, _assert.default)(found.length === foreignKeys.length, 'You\'re instantiating a ' + this.modelName + ' that has a ' + foreignKeyName + ' of ' + foreignKeyLabel + ', but some of those records don\'t exist in the database.');
        } else {
          var _association = Object.keys(this.belongsToAssociations).map(function (key) {
            return _this4.belongsToAssociations[key];
          }).filter(function (association) {
            return association.getForeignKey() === foreignKeyName;
          })[0];

          var _found = void 0;
          if (_association.isPolymorphic) {
            _found = this.schema.db[(0, _normalizeName.toInternalCollectionName)(foreignKeys.type)].find(foreignKeys.id);
          } else {
            _found = this.schema.db[(0, _normalizeName.toInternalCollectionName)(_association.modelName)].find(foreignKeys);
          }

          var _foreignKeyLabel = _association.isPolymorphic ? foreignKeys.type + ':' + foreignKeys.id : foreignKeys;
          (0, _assert.default)(_found, 'You\'re instantiating a ' + this.modelName + ' that has a ' + foreignKeyName + ' of ' + _foreignKeyLabel + ', but that record doesn\'t exist in the database.');
        }
      }
    }, {
      key: '_saveAssociations',
      value: function _saveAssociations() {
        this._saveBelongsToAssociations();
        this._saveHasManyAssociations();
      }
    }, {
      key: '_saveBelongsToAssociations',
      value: function _saveBelongsToAssociations() {
        var _this5 = this;

        (0, _values2.default)(this.belongsToAssociations).forEach(function (association) {
          _this5._disassociateFromOldInverses(association);
          _this5._saveNewAssociates(association);
          _this5._associateWithNewInverses(association);
        });
      }
    }, {
      key: '_saveHasManyAssociations',
      value: function _saveHasManyAssociations() {
        var _this6 = this;

        (0, _values2.default)(this.hasManyAssociations).forEach(function (association) {
          _this6._disassociateFromOldInverses(association);
          _this6._saveNewAssociates(association);
          _this6._associateWithNewInverses(association);
        });
      }
    }, {
      key: '_disassociateFromOldInverses',
      value: function _disassociateFromOldInverses(association) {
        if (association instanceof _hasMany.default) {
          this._disassociateFromHasManyInverses(association);
        } else if (association instanceof _belongsTo.default) {
          this._disassociateFromBelongsToInverse(association);
        }
      }
    }, {
      key: '_disassociateFromHasManyInverses',
      value: function _disassociateFromHasManyInverses(association) {
        var _this7 = this;

        var key = association.key;

        var fk = association.getForeignKey();
        var tempAssociation = this._tempAssociations && this._tempAssociations[key];
        var associateIds = this.attrs[fk];

        if (tempAssociation && associateIds) {
          var models = void 0;
          if (association.isPolymorphic) {
            models = associateIds.map(function (_ref3) {
              var type = _ref3.type,
                  id = _ref3.id;

              return _this7.schema[(0, _normalizeName.toCollectionName)(type)].find(id);
            });
          } else {
            // TODO: prob should initialize hasMany fks with []
            models = this.schema[(0, _normalizeName.toCollectionName)(association.modelName)].find(associateIds || []).models;
          }

          models.filter(function (associate) {
            return !associate.isSaving;
          }) // filter out models that are already being saved
          .filter(function (associate) {
            return !tempAssociation.includes(associate);
          }) // filter out models that will still be associated
          .forEach(function (associate) {
            if (associate.hasInverseFor(association)) {
              var inverse = associate.inverseFor(association);

              associate.disassociate(_this7, inverse);
              associate.save();
            }
          });
        }
      }
    }, {
      key: '_disassociateFromBelongsToInverse',
      value: function _disassociateFromBelongsToInverse(association) {
        var key = association.key;

        var fk = association.getForeignKey();
        var tempAssociation = this._tempAssociations && this._tempAssociations[key];
        var associateId = this.attrs[fk];

        if (tempAssociation !== undefined && associateId) {
          var associate = void 0;
          if (association.isPolymorphic) {
            associate = this.schema[(0, _normalizeName.toCollectionName)(associateId.type)].find(associateId.id);
          } else {
            associate = this.schema[(0, _normalizeName.toCollectionName)(association.modelName)].find(associateId);
          }

          if (associate.hasInverseFor(association)) {
            var inverse = associate.inverseFor(association);

            associate.disassociate(this, inverse);
            associate._updateInDb(associate.attrs);
          }
        }
      }
    }, {
      key: '_disassociateFromDependents',
      value: function _disassociateFromDependents() {
        var _this8 = this;

        this.schema.dependentAssociationsFor(this.modelName).forEach(function (association) {
          association.disassociateAllDependentsFromTarget(_this8);
        });
      }
    }, {
      key: '_saveNewAssociates',
      value: function _saveNewAssociates(association) {
        var key = association.key;

        var fk = association.getForeignKey();
        var tempAssociate = this._tempAssociations && this._tempAssociations[key];

        if (tempAssociate !== undefined) {
          this.__isSavingNewChildren = true;
          delete this._tempAssociations[key];

          if (tempAssociate instanceof _collection.default) {
            tempAssociate.models.filter(function (model) {
              return !model.isSaving;
            }).forEach(function (child) {
              child.save();
            });

            this._updateInDb(_defineProperty({}, fk, tempAssociate.models.map(function (child) {
              return child.id;
            })));
          } else if (tempAssociate instanceof _polymorphicCollection.default) {
            tempAssociate.models.filter(function (model) {
              return !model.isSaving;
            }).forEach(function (child) {
              child.save();
            });

            this._updateInDb(_defineProperty({}, fk, tempAssociate.models.map(function (child) {
              return { type: child.modelName, id: child.id };
            })));
          } else {

            if (tempAssociate === null) {
              this._updateInDb(_defineProperty({}, fk, null));
            } else if (!tempAssociate.isSaving) {
              tempAssociate.save();

              var fkValue = void 0;
              if (association.isPolymorphic) {
                fkValue = { id: tempAssociate.id, type: tempAssociate.modelName };
              } else {
                fkValue = tempAssociate.id;
              }

              this._updateInDb(_defineProperty({}, fk, fkValue));
            }
          }

          this.__isSavingNewChildren = false;
        }
      }
    }, {
      key: '_associateWithNewInverses',
      value: function _associateWithNewInverses(association) {
        var _this9 = this;

        if (!this.__isSavingNewChildren) {
          var modelOrCollection = this[association.key];

          if (modelOrCollection instanceof Model) {
            this._associateModelWithInverse(modelOrCollection, association);
          } else if (modelOrCollection instanceof _collection.default || modelOrCollection instanceof _polymorphicCollection.default) {
            modelOrCollection.models.forEach(function (model) {
              _this9._associateModelWithInverse(model, association);
            });
          }

          delete this._tempAssociations[association.key];
        }
      }
    }, {
      key: '_associateModelWithInverse',
      value: function _associateModelWithInverse(model, association) {
        var _this10 = this;

        if (model.hasInverseFor(association)) {
          var inverse = model.inverseFor(association);
          var inverseFk = inverse.getForeignKey();

          if (inverse instanceof _belongsTo.default) {
            this.schema.db[(0, _normalizeName.toInternalCollectionName)(model.modelName)].update(model.id, _defineProperty({}, inverseFk, this.id));
          } else {
            var ownerId = this.id;
            var inverseCollection = this.schema.db[(0, _normalizeName.toInternalCollectionName)(model.modelName)];
            var currentIdsForInverse = inverseCollection.find(model.id)[inverse.getForeignKey()] || [];
            var newIdsForInverse = (0, _assign2.default)([], currentIdsForInverse);
            var newId = void 0,
                alreadyAssociatedWith = void 0;

            if (inverse.isPolymorphic) {
              newId = { type: this.modelName, id: ownerId };
              alreadyAssociatedWith = newIdsForInverse.some(function (key) {
                return key.type == _this10.modelName && key.id == ownerId;
              });
            } else {
              newId = ownerId;
              alreadyAssociatedWith = newIdsForInverse.indexOf(ownerId) !== -1;
            }

            if (!alreadyAssociatedWith) {
              newIdsForInverse.push(newId);
            }

            inverseCollection.update(model.id, _defineProperty({}, inverseFk, newIdsForInverse));
          }
        }
      }
    }, {
      key: '_updateInDb',
      value: function _updateInDb(attrs) {
        this.attrs = this.schema.db[(0, _normalizeName.toInternalCollectionName)(this.modelName)].update(this.attrs.id, attrs);
      }
    }, {
      key: 'toString',
      value: function toString() {
        var idLabel = this.id ? '(' + this.id + ')' : '';

        return 'model:' + this.modelName + idLabel;
      }
    }, {
      key: 'isSaving',
      get: function get() {
        return this.schema.isSaving[this.toString()];
      }
    }]);

    return Model;
  }();

  Model.extend = _extend.default;
  Model.findBelongsToAssociation = function (associationType) {
    return this.prototype.belongsToAssociations[associationType];
  };

  exports.default = Model;
});