text: var config = require('../config');
  var flag = require('./flag');
  var getProperties = require('./getProperties');
  var isProxyEnabled = require('./isProxyEnabled');
  
  /*!
   * Chai - proxify utility
   * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
   * MIT Licensed
   */
  
  /**
   * ### .proxify(object)
   *
   * Return a proxy of given object that throws an error when a non-existent
   * property is read. By default, the root cause is assumed to be a misspelled
   * property, and thus an attempt is made to offer a reasonable suggestion from
   * the list of existing properties. However, if a nonChainableMethodName is
   * provided, then the root cause is instead a failure to invoke a non-chainable
   * method prior to reading the non-existent property.
   *
   * If proxies are unsupported or disabled via the user's Chai config, then
   * return object without modification.
   *
   * @param {Object} obj
   * @param {String} nonChainableMethodName
   * @namespace Utils
   * @name proxify
   */
  
  var builtins = ['__flags', '__methods', '_obj', 'assert'];
  
  module.exports = function proxify(obj, nonChainableMethodName) {
    if (!isProxyEnabled()) return obj;
  
    return new Proxy(obj, {
      get: function proxyGetter(target, property) {
        // This check is here because we should not throw errors on Symbol properties
        // such as `Symbol.toStringTag`.
        // The values for which an error should be thrown can be configured using
        // the `config.proxyExcludedKeys` setting.
        if (typeof property === 'string' &&
            config.proxyExcludedKeys.indexOf(property) === -1 &&
            !Reflect.has(target, property)) {
          // Special message for invalid property access of non-chainable methods.
          if (nonChainableMethodName) {
            throw Error('Invalid Chai property: ' + nonChainableMethodName + '.' +
              property + '. See docs for proper usage of "' +
              nonChainableMethodName + '".');
          }
  
          // If the property is reasonably close to an existing Chai property,
          // suggest that property to the user. Only suggest properties with a
          // distance less than 4.
          var suggestion = null;
          var suggestionDistance = 4;
          getProperties(target).forEach(function(prop) {
            if (
              !Object.prototype.hasOwnProperty(prop) &&
              builtins.indexOf(prop) === -1
            ) {
              var dist = stringDistanceCapped(
                property,
                prop,
                suggestionDistance
              );
              if (dist < suggestionDistance) {
                suggestion = prop;
                suggestionDistance = dist;
              }
            }
          });
  
          if (suggestion !== null) {
            throw Error('Invalid Chai property: ' + property +
              '. Did you mean "' + suggestion + '"?');
          } else {
            if ([
              'visible', 'active', 'enabled', 'present', 'selected', 'text', 'value'
            ].includes(property)) {
              throw Error('Property ".' + property + '" is not available when asserting on non-element values.');
            }
  
            if ([
              'attribute', 'css'
            ].includes(property)) {
              throw Error('Method ".' + property + '()" is not available when asserting on non-element values.');
            }
  
            throw Error('Invalid Chai property: ' + property);
          }
        }
  
        // Use this proxy getter as the starting point for removing implementation
        // frames from the stack trace of a failed assertion. For property
        // assertions, this prevents the proxy getter from showing up in the stack
        // trace since it's invoked before the property getter. For method and
        // chainable method assertions, this flag will end up getting changed to
        // the method wrapper, which is good since this frame will no longer be in
        // the stack once the method is invoked. Note that Chai builtin assertion
        // properties such as `__flags` are skipped since this is only meant to
        // capture the starting point of an assertion. This step is also skipped
        // if the `lockSsfi` flag is set, thus indicating that this assertion is
        // being called from within another assertion. In that case, the `ssfi`
        // flag is already set to the outer assertion's starting point.
        if (builtins.indexOf(property) === -1 && !flag(target, 'lockSsfi')) {
          flag(target, 'ssfi', proxyGetter);
        }
  
        return Reflect.get(target, property);
      }
    });
  };
  
  /**
   * # stringDistanceCapped(strA, strB, cap)
   * Return the Levenshtein distance between two strings, but no more than cap.
   * @param {string} strA
   * @param {string} strB
   * @param {number} number
   * @return {number} min(string distance between strA and strB, cap)
   * @api private
   */
  
  function stringDistanceCapped(strA, strB, cap) {
    if (Math.abs(strA.length - strB.length) >= cap) {
      return cap;
    }
  
    var memo = [];
    // `memo` is a two-dimensional array containing distances.
    // memo[i][j] is the distance between strA.slice(0, i) and
    // strB.slice(0, j).
    for (var i = 0; i <= strA.length; i++) {
      memo[i] = Array(strB.length + 1).fill(0);
      memo[i][0] = i;
    }
    for (var j = 0; j < strB.length; j++) {
      memo[0][j] = j;
    }
  
    for (var i = 1; i <= strA.length; i++) {
      var ch = strA.charCodeAt(i - 1);
      for (var j = 1; j <= strB.length; j++) {
        if (Math.abs(i - j) >= cap) {
          memo[i][j] = cap;
          continue;
        }
        memo[i][j] = Math.min(
          memo[i - 1][j] + 1,
          memo[i][j - 1] + 1,
          memo[i - 1][j - 1] +
            (ch === strB.charCodeAt(j - 1) ? 0 : 1)
        );
      }
    }
  
    return memo[strA.length][strB.length];
  }
  