text: "use strict";
  
  var valueToString = require("@sinonjs/commons").valueToString;
  var className = require("@sinonjs/commons").className;
  var typeOf = require("@sinonjs/commons").typeOf;
  var arrayProto = require("@sinonjs/commons").prototypes.array;
  var objectProto = require("@sinonjs/commons").prototypes.object;
  var mapForEach = require("@sinonjs/commons").prototypes.map.forEach;
  
  var getClass = require("./get-class");
  var identical = require("./identical");
  var isArguments = require("./is-arguments");
  var isDate = require("./is-date");
  var isElement = require("./is-element");
  var isMap = require("./is-map");
  var isNaN = require("./is-nan");
  var isObject = require("./is-object");
  var isSet = require("./is-set");
  var isSubset = require("./is-subset");
  
  var concat = arrayProto.concat;
  var every = arrayProto.every;
  var push = arrayProto.push;
  
  var getTime = Date.prototype.getTime;
  var hasOwnProperty = objectProto.hasOwnProperty;
  var indexOf = arrayProto.indexOf;
  var keys = Object.keys;
  var getOwnPropertySymbols = Object.getOwnPropertySymbols;
  
  /**
   * Deep equal comparison. Two values are "deep equal" when:
   *
   *   - They are equal, according to samsam.identical
   *   - They are both date objects representing the same time
   *   - They are both arrays containing elements that are all deepEqual
   *   - They are objects with the same set of properties, and each property
   *     in ``actual`` is deepEqual to the corresponding property in ``expectation``
   *
   * Supports cyclic objects.
   *
   * @alias module:samsam.deepEqual
   * @param {*} actual The object to examine
   * @param {*} expectation The object actual is expected to be equal to
   * @param {object} match A value to match on
   * @returns {boolean} Returns true when actual and expectation are considered equal
   */
  function deepEqualCyclic(actual, expectation, match) {
      // used for cyclic comparison
      // contain already visited objects
      var actualObjects = [];
      var expectationObjects = [];
      // contain pathes (position in the object structure)
      // of the already visited objects
      // indexes same as in objects arrays
      var actualPaths = [];
      var expectationPaths = [];
      // contains combinations of already compared objects
      // in the manner: { "$1['ref']$2['ref']": true }
      var compared = {};
  
      // does the recursion for the deep equal check
      // eslint-disable-next-line complexity
      return (function deepEqual(
          actualObj,
          expectationObj,
          actualPath,
          expectationPath
      ) {
          // If both are matchers they must be the same instance in order to be
          // considered equal If we didn't do that we would end up running one
          // matcher against the other
          if (match && match.isMatcher(expectationObj)) {
              if (match.isMatcher(actualObj)) {
                  return actualObj === expectationObj;
              }
              return expectationObj.test(actualObj);
          }
  
          var actualType = typeof actualObj;
          var expectationType = typeof expectationObj;
  
          if (
              actualObj === expectationObj ||
              isNaN(actualObj) ||
              isNaN(expectationObj) ||
              actualObj === null ||
              expectationObj === null ||
              actualObj === undefined ||
              expectationObj === undefined ||
              actualType !== "object" ||
              expectationType !== "object"
          ) {
              return identical(actualObj, expectationObj);
          }
  
          // Elements are only equal if identical(expected, actual)
          if (isElement(actualObj) || isElement(expectationObj)) {
              return false;
          }
  
          var isActualDate = isDate(actualObj);
          var isExpectationDate = isDate(expectationObj);
          if (isActualDate || isExpectationDate) {
              if (
                  !isActualDate ||
                  !isExpectationDate ||
                  getTime.call(actualObj) !== getTime.call(expectationObj)
              ) {
                  return false;
              }
          }
  
          if (actualObj instanceof RegExp && expectationObj instanceof RegExp) {
              if (valueToString(actualObj) !== valueToString(expectationObj)) {
                  return false;
              }
          }
  
          if (actualObj instanceof Error && expectationObj instanceof Error) {
              return actualObj === expectationObj;
          }
  
          var actualClass = getClass(actualObj);
          var expectationClass = getClass(expectationObj);
          var actualKeys = keys(actualObj);
          var expectationKeys = keys(expectationObj);
          var actualName = className(actualObj);
          var expectationName = className(expectationObj);
          var expectationSymbols =
              typeOf(getOwnPropertySymbols) === "function"
                  ? getOwnPropertySymbols(expectationObj)
                  : /* istanbul ignore next: cannot collect coverage for engine that doesn't support Symbol */
                    [];
          var expectationKeysAndSymbols = concat(
              expectationKeys,
              expectationSymbols
          );
  
          if (isArguments(actualObj) || isArguments(expectationObj)) {
              if (actualObj.length !== expectationObj.length) {
                  return false;
              }
          } else {
              if (
                  actualType !== expectationType ||
                  actualClass !== expectationClass ||
                  actualKeys.length !== expectationKeys.length ||
                  (actualName &&
                      expectationName &&
                      actualName !== expectationName)
              ) {
                  return false;
              }
          }
  
          if (isSet(actualObj) || isSet(expectationObj)) {
              if (
                  !isSet(actualObj) ||
                  !isSet(expectationObj) ||
                  actualObj.size !== expectationObj.size
              ) {
                  return false;
              }
  
              return isSubset(actualObj, expectationObj, deepEqual);
          }
  
          if (isMap(actualObj) || isMap(expectationObj)) {
              if (
                  !isMap(actualObj) ||
                  !isMap(expectationObj) ||
                  actualObj.size !== expectationObj.size
              ) {
                  return false;
              }
  
              var mapsDeeplyEqual = true;
              mapForEach(actualObj, function(value, key) {
                  mapsDeeplyEqual =
                      mapsDeeplyEqual &&
                      deepEqualCyclic(value, expectationObj.get(key));
              });
  
              return mapsDeeplyEqual;
          }
  
          return every(expectationKeysAndSymbols, function(key) {
              if (!hasOwnProperty(actualObj, key)) {
                  return false;
              }
  
              var actualValue = actualObj[key];
              var expectationValue = expectationObj[key];
              var actualObject = isObject(actualValue);
              var expectationObject = isObject(expectationValue);
              // determines, if the objects were already visited
              // (it's faster to check for isObject first, than to
              // get -1 from getIndex for non objects)
              var actualIndex = actualObject
                  ? indexOf(actualObjects, actualValue)
                  : -1;
              var expectationIndex = expectationObject
                  ? indexOf(expectationObjects, expectationValue)
                  : -1;
              // determines the new paths of the objects
              // - for non cyclic objects the current path will be extended
              //   by current property name
              // - for cyclic objects the stored path is taken
              var newActualPath =
                  actualIndex !== -1
                      ? actualPaths[actualIndex]
                      : actualPath + "[" + JSON.stringify(key) + "]";
              var newExpectationPath =
                  expectationIndex !== -1
                      ? expectationPaths[expectationIndex]
                      : expectationPath + "[" + JSON.stringify(key) + "]";
              var combinedPath = newActualPath + newExpectationPath;
  
              // stop recursion if current objects are already compared
              if (compared[combinedPath]) {
                  return true;
              }
  
              // remember the current objects and their paths
              if (actualIndex === -1 && actualObject) {
                  push(actualObjects, actualValue);
                  push(actualPaths, newActualPath);
              }
              if (expectationIndex === -1 && expectationObject) {
                  push(expectationObjects, expectationValue);
                  push(expectationPaths, newExpectationPath);
              }
  
              // remember that the current objects are already compared
              if (actualObject && expectationObject) {
                  compared[combinedPath] = true;
              }
  
              // End of cyclic logic
  
              // neither actualValue nor expectationValue is a cycle
              // continue with next level
              return deepEqual(
                  actualValue,
                  expectationValue,
                  newActualPath,
                  newExpectationPath
              );
          });
      })(actual, expectation, "$1", "$2");
  }
  
  deepEqualCyclic.use = function(match) {
      return function deepEqual(a, b) {
          return deepEqualCyclic(a, b, match);
      };
  };
  
  module.exports = deepEqualCyclic;
  