text: 'use strict';
  
  const mongoErrorContextSymbol = Symbol('mongoErrorContextSymbol');
  const maxWireVersion = require('./utils').maxWireVersion;
  
  /**
   * Creates a new MongoError
   *
   * @augments Error
   * @param {Error|string|object} message The error message
   * @property {string} message The error message
   * @property {string} stack The error call stack
   */
  class MongoError extends Error {
    constructor(message) {
      if (message instanceof Error) {
        super(message.message);
        this.stack = message.stack;
      } else {
        if (typeof message === 'string') {
          super(message);
        } else {
          super(message.message || message.errmsg || message.$err || 'n/a');
          for (var name in message) {
            this[name] = message[name];
          }
        }
  
        Error.captureStackTrace(this, this.constructor);
      }
  
      this.name = 'MongoError';
      this[mongoErrorContextSymbol] = this[mongoErrorContextSymbol] || {};
    }
  
    /**
     * Creates a new MongoError object
     *
     * @param {Error|string|object} options The options used to create the error.
     * @return {MongoError} A MongoError instance
     * @deprecated Use `new MongoError()` instead.
     */
    static create(options) {
      return new MongoError(options);
    }
  
    hasErrorLabel(label) {
      return this.errorLabels && this.errorLabels.indexOf(label) !== -1;
    }
  }
  
  /**
   * Creates a new MongoNetworkError
   *
   * @param {Error|string|object} message The error message
   * @property {string} message The error message
   * @property {string} stack The error call stack
   */
  class MongoNetworkError extends MongoError {
    constructor(message) {
      super(message);
      this.name = 'MongoNetworkError';
    }
  }
  
  /**
   * An error used when attempting to parse a value (like a connection string)
   *
   * @param {Error|string|object} message The error message
   * @property {string} message The error message
   */
  class MongoParseError extends MongoError {
    constructor(message) {
      super(message);
      this.name = 'MongoParseError';
    }
  }
  
  /**
   * An error signifying a timeout event
   *
   * @param {Error|string|object} message The error message
   * @param {string|object} [reason] The reason the timeout occured
   * @property {string} message The error message
   * @property {string} [reason] An optional reason context for the timeout, generally an error saved during flow of monitoring and selecting servers
   */
  class MongoTimeoutError extends MongoError {
    constructor(message, reason) {
      super(message);
      this.name = 'MongoTimeoutError';
      if (reason != null) {
        this.reason = reason;
      }
    }
  }
  
  function makeWriteConcernResultObject(input) {
    const output = Object.assign({}, input);
  
    if (output.ok === 0) {
      output.ok = 1;
      delete output.errmsg;
      delete output.code;
      delete output.codeName;
    }
  
    return output;
  }
  
  /**
   * An error thrown when the server reports a writeConcernError
   *
   * @param {Error|string|object} message The error message
   * @param {object} result The result document (provided if ok: 1)
   * @property {string} message The error message
   * @property {object} [result] The result document (provided if ok: 1)
   */
  class MongoWriteConcernError extends MongoError {
    constructor(message, result) {
      super(message);
      this.name = 'MongoWriteConcernError';
  
      if (result != null) {
        this.result = makeWriteConcernResultObject(result);
      }
    }
  }
  
  // see: https://github.com/mongodb/specifications/blob/master/source/retryable-writes/retryable-writes.rst#terms
  const RETRYABLE_ERROR_CODES = new Set([
    6, // HostUnreachable
    7, // HostNotFound
    89, // NetworkTimeout
    91, // ShutdownInProgress
    189, // PrimarySteppedDown
    9001, // SocketException
    10107, // NotMaster
    11600, // InterruptedAtShutdown
    11602, // InterruptedDueToReplStateChange
    13435, // NotMasterNoSlaveOk
    13436 // NotMasterOrSecondary
  ]);
  
  /**
   * Determines whether an error is something the driver should attempt to retry
   *
   * @param {MongoError|Error} error
   */
  function isRetryableError(error) {
    return (
      RETRYABLE_ERROR_CODES.has(error.code) ||
      error instanceof MongoNetworkError ||
      error.message.match(/not master/) ||
      error.message.match(/node is recovering/)
    );
  }
  
  const SDAM_RECOVERING_CODES = new Set([
    91, // ShutdownInProgress
    189, // PrimarySteppedDown
    11600, // InterruptedAtShutdown
    11602, // InterruptedDueToReplStateChange
    13436 // NotMasterOrSecondary
  ]);
  
  const SDAM_NOTMASTER_CODES = new Set([
    10107, // NotMaster
    13435 // NotMasterNoSlaveOk
  ]);
  
  const SDAM_NODE_SHUTTING_DOWN_ERROR_CODES = new Set([
    11600, // InterruptedAtShutdown
    91 // ShutdownInProgress
  ]);
  
  function isRecoveringError(err) {
    if (err.code && SDAM_RECOVERING_CODES.has(err.code)) {
      return true;
    }
  
    return err.message.match(/not master or secondary/) || err.message.match(/node is recovering/);
  }
  
  function isNotMasterError(err) {
    if (err.code && SDAM_NOTMASTER_CODES.has(err.code)) {
      return true;
    }
  
    if (isRecoveringError(err)) {
      return false;
    }
  
    return err.message.match(/not master/);
  }
  
  function isNodeShuttingDownError(err) {
    return err.code && SDAM_NODE_SHUTTING_DOWN_ERROR_CODES.has(err.code);
  }
  
  /**
   * Determines whether SDAM can recover from a given error. If it cannot
   * then the pool will be cleared, and server state will completely reset
   * locally.
   *
   * @see https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#not-master-and-node-is-recovering
   * @param {MongoError|Error} error
   * @param {Server} server
   */
  function isSDAMUnrecoverableError(error, server) {
    // NOTE: null check is here for a strictly pre-CMAP world, a timeout or
    //       close event are considered unrecoverable
    if (error instanceof MongoParseError || error == null) {
      return true;
    }
  
    if (isRecoveringError(error) || isNotMasterError(error)) {
      if (maxWireVersion(server) >= 8 && !isNodeShuttingDownError(error)) {
        return false;
      }
  
      return true;
    }
  
    return false;
  }
  
  module.exports = {
    MongoError,
    MongoNetworkError,
    MongoParseError,
    MongoTimeoutError,
    MongoWriteConcernError,
    mongoErrorContextSymbol,
    isRetryableError,
    isSDAMUnrecoverableError
  };
  