text: 'use strict';
  
  const Types = require('./types');
  
  
  const internals = {
      mismatched: null
  };
  
  
  module.exports = function (obj, ref, options) {
  
      options = Object.assign({ prototype: true }, options);
  
      return !!internals.isDeepEqual(obj, ref, options, []);
  };
  
  
  internals.isDeepEqual = function (obj, ref, options, seen) {
  
      if (obj === ref) {                                                      // Copied from Deep-eql, copyright(c) 2013 Jake Luer, jake@alogicalparadox.com, MIT Licensed, https://github.com/chaijs/deep-eql
          return obj !== 0 || 1 / obj === 1 / ref;
      }
  
      const type = typeof obj;
  
      if (type !== typeof ref) {
          return false;
      }
  
      if (obj === null ||
          ref === null) {
  
          return false;
      }
  
      if (type === 'function') {
          if (!options.deepFunction ||
              obj.toString() !== ref.toString()) {
  
              return false;
          }
  
          // Continue as object
      }
      else if (type !== 'object') {
          return obj !== obj && ref !== ref;                                  // NaN
      }
  
      const instanceType = internals.getSharedType(obj, ref, !!options.prototype);
      switch (instanceType) {
          case Types.buffer:
              return Buffer && Buffer.prototype.equals.call(obj, ref);        // $lab:coverage:ignore$
          case Types.promise:
              return obj === ref;
          case Types.regex:
              return obj.toString() === ref.toString();
          case internals.mismatched:
              return false;
      }
  
      for (let i = seen.length - 1; i >= 0; --i) {
          if (seen[i].isSame(obj, ref)) {
              return true;                                                    // If previous comparison failed, it would have stopped execution
          }
      }
  
      seen.push(new internals.SeenEntry(obj, ref));
  
      try {
          return !!internals.isDeepEqualObj(instanceType, obj, ref, options, seen);
      }
      finally {
          seen.pop();
      }
  };
  
  
  internals.getSharedType = function (obj, ref, checkPrototype) {
  
      if (checkPrototype) {
          if (Object.getPrototypeOf(obj) !== Object.getPrototypeOf(ref)) {
              return internals.mismatched;
          }
  
          return Types.getInternalProto(obj);
      }
  
      const type = Types.getInternalProto(obj);
      if (type !== Types.getInternalProto(ref)) {
          return internals.mismatched;
      }
  
      return type;
  };
  
  
  internals.valueOf = function (obj) {
  
      const objValueOf = obj.valueOf;
      if (objValueOf === undefined) {
          return obj;
      }
  
      try {
          return objValueOf.call(obj);
      }
      catch (err) {
          return err;
      }
  };
  
  
  internals.hasOwnEnumerableProperty = function (obj, key) {
  
      return Object.prototype.propertyIsEnumerable.call(obj, key);
  };
  
  
  internals.isSetSimpleEqual = function (obj, ref) {
  
      for (const entry of obj) {
          if (!ref.has(entry)) {
              return false;
          }
      }
  
      return true;
  };
  
  
  internals.isDeepEqualObj = function (instanceType, obj, ref, options, seen) {
  
      const { isDeepEqual, valueOf, hasOwnEnumerableProperty } = internals;
      const { keys, getOwnPropertySymbols } = Object;
  
      if (instanceType === Types.array) {
          if (options.part) {
  
              // Check if any index match any other index
  
              for (const objValue of obj) {
                  for (const refValue of ref) {
                      if (isDeepEqual(objValue, refValue, options, seen)) {
                          return true;
                      }
                  }
              }
          }
          else {
              if (obj.length !== ref.length) {
                  return false;
              }
  
              for (let i = 0; i < obj.length; ++i) {
                  if (!isDeepEqual(obj[i], ref[i], options, seen)) {
                      return false;
                  }
              }
  
              return true;
          }
      }
      else if (instanceType === Types.set) {
          if (obj.size !== ref.size) {
              return false;
          }
  
          if (!internals.isSetSimpleEqual(obj, ref)) {
  
              // Check for deep equality
  
              const ref2 = new Set(ref);
              for (const objEntry of obj) {
                  if (ref2.delete(objEntry)) {
                      continue;
                  }
  
                  let found = false;
                  for (const refEntry of ref2) {
                      if (isDeepEqual(objEntry, refEntry, options, seen)) {
                          ref2.delete(refEntry);
                          found = true;
                          break;
                      }
                  }
  
                  if (!found) {
                      return false;
                  }
              }
          }
      }
      else if (instanceType === Types.map) {
          if (obj.size !== ref.size) {
              return false;
          }
  
          for (const [key, value] of obj) {
              if (value === undefined && !ref.has(key)) {
                  return false;
              }
  
              if (!isDeepEqual(value, ref.get(key), options, seen)) {
                  return false;
              }
          }
      }
      else if (instanceType === Types.error) {
  
          // Always check name and message
  
          if (obj.name !== ref.name ||
              obj.message !== ref.message) {
  
              return false;
          }
      }
  
      // Check .valueOf()
  
      const valueOfObj = valueOf(obj);
      const valueOfRef = valueOf(ref);
      if ((obj !== valueOfObj || ref !== valueOfRef) &&
          !isDeepEqual(valueOfObj, valueOfRef, options, seen)) {
  
          return false;
      }
  
      // Check properties
  
      const objKeys = keys(obj);
      if (!options.part &&
          objKeys.length !== keys(ref).length &&
          !options.skip) {
  
          return false;
      }
  
      for (const key of objKeys) {
          if (options.skip &&
              options.skip.includes(key)) {
  
              continue;
          }
  
          if (!hasOwnEnumerableProperty(ref, key)) {
              return false;
          }
  
          if (!isDeepEqual(obj[key], ref[key], options, seen)) {
              return false;
          }
      }
  
      // Check symbols
  
      if (options.symbols !== false) {                                // Defaults to true
          const objSymbols = getOwnPropertySymbols(obj);
          const refSymbols = new Set(getOwnPropertySymbols(ref));
  
          for (const key of objSymbols) {
              if (!options.skip ||
                  !options.skip.includes(key)) {
  
                  if (hasOwnEnumerableProperty(obj, key)) {
                      if (!hasOwnEnumerableProperty(ref, key)) {
                          return false;
                      }
  
                      if (!isDeepEqual(obj[key], ref[key], options, seen)) {
                          return false;
                      }
                  }
                  else if (hasOwnEnumerableProperty(ref, key)) {
                      return false;
                  }
              }
  
              refSymbols.delete(key);
          }
  
          for (const key of refSymbols) {
              if (hasOwnEnumerableProperty(ref, key)) {
                  return false;
              }
          }
      }
  
      return true;
  };
  
  
  internals.SeenEntry = class {
  
      constructor(obj, ref) {
  
          this.obj = obj;
          this.ref = ref;
      }
  
      isSame(obj, ref) {
  
          return this.obj === obj && this.ref === ref;
      }
  };
  