text: /* eslint no-func-assign: 1 */
  
  /*!
   * Module dependencies.
   */
  
  'use strict';
  
  import { Document } from '../document_provider.js';
  import { EventEmitter } from 'https://deno.land/std@0.95.0/node/events.ts';
  import { ValidationError } from '../error/validation.js';
  import { immediate } from '../helpers/immediate.js';
  import { internalToObjectOptions } from '../options.js';
  import { get } from '../helpers/get.js';
  import { promiseOrCallback } from '../helpers/promiseOrCallback.js';
  import * as util from "https://deno.land/std@0.95.0/node/util.ts";
  
  import { documentArrayParent } from '../helpers/symbols.js';
  import { validatorErrorSymbol } from '../helpers/symbols.js';
  
  /**
   * EmbeddedDocument constructor.
   *
   * @param {Object} obj js object returned from the db
   * @param {MongooseDocumentArray} parentArr the parent array of this document
   * @param {Boolean} skipId
   * @inherits Document
   * @api private
   */
  
  function EmbeddedDocument(obj, parentArr, skipId, fields, index) {
    const options = {};
    if (parentArr != null && parentArr.isMongooseDocumentArray) {
      this.__parentArray = parentArr;
      this[documentArrayParent] = parentArr.$parent();
    } else {
      this.__parentArray = undefined;
      this[documentArrayParent] = undefined;
    }
    this.$setIndex(index);
    this.$isDocumentArrayElement = true;
  
    if (this[documentArrayParent] != null) {
      options.defaults = this[documentArrayParent].$__.$options.defaults;
    }
  
    Document.call(this, obj, fields, skipId, options);
  
    const _this = this;
    this.on('isNew', function(val) {
      _this.isNew = val;
    });
  
    _this.on('save', function() {
      _this.constructor.emit('save', _this);
    });
  }
  
  /*!
   * Inherit from Document
   */
  EmbeddedDocument.prototype = Object.create(Document.prototype);
  EmbeddedDocument.prototype.constructor = EmbeddedDocument;
  
  for (const i in EventEmitter.prototype) {
    EmbeddedDocument[i] = EventEmitter.prototype[i];
  }
  
  EmbeddedDocument.prototype.toBSON = function() {
    return this.toObject(internalToObjectOptions);
  };
  
  /*!
   * ignore
   */
  
  EmbeddedDocument.prototype.$setIndex = function(index) {
    this.__index = index;
  
    if (get(this, '$__.validationError', null) != null) {
      const keys = Object.keys(this.$__.validationError.errors);
      for (const key of keys) {
        this.invalidate(key, this.$__.validationError.errors[key]);
      }
    }
  };
  
  /**
   * Marks the embedded doc modified.
   *
   * ####Example:
   *
   *     const doc = blogpost.comments.id(hexstring);
   *     doc.mixed.type = 'changed';
   *     doc.markModified('mixed.type');
   *
   * @param {String} path the path which changed
   * @api public
   * @receiver EmbeddedDocument
   */
  
  EmbeddedDocument.prototype.markModified = function(path) {
    this.$__.activePaths.modify(path);
    if (!this.__parentArray) {
      return;
    }
  
    const pathToCheck = this.__parentArray.$path() + '.0.' + path;
    if (this.isNew && this.ownerDocument().$__isSelected(pathToCheck)) {
      // Mark the WHOLE parent array as modified
      // if this is a new document (i.e., we are initializing
      // a document),
      this.__parentArray._markModified();
    } else {
      this.__parentArray._markModified(this, path);
    }
  };
  
  /*!
   * ignore
   */
  
  EmbeddedDocument.prototype.populate = function() {
    throw new Error('Mongoose does not support calling populate() on nested ' +
      'docs. Instead of `doc.arr[0].populate("path")`, use ' +
      '`doc.populate("arr.0.path")`');
  };
  
  /**
   * Used as a stub for [hooks.js](https://github.com/bnoguchi/hooks-js/tree/31ec571cef0332e21121ee7157e0cf9728572cc3)
   *
   * ####NOTE:
   *
   * _This is a no-op. Does not actually save the doc to the db._
   *
   * @param {Function} [fn]
   * @return {Promise} resolved Promise
   * @api private
   */
  
  EmbeddedDocument.prototype.save = function(options, fn) {
    if (typeof options === 'function') {
      fn = options;
      options = {};
    }
    options = options || {};
  
    if (!options.suppressWarning) {
      console.warn('mongoose: calling `save()` on a subdoc does **not** save ' +
        'the document to MongoDB, it only runs save middleware. ' +
        'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' +
        'if you\'re sure this behavior is right for your app.');
    }
  
    return promiseOrCallback(fn, cb => {
      this.$__save(cb);
    });
  };
  
  /**
   * Used as a stub for middleware
   *
   * ####NOTE:
   *
   * _This is a no-op. Does not actually save the doc to the db._
   *
   * @param {Function} [fn]
   * @method $__save
   * @api private
   */
  
  EmbeddedDocument.prototype.$__save = function(fn) {
    return immediate(() => fn(null, this));
  };
  
  /*!
   * Registers remove event listeners for triggering
   * on subdocuments.
   *
   * @param {EmbeddedDocument} sub
   * @api private
   */
  
  function registerRemoveListener(sub) {
    let owner = sub.ownerDocument();
  
    function emitRemove() {
      owner.removeListener('save', emitRemove);
      owner.removeListener('remove', emitRemove);
      sub.emit('remove', sub);
      sub.constructor.emit('remove', sub);
      owner = sub = null;
    }
  
    owner.on('save', emitRemove);
    owner.on('remove', emitRemove);
  }
  
  /*!
   * no-op for hooks
   */
  
  EmbeddedDocument.prototype.$__remove = function(cb) {
    if (cb == null) {
      return;
    }
    return cb(null, this);
  };
  
  /**
   * Removes the subdocument from its parent array.
   *
   * @param {Object} [options]
   * @param {Function} [fn]
   * @api public
   */
  
  EmbeddedDocument.prototype.remove = function(options, fn) {
    if (typeof options === 'function' && !fn) {
      fn = options;
      options = undefined;
    }
    if (!this.__parentArray || (options && options.noop)) {
      this.$__remove(fn);
      return this;
    }
  
    let _id;
    if (!this.willRemove) {
      _id = this._doc._id;
      if (!_id) {
        throw new Error('For your own good, Mongoose does not know ' +
            'how to remove an EmbeddedDocument that has no _id');
      }
      this.__parentArray.pull({ _id: _id });
      this.willRemove = true;
      registerRemoveListener(this);
    }
  
    this.$__remove(fn);
  
    return this;
  };
  
  /**
   * Override #update method of parent documents.
   * @api private
   */
  
  EmbeddedDocument.prototype.update = function() {
    throw new Error('The #update method is not available on EmbeddedDocuments');
  };
  
  /**
   * Helper for console.log
   *
   * @api public
   */
  
  EmbeddedDocument.prototype.inspect = function() {
    return this.toObject({
      transform: false,
      virtuals: false,
      flattenDecimals: false
    });
  };
  
  if (util.inspect.custom) {
    /*!
    * Avoid Node deprecation warning DEP0079
    */
  
    EmbeddedDocument.prototype[util.inspect.custom] = EmbeddedDocument.prototype.inspect;
  }
  
  /**
   * Marks a path as invalid, causing validation to fail.
   *
   * @param {String} path the field to invalidate
   * @param {String|Error} err error which states the reason `path` was invalid
   * @return {Boolean}
   * @api public
   */
  
  EmbeddedDocument.prototype.invalidate = function(path, err, val) {
    Document.prototype.invalidate.call(this, path, err, val);
  
    if (!this[documentArrayParent] || this.__index == null) {
      if (err[validatorErrorSymbol] || err instanceof ValidationError) {
        return this.ownerDocument().$__.validationError;
      }
      throw err;
    }
  
    const index = this.__index;
    const parentPath = this.__parentArray.$path();
    const fullPath = [parentPath, index, path].join('.');
    this[documentArrayParent].invalidate(fullPath, err, val);
  
    return this.ownerDocument().$__.validationError;
  };
  
  /**
   * Marks a path as valid, removing existing validation errors.
   *
   * @param {String} path the field to mark as valid
   * @api private
   * @method $markValid
   * @receiver EmbeddedDocument
   */
  
  EmbeddedDocument.prototype.$markValid = function(path) {
    if (!this[documentArrayParent]) {
      return;
    }
  
    const index = this.__index;
    if (typeof index !== 'undefined') {
      const parentPath = this.__parentArray.$path();
      const fullPath = [parentPath, index, path].join('.');
      this[documentArrayParent].$markValid(fullPath);
    }
  };
  
  /*!
   * ignore
   */
  
  EmbeddedDocument.prototype.$ignore = function(path) {
    Document.prototype.$ignore.call(this, path);
  
    if (!this[documentArrayParent]) {
      return;
    }
  
    const index = this.__index;
    if (typeof index !== 'undefined') {
      const parentPath = this.__parentArray.$path();
      const fullPath = [parentPath, index, path].join('.');
      this[documentArrayParent].$ignore(fullPath);
    }
  };
  
  /**
   * Checks if a path is invalid
   *
   * @param {String} path the field to check
   * @api private
   * @method $isValid
   * @receiver EmbeddedDocument
   */
  
  EmbeddedDocument.prototype.$isValid = function(path) {
    const index = this.__index;
    if (typeof index !== 'undefined' && this[documentArrayParent]) {
      return !this[documentArrayParent].$__.validationError ||
        !this[documentArrayParent].$__.validationError.errors[this.$__fullPath(path)];
    }
  
    return true;
  };
  
  /**
   * Returns the top level document of this sub-document.
   *
   * @return {Document}
   */
  
  EmbeddedDocument.prototype.ownerDocument = function() {
    if (this.$__.ownerDocument) {
      return this.$__.ownerDocument;
    }
  
    let parent = this[documentArrayParent];
    if (!parent) {
      return this;
    }
  
    while (parent[documentArrayParent] || parent.$__parent) {
      parent = parent[documentArrayParent] || parent.$__parent;
    }
  
    this.$__.ownerDocument = parent;
    return this.$__.ownerDocument;
  };
  
  /**
   * Returns the full path to this document. If optional `path` is passed, it is appended to the full path.
   *
   * @param {String} [path]
   * @return {String}
   * @api private
   * @method $__fullPath
   * @memberOf EmbeddedDocument
   * @instance
   */
  
  EmbeddedDocument.prototype.$__fullPath = function(path) {
    if (!this.$__.fullPath) {
      let parent = this; // eslint-disable-line consistent-this
      if (!parent[documentArrayParent]) {
        return path;
      }
  
      const paths = [];
      while (parent[documentArrayParent] || parent.$__parent) {
        if (parent[documentArrayParent]) {
          paths.unshift(parent.__parentArray.$path());
        } else {
          paths.unshift(parent.$basePath);
        }
        parent = parent[documentArrayParent] || parent.$__parent;
      }
  
      this.$__.fullPath = paths.join('.');
  
      if (!this.$__.ownerDocument) {
        // optimization
        this.$__.ownerDocument = parent;
      }
    }
  
    return path
      ? this.$__.fullPath + '.' + path
      : this.$__.fullPath;
  };
  
  /**
   * Returns this sub-documents parent document.
   *
   * @api public
   */
  
  EmbeddedDocument.prototype.parent = function() {
    return this[documentArrayParent];
  };
  
  /**
   * Returns this sub-documents parent document.
   *
   * @api public
   */
  
  EmbeddedDocument.prototype.$parent = EmbeddedDocument.prototype.parent;
  
  /**
   * Returns this sub-documents parent array.
   *
   * @api public
   */
  
  EmbeddedDocument.prototype.parentArray = function() {
    return this.__parentArray;
  };
  
  /*!
   * Module exports.
   */
  
  export { EmbeddedDocument }
  