text: 'use strict'
  
  var decimal = require('is-decimal')
  var alphanumeric = require('is-alphanumeric')
  var whitespace = require('is-whitespace-character')
  var escapes = require('markdown-escapes')
  var prefix = require('./util/entity-prefix-length')
  
  module.exports = factory
  
  var tab = '\t'
  var lineFeed = '\n'
  var space = ' '
  var numberSign = '#'
  var ampersand = '&'
  var leftParenthesis = '('
  var rightParenthesis = ')'
  var asterisk = '*'
  var plusSign = '+'
  var dash = '-'
  var dot = '.'
  var colon = ':'
  var lessThan = '<'
  var greaterThan = '>'
  var leftSquareBracket = '['
  var backslash = '\\'
  var rightSquareBracket = ']'
  var underscore = '_'
  var graveAccent = '`'
  var verticalBar = '|'
  var tilde = '~'
  var exclamationMark = '!'
  
  var entities = {
    '<': '&lt;',
    ':': '&#x3A;',
    '&': '&amp;',
    '|': '&#x7C;',
    '~': '&#x7E;'
  }
  
  var shortcut = 'shortcut'
  var mailto = 'mailto'
  var https = 'https'
  var http = 'http'
  
  var blankExpression = /\n\s*$/
  
  // Factory to escape characters.
  function factory(options) {
    return escape
  
    // Escape punctuation characters in a node’s value.
    function escape(value, node, parent) {
      var self = this
      var gfm = options.gfm
      var commonmark = options.commonmark
      var pedantic = options.pedantic
      var markers = commonmark ? [dot, rightParenthesis] : [dot]
      var siblings = parent && parent.children
      var index = siblings && siblings.indexOf(node)
      var previous = siblings && siblings[index - 1]
      var next = siblings && siblings[index + 1]
      var length = value.length
      var escapable = escapes(options)
      var position = -1
      var queue = []
      var escaped = queue
      var afterNewLine
      var character
      var wordCharBefore
      var wordCharAfter
      var offset
      var replace
  
      if (previous) {
        afterNewLine = text(previous) && blankExpression.test(previous.value)
      } else {
        afterNewLine =
          !parent || parent.type === 'root' || parent.type === 'paragraph'
      }
  
      while (++position < length) {
        character = value.charAt(position)
        replace = false
  
        if (character === '\n') {
          afterNewLine = true
        } else if (
          character === backslash ||
          character === graveAccent ||
          character === asterisk ||
          character === leftSquareBracket ||
          character === lessThan ||
          (character === ampersand && prefix(value.slice(position)) > 0) ||
          (character === rightSquareBracket && self.inLink) ||
          (gfm && character === tilde && value.charAt(position + 1) === tilde) ||
          (gfm &&
            character === verticalBar &&
            (self.inTable || alignment(value, position))) ||
          (character === underscore &&
            // Delegate leading/trailing underscores to the multinode version below.
            position > 0 &&
            position < length - 1 &&
            (pedantic ||
              !alphanumeric(value.charAt(position - 1)) ||
              !alphanumeric(value.charAt(position + 1)))) ||
          (gfm && !self.inLink && character === colon && protocol(queue.join('')))
        ) {
          replace = true
        } else if (afterNewLine) {
          if (
            character === greaterThan ||
            character === numberSign ||
            character === asterisk ||
            character === dash ||
            character === plusSign
          ) {
            replace = true
          } else if (decimal(character)) {
            offset = position + 1
  
            while (offset < length) {
              if (!decimal(value.charAt(offset))) {
                break
              }
  
              offset++
            }
  
            if (markers.indexOf(value.charAt(offset)) !== -1) {
              next = value.charAt(offset + 1)
  
              if (!next || next === space || next === tab || next === lineFeed) {
                queue.push(value.slice(position, offset))
                position = offset
                character = value.charAt(position)
                replace = true
              }
            }
          }
        }
  
        if (afterNewLine && !whitespace(character)) {
          afterNewLine = false
        }
  
        queue.push(replace ? one(character) : character)
      }
  
      // Multi-node versions.
      if (siblings && text(node)) {
        // Check for an opening parentheses after a link-reference (which can be
        // joined by white-space).
        if (previous && previous.referenceType === shortcut) {
          position = -1
          length = escaped.length
  
          while (++position < length) {
            character = escaped[position]
  
            if (character === space || character === tab) {
              continue
            }
  
            if (character === leftParenthesis || character === colon) {
              escaped[position] = one(character)
            }
  
            break
          }
  
          // If the current node is all spaces / tabs, preceded by a shortcut,
          // and followed by a text starting with `(`, escape it.
          if (
            text(next) &&
            position === length &&
            next.value.charAt(0) === leftParenthesis
          ) {
            escaped.push(backslash)
          }
        }
  
        // Ensure non-auto-links are not seen as links.  This pattern needs to
        // check the preceding nodes too.
        if (
          gfm &&
          !self.inLink &&
          text(previous) &&
          value.charAt(0) === colon &&
          protocol(previous.value.slice(-6))
        ) {
          escaped[0] = one(colon)
        }
  
        // Escape ampersand if it would otherwise start an entity.
        if (
          text(next) &&
          value.charAt(length - 1) === ampersand &&
          prefix(ampersand + next.value) !== 0
        ) {
          escaped[escaped.length - 1] = one(ampersand)
        }
  
        // Escape exclamation marks immediately followed by links.
        if (
          next &&
          next.type === 'link' &&
          value.charAt(length - 1) === exclamationMark
        ) {
          escaped[escaped.length - 1] = one(exclamationMark)
        }
  
        // Escape double tildes in GFM.
        if (
          gfm &&
          text(next) &&
          value.charAt(length - 1) === tilde &&
          next.value.charAt(0) === tilde
        ) {
          escaped.splice(-1, 0, backslash)
        }
  
        // Escape underscores, but not mid-word (unless in pedantic mode).
        wordCharBefore = text(previous) && alphanumeric(previous.value.slice(-1))
        wordCharAfter = text(next) && alphanumeric(next.value.charAt(0))
  
        if (length === 1) {
          if (
            value === underscore &&
            (pedantic || !wordCharBefore || !wordCharAfter)
          ) {
            escaped.unshift(backslash)
          }
        } else {
          if (
            value.charAt(0) === underscore &&
            (pedantic || !wordCharBefore || !alphanumeric(value.charAt(1)))
          ) {
            escaped.unshift(backslash)
          }
  
          if (
            value.charAt(length - 1) === underscore &&
            (pedantic ||
              !wordCharAfter ||
              !alphanumeric(value.charAt(length - 2)))
          ) {
            escaped.splice(-1, 0, backslash)
          }
        }
      }
  
      return escaped.join('')
  
      function one(character) {
        return escapable.indexOf(character) === -1
          ? entities[character]
          : backslash + character
      }
    }
  }
  
  // Check if `index` in `value` is inside an alignment row.
  function alignment(value, index) {
    var start = value.lastIndexOf(lineFeed, index)
    var end = value.indexOf(lineFeed, index)
    var char
  
    end = end === -1 ? value.length : end
  
    while (++start < end) {
      char = value.charAt(start)
  
      if (
        char !== colon &&
        char !== dash &&
        char !== space &&
        char !== verticalBar
      ) {
        return false
      }
    }
  
    return true
  }
  
  // Check if `node` is a text node.
  function text(node) {
    return node && node.type === 'text'
  }
  
  // Check if `value` ends in a protocol.
  function protocol(value) {
    var tail = value.slice(-6).toLowerCase()
    return tail === mailto || tail.slice(-5) === https || tail.slice(-4) === http
  }
  