/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow
 */

'use strict';

/**
 * EventValidator is designed to validate event types to make it easier to catch
 * common mistakes. It accepts a map of all of the different types of events
 * that the emitter can emit. Then, if a user attempts to emit an event that is
 * not one of those specified types the emitter will throw an error. Also, it
 * provides a relatively simple matcher so that if it thinks that you likely
 * mistyped the event name it will suggest what you might have meant to type in
 * the error message.
 */
const EventValidator = {
  /**
   * @param {Object} emitter - The object responsible for emitting the actual
   *                             events
   * @param {Object} types - The collection of valid types that will be used to
   *                         check for errors
   * @return {Object} A new emitter with event type validation
   * @example
   *   const types = {someEvent: true, anotherEvent: true};
   *   const emitter = EventValidator.addValidation(emitter, types);
   */
  addValidation: function(emitter: Object, types: Object) {
    const eventTypes = Object.keys(types);
    const emitterWithValidation = Object.create(emitter);

    Object.assign(emitterWithValidation, {
      emit: function emit(type, a, b, c, d, e, _) {
        assertAllowsEventType(type, eventTypes);
        return emitter.emit.call(this, type, a, b, c, d, e, _);
      },
    });

    return emitterWithValidation;
  },
};

function assertAllowsEventType(type, allowedTypes) {
  if (allowedTypes.indexOf(type) === -1) {
    throw new TypeError(errorMessageFor(type, allowedTypes));
  }
}

function errorMessageFor(type, allowedTypes) {
  let message = 'Unknown event type "' + type + '". ';
  if (__DEV__) {
    message += recommendationFor(type, allowedTypes);
  }
  message += 'Known event types: ' + allowedTypes.join(', ') + '.';
  return message;
}

// Allow for good error messages
if (__DEV__) {
  var recommendationFor = function(type, allowedTypes) {
    const closestTypeRecommendation = closestTypeFor(type, allowedTypes);
    if (isCloseEnough(closestTypeRecommendation, type)) {
      return 'Did you mean "' + closestTypeRecommendation.type + '"? ';
    } else {
      return '';
    }
  };

  var closestTypeFor = function(type, allowedTypes) {
    const typeRecommendations = allowedTypes.map(
      typeRecommendationFor.bind(this, type),
    );
    return typeRecommendations.sort(recommendationSort)[0];
  };

  var typeRecommendationFor = function(type, recommendedType) {
    return {
      type: recommendedType,
      distance: damerauLevenshteinDistance(type, recommendedType),
    };
  };

  var recommendationSort = function(recommendationA, recommendationB) {
    if (recommendationA.distance < recommendationB.distance) {
      return -1;
    } else if (recommendationA.distance > recommendationB.distance) {
      return 1;
    } else {
      return 0;
    }
  };

  var isCloseEnough = function(closestType, actualType) {
    return closestType.distance / actualType.length < 0.334;
  };

  var damerauLevenshteinDistance = function(a, b) {
    let i, j;
    const d = [];

    for (i = 0; i <= a.length; i++) {
      d[i] = [i];
    }

    for (j = 1; j <= b.length; j++) {
      d[0][j] = j;
    }

    for (i = 1; i <= a.length; i++) {
      for (j = 1; j <= b.length; j++) {
        const cost = a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1;

        d[i][j] = Math.min(
          d[i - 1][j] + 1,
          d[i][j - 1] + 1,
          d[i - 1][j - 1] + cost,
        );

        if (
          i > 1 &&
          j > 1 &&
          a.charAt(i - 1) === b.charAt(j - 2) &&
          a.charAt(i - 2) === b.charAt(j - 1)
        ) {
          d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + cost);
        }
      }
    }

    return d[a.length][b.length];
  };
}

module.exports = EventValidator;

@@ -2426,19 +2426,21 @@
   %7D;%0A%0A  
-var
+const
  closest
@@ -2651,19 +2651,21 @@
   %7D;%0A%0A  
-var
+const
  typeRec
@@ -2839,19 +2839,21 @@
   %7D;%0A%0A  
-var
+const
  recomme
@@ -3120,19 +3120,21 @@
   %7D;%0A%0A  
-var
+const
  isClose
@@ -3247,19 +3247,21 @@
   %7D;%0A%0A  
-var
+const
  damerau

Remove var in Libraries/emitter/* (#22087)