text: from collections import OrderedDict
  from typing import List
  
  from typing_extensions import TypedDict
  
  
  class KeyBinding(TypedDict, total=False):
      keys: List[str]
      help_text: str
      excluded_from_random_tips: bool
      key_category: str
  
  
  # fmt: off
  KEY_BINDINGS: 'OrderedDict[str, KeyBinding]' = OrderedDict([
      # Key that is displayed in the UI is determined by the method
      # primary_key_for_command. (Currently the first key in the list)
  
      ('HELP', {
          'keys': ['?'],
          'help_text': 'Show/hide help menu',
          'excluded_from_random_tips': True,
          'key_category': 'general',
      }),
      ('ABOUT', {
          'keys': ['meta ?'],
          'help_text': 'Show/hide about menu',
          'key_category': 'general',
      }),
      ('GO_BACK', {
          'keys': ['esc'],
          'help_text': 'Go Back',
          'excluded_from_random_tips': False,
          'key_category': 'general',
      }),
      ('OPEN_DRAFT', {
          'keys': ['d'],
          'help_text': 'Open draft message saved in this session',
          'key_category': 'general',
      }),
      ('GO_UP', {
          'keys': ['up', 'k'],
          'help_text': 'Go up / Previous message',
          'key_category': 'navigation',
      }),
      ('GO_DOWN', {
          'keys': ['down', 'j'],
          'help_text': 'Go down / Next message',
          'key_category': 'navigation',
      }),
      ('GO_LEFT', {
          'keys': ['left', 'h'],
          'help_text': 'Go left',
          'key_category': 'navigation',
      }),
      ('GO_RIGHT', {
          'keys': ['right', 'l'],
          'help_text': 'Go right',
          'key_category': 'navigation',
      }),
      ('SCROLL_UP', {
          'keys': ['page up', 'K'],
          'help_text': 'Scroll up',
          'key_category': 'navigation',
      }),
      ('SCROLL_DOWN', {
          'keys': ['page down', 'J'],
          'help_text': 'Scroll down',
          'key_category': 'navigation',
      }),
      ('GO_TO_BOTTOM', {
          'keys': ['end', 'G'],
          'help_text': 'Go to bottom / Last message',
          'key_category': 'navigation',
      }),
      ('REPLY_MESSAGE', {
          'keys': ['r', 'enter'],
          'help_text': 'Reply to the current message',
          'key_category': 'msg_actions',
      }),
      ('MENTION_REPLY', {
          'keys': ['@'],
          'help_text': 'Reply mentioning the sender of the current message',
          'key_category': 'msg_actions',
      }),
      ('QUOTE_REPLY', {
          'keys': ['>'],
          'help_text': 'Reply quoting the current message text',
          'key_category': 'msg_actions',
      }),
      ('REPLY_AUTHOR', {
          'keys': ['R'],
          'help_text': 'Reply privately to the sender of the current message',
          'key_category': 'msg_actions',
      }),
      ('EDIT_MESSAGE', {
          'keys': ['e'],
          'help_text': "Edit message's content or topic",
          'key_category': 'msg_actions'
      }),
      ('STREAM_MESSAGE', {
          'keys': ['c'],
          'help_text': 'New message to a stream',
          'key_category': 'msg_actions',
      }),
      ('PRIVATE_MESSAGE', {
          'keys': ['x'],
          'help_text': 'New message to a person or group of people',
          'key_category': 'msg_actions',
      }),
      ('CYCLE_COMPOSE_FOCUS', {
          'keys': ['tab'],
          'help_text': 'Cycle through recipient and content boxes',
          'key_category': 'msg_compose',
      }),
      ('SEND_MESSAGE', {
          'keys': ['ctrl d', 'meta enter'],
          'help_text': 'Send a message',
          'key_category': 'msg_compose',
      }),
      ('SAVE_AS_DRAFT', {
          'keys': ['meta s'],
          'help_text': 'Save current message as a draft',
          'key_category': 'msg_compose',
      }),
      ('AUTOCOMPLETE', {
          'keys': ['ctrl f'],
          'help_text': ('Autocomplete @mentions, #stream_names, :emoji:'
                        ' and topics'),
          'key_category': 'msg_compose',
      }),
      ('AUTOCOMPLETE_REVERSE', {
          'keys': ['ctrl r'],
          'help_text': 'Cycle through autocomplete suggestions in reverse',
          'key_category': 'msg_compose',
      }),
      ('STREAM_NARROW', {
          'keys': ['s'],
          'help_text': 'Narrow to the stream of the current message',
          'key_category': 'msg_actions',
      }),
      ('TOPIC_NARROW', {
          'keys': ['S'],
          'help_text': 'Narrow to the topic of the current message',
          'key_category': 'msg_actions',
      }),
      ('TOGGLE_NARROW', {
          'keys': ['z'],
          'help_text':
              'Narrow to a topic/private-chat, or stream/all-private-messages',
          'key_category': 'msg_actions',
      }),
      ('TOGGLE_TOPIC', {
          'keys': ['t'],
          'help_text': 'Toggle topics in a stream',
          'key_category': 'stream_list',
      }),
      ('ALL_MESSAGES', {
          'keys': ['a', 'esc'],
          'help_text': 'Narrow to all messages',
          'key_category': 'navigation',
      }),
      ('ALL_PM', {
          'keys': ['P'],
          'help_text': 'Narrow to all private messages',
          'key_category': 'navigation',
      }),
      ('ALL_STARRED', {
          'keys': ['f'],
          'help_text': 'Narrow to all starred messages',
          'key_category': 'navigation',
      }),
      ('ALL_MENTIONS', {
          'keys': ['#'],
          'help_text': "Narrow to messages in which you're mentioned",
          'key_category': 'navigation',
      }),
      ('NEXT_UNREAD_TOPIC', {
          'keys': ['n'],
          'help_text': 'Next unread topic',
          'key_category': 'navigation',
      }),
      ('NEXT_UNREAD_PM', {
          'keys': ['p'],
          'help_text': 'Next unread private message',
          'key_category': 'navigation',
      }),
      ('SEARCH_PEOPLE', {
          'keys': ['w'],
          'help_text': 'Search Users',
          'key_category': 'searching',
      }),
      ('SEARCH_MESSAGES', {
          'keys': ['/'],
          'help_text': 'Search Messages',
          'key_category': 'searching',
      }),
      ('SEARCH_STREAMS', {
          'keys': ['q'],
          'help_text': 'Search Streams',
          'key_category': 'searching',
      }),
      ('SEARCH_TOPICS', {
          'keys': ['q'],
          'help_text': 'Search topics in a stream',
          'key_category': 'searching',
      }),
      ('TOGGLE_MUTE_STREAM', {
          'keys': ['m'],
          'help_text': 'Mute/unmute Streams',
          'key_category': 'stream_list',
      }),
      ('ENTER', {
          'keys': ['enter'],
          'help_text': 'Perform current action',
          'key_category': 'navigation',
      }),
      ('THUMBS_UP', {
          'keys': ['+'],
          'help_text': 'Add/remove thumbs-up reaction to the current message',
          'key_category': 'msg_actions',
      }),
      ('TOGGLE_STAR_STATUS', {
          'keys': ['ctrl s', '*'],
          'help_text': 'Add/remove star status of the current message',
          'key_category': 'msg_actions',
      }),
      ('MSG_INFO', {
          'keys': ['i'],
          'help_text': 'Show/hide message information',
          'key_category': 'msg_actions',
      }),
      ('EDIT_HISTORY', {
          'keys': ['e'],
          'help_text': 'Show/hide edit history (from message information)',
          'excluded_from_random_tips': True,
          'key_category': 'msg_actions',
      }),
      ('VIEW_IN_BROWSER', {
          'keys': ['v'],
          'help_text':
              'View current message in browser (from message information)',
          'excluded_from_random_tips': True,
          'key_category': 'msg_actions',
      }),
      ('STREAM_DESC', {
          'keys': ['i'],
          'help_text': 'Show/hide stream information & modify settings',
          'key_category': 'stream_list',
      }),
      ('STREAM_MEMBERS', {
          'keys': ['m'],
          'help_text': 'Show/hide stream members (from stream information)',
          'excluded_from_random_tips': True,
          'key_category': 'stream_list',
      }),
      ('REDRAW', {
          'keys': ['ctrl l'],
          'help_text': 'Redraw screen',
          'key_category': 'general',
      }),
      ('QUIT', {
          'keys': ['ctrl c'],
          'help_text': 'Quit',
          'key_category': 'general',
      }),
      ('USER_INFO', {
          'keys': ['i'],
          'help_text': 'View user information (From Users list)',
          'key_category': 'general',
      }),
      ('BEGINNING_OF_LINE', {
          'keys': ['ctrl a'],
          'help_text': 'Jump to the beginning of line',
          'key_category': 'msg_compose',
      }),
      ('END_OF_LINE', {
          'keys': ['ctrl e'],
          'help_text': 'Jump to the end of line',
          'key_category': 'msg_compose',
      }),
      ('ONE_WORD_BACKWARD', {
          'keys': ['meta b'],
          'help_text': 'Jump backward one word',
          'key_category': 'msg_compose',
      }),
      ('ONE_WORD_FORWARD', {
          'keys': ['meta f'],
          'help_text': 'Jump forward one word',
          'key_category': 'msg_compose',
      }),
      ('DELETE_LAST_CHARACTER', {
          'keys': ['ctrl h'],
          'help_text': 'Delete previous character (to left)',
          'key_category': 'msg_compose',
      }),
      ('TRANSPOSE_CHARACTERS', {
          'keys': ['ctrl t'],
          'help_text': 'Transpose characters',
          'key_category': 'msg_compose',
      }),
      ('CUT_TO_END_OF_LINE', {
          'keys': ['ctrl k'],
          'help_text': 'Cut forwards to the end of the line',
          'key_category': 'msg_compose',
      }),
      ('CUT_TO_START_OF_LINE', {
          'keys': ['ctrl u'],
          'help_text': 'Cut backwards to the start of the line',
          'key_category': 'msg_compose',
      }),
      ('CUT_TO_END_OF_WORD', {
          'keys': ['meta d'],
          'help_text': 'Cut forwards to the end of the current word',
          'key_category': 'msg_compose',
      }),
      ('CUT_TO_START_OF_WORD', {
          'keys': ['ctrl w'],
          'help_text': 'Cut backwards to the start of the current word',
          'key_category': 'msg_compose',
      }),
      ('PASTE_LAST_CUT', {
          'keys': ['ctrl y'],
          'help_text': 'Paste last cut section',
          'key_category': 'msg_compose',
      }),
      ('UNDO_LAST_ACTION', {
          'keys': ['ctrl _'],
          'help_text': 'Undo last action',
          'key_category': 'msg_compose',
      }),
      ('PREV_LINE', {
          'keys': ['up', 'ctrl p'],
          'help_text': 'Jump to the previous line',
          'key_category': 'msg_compose',
      }),
      ('NEXT_LINE', {
          'keys': ['down', 'ctrl n'],
          'help_text': 'Jump to the next line',
          'key_category': 'msg_compose',
      }),
      ('CLEAR_MESSAGE', {
          'keys': ['ctrl l'],
          'help_text': 'Clear compose box',
          'key_category': 'msg_compose',
      }),
  ])
  # fmt: on
  
  HELP_CATEGORIES = OrderedDict(
      [
          ("general", "General"),
          ("navigation", "Navigation"),
          ("searching", "Searching"),
          ("msg_actions", "Message actions"),
          ("stream_list", "Stream list actions"),
          ("msg_compose", "Composing a Message"),
      ]
  )
  
  
  class InvalidCommand(Exception):
      pass
  
  
  def is_command_key(command: str, key: str) -> bool:
      """
      Returns the mapped binding for a key if mapped
      or the key otherwise.
      """
      try:
          return key in KEY_BINDINGS[command]["keys"]
      except KeyError as exception:
          raise InvalidCommand(command)
  
  
  def keys_for_command(command: str) -> List[str]:
      """
      Returns the actual keys for a given mapped command
      """
      try:
          return list(KEY_BINDINGS[command]["keys"])
      except KeyError as exception:
          raise InvalidCommand(command)
  
  
  def primary_key_for_command(command: str) -> str:
      """
      Primary Key is the key that will be displayed eg. in the UI
      """
      return keys_for_command(command).pop(0)
  
  
  def commands_for_random_tips() -> List[KeyBinding]:
      """
      Return list of commands which may be displayed as a random tip
      """
      return [
          key_binding
          for key_binding in KEY_BINDINGS.values()
          if not key_binding.get("excluded_from_random_tips", False)
      ]
  