Suggest spelling for `value`.
function suggest(value) {
  var self = this
  var replacementTable = self.replacementTable
  var conversion = self.conversion
  var groups = self.flags.KEY
  var suggestions = []
  var weighted = {}
  var memory
  var replacement
  var edits = []
  var values
  var index
  var length
  var offset
  var position
  var count
  var otherOffset
  var otherCount
  var otherCharacter
  var character
  var group
  var before
  var after
  var upper
  var insensitive
  var firstLevel
  var prev
  var next
  var nextCharacter
  var max
  var distance
  var end
  var size
  var normalized
  var suggestion
  var currentCase

  value = normalize(trim(value), conversion.in)

  if (!value || self.correct(value)) {
    return []
  }

  currentCase = casing(value)

  // Check the replacement table.
  length = replacementTable.length
  index = -1

  while (++index < length) {
    replacement = replacementTable[index]
    offset = value.indexOf(replacement[0])

    while (offset !== -1) {
      edits.push(value.replace(replacement[0], replacement[1]))
      offset = value.indexOf(replacement[0], offset + 1)
    }
  }

  // Check the keyboard.
  length = value.length
  index = -1

  while (++index < length) {
    character = value.charAt(index)
    insensitive = character.toLowerCase()
    upper = insensitive !== character
    offset = -1
    count = groups.length

    while (++offset < count) {
      group = groups[offset]
      position = group.indexOf(insensitive)

      if (position === -1) {
        continue
      }

      before = value.slice(0, position)
      after = value.slice(position + 1)
      otherOffset = -1
      otherCount = group.length

      while (++otherOffset < otherCount) {
        if (otherOffset !== position) {
          otherCharacter = group.charAt(otherOffset)

          if (upper) {
            otherCharacter = otherCharacter.toUpperCase()
          }

          edits.push(before + otherCharacter + after)
        }
      }
    }
  }

  // Check cases where one of a double character was forgotten, or one too many
  // were added, up to three “distances”.  This increases the success-rate by 2%
  // and speeds the process up by 13%.
  length = value.length
  index = -1
  nextCharacter = value.charAt(0)
  values = ['']
  max = 1
  distance = 0

  while (++index < length) {
    character = nextCharacter
    nextCharacter = value.charAt(index + 1)
    before = value.slice(0, index)

    replacement = character === nextCharacter ? '' : character + character
    offset = -1
    count = values.length

    while (++offset < count) {
      if (offset <= max) {
        values.push(values[offset] + replacement)
      }

      values[offset] += character
    }

    if (++distance < 3) {
      max = values.length
    }
  }

  edits = edits.concat(values)

  // Ensure the lower-cased, capitalised, and uppercase values are included.
  values = [value]
  replacement = value.toLowerCase()

  if (value === replacement) {
    values.push(value.charAt(0).toUpperCase() + replacement.slice(1))
  } else {
    values.push(replacement)
  }

  replacement = value.toUpperCase()

  if (value !== replacement) {
    values.push(replacement)
  }

  // Construct a memory object for `generate`.
  memory = {
    state: {},
    weighted: weighted,
    suggestions: suggestions
  }

  firstLevel = generate(self, memory, values, edits)

  // While there are no suggestions based on generated values with an
  // edit-distance of `1`, check the generated values, `SIZE` at a time.
  // Basically, we’re generating values with an edit-distance of `2`, but were
  // doing it in small batches because it’s such an expensive operation.
  prev = 0
  max = Math.pow(Math.max(15 - value.length, 3), 3)
  max = Math.min(firstLevel.length, max)
  end = Date.now() + Math.min(30 * value.length, 200)
  size = Math.max(Math.pow(10 - value.length, 3), 1)

  while (!suggestions.length && prev < max) {
    next = prev + size
    generate(self, memory, firstLevel.slice(prev, next))
    prev = next

    if (Date.now() > end) {
      break
    }
  }

  // Sort the suggestions based on their weight.
  suggestions.sort(sort)

  // Normalize the output.
  values = []
  normalized = []
  index = -1
  length = suggestions.length

  while (++index < length) {
    suggestion = normalize(suggestions[index], conversion.out)
    suggestions[index] = suggestion
    replacement = suggestion.toLowerCase()

    if (normalized.indexOf(replacement) === -1) {
      values.push(suggestion)
      normalized.push(replacement)
    }
  }

  // BOOM! All done!
  return values

  function sort(a, b) {
    return sortWeight(a, b) || sortCasing(a, b) || sortAlpha(a, b)
  }

  function sortWeight(a, b) {
    if (weighted[a] === weighted[b]) {
      return 0
    }

    return weighted[a] > weighted[b] ? -1 : 1
  }

  function sortCasing(a, b) {
    var leftCasing = casing(a)
    var rightCasing = casing(b)

    if (leftCasing !== rightCasing) {
      if (leftCasing === currentCase) {
        return -1
      }

      if (rightCasing === currentCase) {
        return 1
      }
    }

    return 0
  }

  function sortAlpha(a, b) {
    return a.localeCompare(b)
  }
}