# frozen_string_literal: true
#
# = net/imap.rb
#
# Copyright (C) 2000  Shugo Maeda <shugo@ruby-lang.org>
#
# This library is distributed under the terms of the Ruby license.
# You can freely distribute/modify this library.
#
# Documentation: Shugo Maeda, with RDoc conversion and overview by William
# Webber.
#
# See Net::IMAP for documentation.
#

require "socket"
require "monitor"
require 'net/protocol'
begin
  require "openssl"
rescue LoadError
end

module Net

  # Net::IMAP implements Internet Message Access Protocol (\IMAP) client
  # functionality.  The protocol is described
  # in {IMAP4rev1 [RFC3501]}[https://www.rfc-editor.org/rfc/rfc3501]
  # and {IMAP4rev2 [RFC9051]}[https://www.rfc-editor.org/rfc/rfc9051].
  #
  # == \IMAP Overview
  #
  # An \IMAP client connects to a server, and then authenticates
  # itself using either #authenticate or #login.  Having
  # authenticated itself, there is a range of commands
  # available to it.  Most work with mailboxes, which may be
  # arranged in an hierarchical namespace, and each of which
  # contains zero or more messages.  How this is implemented on
  # the server is implementation-dependent; on a UNIX server, it
  # will frequently be implemented as files in mailbox format
  # within a hierarchy of directories.
  #
  # To work on the messages within a mailbox, the client must
  # first select that mailbox, using either #select or #examine
  # (for read-only access).  Once the client has successfully
  # selected a mailbox, they enter the +selected+ state, and that
  # mailbox becomes the _current_ mailbox, on which mail-item
  # related commands implicitly operate.
  #
  # === Connection state
  #
  # Once an IMAP connection is established, the connection is in one of four
  # states: <tt>not authenticated</tt>, +authenticated+, +selected+, and
  # +logout+.  Most commands are valid only in certain states.
  #
  # See #connection_state.
  #
  # === Sequence numbers and UIDs
  #
  # Messages have two sorts of identifiers: message sequence
  # numbers and UIDs.
  #
  # Message sequence numbers number messages within a mailbox
  # from 1 up to the number of items in the mailbox.  If a new
  # message arrives during a session, it receives a sequence
  # number equal to the new size of the mailbox.  If messages
  # are expunged from the mailbox, remaining messages have their
  # sequence numbers "shuffled down" to fill the gaps.
  #
  # To avoid sequence number race conditions, servers must not expunge messages
  # when no command is in progress, nor when responding to #fetch, #store, or
  # #search.  Expunges _may_ be sent during any other command, including
  # #uid_fetch, #uid_store, and #uid_search.  The #noop and #idle commands are
  # both useful for this side-effect: they allow the server to send all mailbox
  # updates, including expunges.
  #
  # UIDs, on the other hand, are permanently guaranteed not to
  # identify another message within the same mailbox, even if
  # the existing message is deleted.  UIDs are required to
  # be assigned in ascending (but not necessarily sequential)
  # order within a mailbox; this means that if a non-IMAP client
  # rearranges the order of mail items within a mailbox, the
  # UIDs have to be reassigned.  An \IMAP client thus cannot
  # rearrange message orders.
  #
  # === Examples of Usage
  #
  # ==== List sender and subject of all recent messages in the default mailbox
  #
  #   imap = Net::IMAP.new('mail.example.com')
  #   imap.authenticate('PLAIN', 'joe_user', 'joes_password')
  #   imap.examine('INBOX')
  #   imap.search(["RECENT"]).each do |message_id|
  #     envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
  #     puts "#{envelope.from[0].name}: \t#{envelope.subject}"
  #   end
  #
  # ==== Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
  #
  #   imap = Net::IMAP.new('mail.example.com')
  #   imap.authenticate('PLAIN', 'joe_user', 'joes_password')
  #   imap.select('Mail/sent-mail')
  #   if not imap.list('Mail/', 'sent-apr03')
  #     imap.create('Mail/sent-apr03')
  #   end
  #   imap.search(["BEFORE", "30-Apr-2003", "SINCE", "1-Apr-2003"]).each do |message_id|
  #     imap.copy(message_id, "Mail/sent-apr03")
  #     imap.store(message_id, "+FLAGS", [:Deleted])
  #   end
  #   imap.expunge
  #
  # == Capabilities
  #
  # Most Net::IMAP methods do not _currently_ modify their behaviour according
  # to the server's advertised #capabilities.  Users of this class must check
  # that the server is capable of extension commands or command arguments before
  # sending them.  Special care should be taken to follow the #capabilities
  # requirements for #starttls, #login, and #authenticate.
  #
  # See #capable?, #auth_capable?, #capabilities, #auth_mechanisms to discover
  # server capabilities.  For relevant capability requirements, see the
  # documentation on each \IMAP command.
  #
  #   imap = Net::IMAP.new("mail.example.com")
  #   imap.capable?(:IMAP4rev1) or raise "Not an IMAP4rev1 server"
  #   imap.capable?(:starttls)  or raise "Cannot start TLS"
  #   imap.starttls
  #
  #   if imap.auth_capable?("PLAIN")
  #     imap.authenticate "PLAIN", username, password
  #   elsif !imap.capability?("LOGINDISABLED")
  #     imap.login username, password
  #   else
  #     raise "No acceptable authentication mechanisms"
  #   end
  #
  #   # Support for "UTF8=ACCEPT" implies support for "ENABLE"
  #   imap.enable :utf8 if imap.capable?("UTF8=ACCEPT")
  #
  #   namespaces  = imap.namespace if imap.capable?(:namespace)
  #   mbox_prefix = namespaces&.personal&.first&.prefix || ""
  #   mbox_delim  = namespaces&.personal&.first&.delim  || "/"
  #   mbox_path   = prefix + %w[path to my mailbox].join(delim)
  #   imap.create mbox_path
  #
  # === Basic IMAP4rev1 capabilities
  #
  # IMAP4rev1 servers must advertise +IMAP4rev1+ in their capabilities list.
  # IMAP4rev1 servers must _implement_ the +STARTTLS+, <tt>AUTH=PLAIN</tt>,
  # and +LOGINDISABLED+ capabilities.  See #starttls, #login, and #authenticate
  # for the implications of these capabilities.
  #
  # === Caching +CAPABILITY+ responses
  #
  # Net::IMAP automatically stores and discards capability data according to the
  # the requirements and recommendations in
  # {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1],
  # {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and
  # {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1].
  # Use #capable?, #auth_capable?, or #capabilities to use this cache and avoid
  # sending the #capability command unnecessarily.
  #
  # The server may advertise its initial capabilities using the +CAPABILITY+
  # ResponseCode in a +PREAUTH+ or +OK+ #greeting.  When TLS has started
  # (#starttls) and after authentication (#login or #authenticate), the server's
  # capabilities may change and cached capabilities are discarded.  The server
  # may send updated capabilities with an +OK+ TaggedResponse to #login or
  # #authenticate, and these will be cached by Net::IMAP.  But the
  # TaggedResponse to #starttls MUST be ignored--it is sent before TLS starts
  # and is unprotected.
  #
  # When storing capability values to variables, be careful that they are
  # discarded or reset appropriately, especially following #starttls.
  #
  # === Using IMAP4rev1 extensions
  #
  # See the {IANA IMAP4 capabilities
  # registry}[http://www.iana.org/assignments/imap4-capabilities] for a list of
  # all standard capabilities, and their reference RFCs.
  #
  # IMAP4rev1 servers must not activate behavior that is incompatible with the
  # base specification until an explicit client action invokes a capability,
  # e.g. sending a command or command argument specific to that capability.
  # Servers may send data with backward compatible behavior, such as response
  # codes or mailbox attributes, at any time without client action.
  #
  # Invoking capabilities which are unknown to Net::IMAP may cause unexpected
  # behavior and errors.  For example, ResponseParseError is raised when
  # unknown response syntax is received.  Invoking commands or command
  # parameters that are unsupported by the server may raise NoResponseError,
  # BadResponseError, or cause other unexpected behavior.
  #
  # Some capabilities must be explicitly activated using the #enable command.
  # See #enable for details.
  #
  # == Thread Safety
  #
  # Net::IMAP supports concurrent threads. For example,
  #
  #   imap = Net::IMAP.new("imap.foo.net", "imap2")
  #   imap.authenticate("scram-md5", "bar", "password")
  #   imap.select("inbox")
  #   fetch_thread = Thread.start { imap.fetch(1..-1, "UID") }
  #   search_result = imap.search(["BODY", "hello"])
  #   fetch_result = fetch_thread.value
  #   imap.disconnect
  #
  # This script invokes the FETCH command and the SEARCH command concurrently.
  #
  # When running multiple commands, care must be taken to avoid ambiguity.  For
  # example, SEARCH responses are ambiguous about which command they are
  # responding to, so search commands should not run simultaneously, unless the
  # server supports +ESEARCH+ {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731] or
  # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051].  See {RFC9051
  # §5.5}[https://www.rfc-editor.org/rfc/rfc9051.html#section-5.5] for
  # other examples of command sequences which should not be pipelined.
  #
  # == Unbounded memory use
  #
  # Net::IMAP reads server responses in a separate receiver thread per client.
  # Unhandled response data is saved to #responses, and response_handlers run
  # inside the receiver thread.  See the list of methods for {handling server
  # responses}[rdoc-ref:Net::IMAP@Handling+server+responses], below.
  #
  # Because the receiver thread continuously reads and saves new responses, some
  # scenarios must be careful to avoid unbounded memory use:
  #
  # * Commands such as #list or #fetch can have an enormous number of responses.
  # * Commands such as #fetch can result in an enormous size per response.
  # * Long-lived connections will gradually accumulate unsolicited server
  #   responses, especially +EXISTS+, +FETCH+, and +EXPUNGE+ responses.
  # * A buggy or untrusted server could send inappropriate responses, which
  #   could be very numerous, very large, and very rapid.
  #
  # Use paginated or limited versions of commands whenever possible.
  #
  # Use Config#max_response_size to impose a limit on incoming server responses
  # as they are being read.  <em>This is especially important for untrusted
  # servers.</em>
  #
  # Use #add_response_handler to handle responses after each one is received.
  # Use the +response_handlers+ argument to ::new to assign response handlers
  # before the receiver thread is started.  Use #extract_responses,
  # #clear_responses, or #responses (with a block) to prune responses.
  #
  # == Errors
  #
  # An \IMAP server can send three different types of responses to indicate
  # failure:
  #
  # NO:: the attempted command could not be successfully completed.  For
  #      instance, the username/password used for logging in are incorrect;
  #      the selected mailbox does not exist; etc.
  #
  # BAD:: the request from the client does not follow the server's
  #       understanding of the \IMAP protocol.  This includes attempting
  #       commands from the wrong client state; for instance, attempting
  #       to perform a SEARCH command without having SELECTed a current
  #       mailbox.  It can also signal an internal server
  #       failure (such as a disk crash) has occurred.
  #
  # BYE:: the server is saying goodbye.  This can be part of a normal
  #       logout sequence, and can be used as part of a login sequence
  #       to indicate that the server is (for some reason) unwilling
  #       to accept your connection.  As a response to any other command,
  #       it indicates either that the server is shutting down, or that
  #       the server is timing out the client connection due to inactivity.
  #
  # These three error response are represented by the errors
  # Net::IMAP::NoResponseError, Net::IMAP::BadResponseError, and
  # Net::IMAP::ByeResponseError, all of which are subclasses of
  # Net::IMAP::ResponseError.  Essentially, all methods that involve
  # sending a request to the server can generate one of these errors.
  # Only the most pertinent instances have been documented below.
  #
  # Because the IMAP class uses Sockets for communication, its methods
  # are also susceptible to the various errors that can occur when
  # working with sockets.  These are generally represented as
  # Errno errors.  For instance, any method that involves sending a
  # request to the server and/or receiving a response from it could
  # raise an Errno::EPIPE error if the network connection unexpectedly
  # goes down.  See the socket(7), ip(7), tcp(7), socket(2), connect(2),
  # and associated man pages.
  #
  # Finally, a Net::IMAP::DataFormatError is thrown if low-level data
  # is found to be in an incorrect format (for instance, when converting
  # between UTF-8 and UTF-16), and Net::IMAP::ResponseParseError is
  # thrown if a server response is non-parseable.
  #
  # == What's here?
  #
  # * {Connection control}[rdoc-ref:Net::IMAP@Connection+control+methods]
  # * {Server capabilities}[rdoc-ref:Net::IMAP@Server+capabilities]
  # * {Handling server responses}[rdoc-ref:Net::IMAP@Handling+server+responses]
  # * {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]
  #   * {for any state}[rdoc-ref:Net::IMAP@Any+state]
  #   * {for the "not authenticated" state}[rdoc-ref:Net::IMAP@Not+Authenticated+state]
  #   * {for the "authenticated" state}[rdoc-ref:Net::IMAP@Authenticated+state]
  #   * {for the "selected" state}[rdoc-ref:Net::IMAP@Selected+state]
  #   * {for the "logout" state}[rdoc-ref:Net::IMAP@Logout+state]
  # * {IMAP extension support}[rdoc-ref:Net::IMAP@IMAP+extension+support]
  #
  # === Connection control methods
  #
  # - Net::IMAP.new: Creates a new \IMAP client which connects immediately and
  #   waits for a successful server greeting before the method returns.
  # - #connection_state: Returns the connection state.
  # - #starttls: Asks the server to upgrade a clear-text connection to use TLS.
  # - #logout: Tells the server to end the session.  Enters the +logout+ state.
  # - #disconnect: Disconnects the connection (without sending #logout first).
  # - #disconnected?: True if the connection has been closed.
  #
  # === Server capabilities
  #
  # - #capable?: Returns whether the server supports a given capability.
  # - #capabilities: Returns the server's capabilities as an array of strings.
  # - #auth_capable?: Returns whether the server advertises support for a given
  #   SASL mechanism, for use with #authenticate.
  # - #auth_mechanisms: Returns the #authenticate SASL mechanisms which
  #   the server claims to support as an array of strings.
  # - #clear_cached_capabilities: Clears cached capabilities.
  #
  #   <em>The capabilities cache is automatically cleared after completing
  #   #starttls, #login, or #authenticate.</em>
  # - #capability: Sends the +CAPABILITY+ command and returns the #capabilities.
  #
  #   <em>In general, #capable? should be used rather than explicitly sending a
  #   +CAPABILITY+ command to the server.</em>
  #
  # === Handling server responses
  #
  # - #greeting: The server's initial untagged response, which can indicate a
  #   pre-authenticated connection.
  # - #responses: Yields unhandled UntaggedResponse#data and <em>non-+nil+</em>
  #   ResponseCode#data.
  # - #extract_responses: Removes and returns the responses for which the block
  #   returns a true value.
  # - #clear_responses: Deletes unhandled data from #responses and returns it.
  # - #add_response_handler: Add a block to be called inside the receiver thread
  #   with every server response.
  # - #response_handlers: Returns the list of response handlers.
  # - #remove_response_handler: Remove a previously added response handler.
  #
  # === Core \IMAP commands
  #
  # The following commands are defined either by
  # the [IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501]] base specification, or
  # by one of the following extensions:
  # [IDLE[https://www.rfc-editor.org/rfc/rfc2177]],
  # [NAMESPACE[https://www.rfc-editor.org/rfc/rfc2342]],
  # [UNSELECT[https://www.rfc-editor.org/rfc/rfc3691]],
  # [ENABLE[https://www.rfc-editor.org/rfc/rfc5161]],
  # [MOVE[https://www.rfc-editor.org/rfc/rfc6851]].
  # These extensions are widely supported by modern IMAP4rev1 servers and have
  # all been integrated into [IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051]].
  # <em>*NOTE:* Net::IMAP doesn't support IMAP4rev2 yet.</em>
  #
  # ==== Any state
  #
  # - #capability: Returns the server's capabilities as an array of strings.
  #
  #   <em>In general,</em> #capable? <em>should be used rather than explicitly
  #   sending a +CAPABILITY+ command to the server.</em>
  # - #noop: Allows the server to send unsolicited untagged #responses.
  # - #logout: Tells the server to end the session. Enters the +logout+ state.
  #
  # ==== Not Authenticated state
  #
  # In addition to the commands for any state, the following commands are valid
  # in the +not_authenticated+ state:
  #
  # - #starttls: Upgrades a clear-text connection to use TLS.
  #
  #   <em>Requires the +STARTTLS+ capability.</em>
  # - #authenticate: Identifies the client to the server using the given
  #   {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
  #   and credentials.  Enters the +authenticated+ state.
  #
  #   <em>The server should list <tt>"AUTH=#{mechanism}"</tt> capabilities for
  #   supported mechanisms.</em>
  # - #login: Identifies the client to the server using a plain text password.
  #   Using #authenticate is preferred.  Enters the +authenticated+ state.
  #
  #   <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
  #
  # ==== Authenticated state
  #
  # In addition to the commands for any state, the following commands are valid
  # in the +authenticated+ state:
  #
  # - #enable: Enables backwards incompatible server extensions.
  #   <em>Requires the +ENABLE+ or +IMAP4rev2+ capability.</em>
  # - #select:  Open a mailbox and enter the +selected+ state.
  # - #examine: Open a mailbox read-only, and enter the +selected+ state.
  # - #create: Creates a new mailbox.
  # - #delete: Permanently remove a mailbox.
  # - #rename: Change the name of a mailbox.
  # - #subscribe: Adds a mailbox to the "subscribed" set.
  # - #unsubscribe: Removes a mailbox from the "subscribed" set.
  # - #list: Returns names and attributes of mailboxes matching a given pattern.
  # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters.
  #   <em>Requires the +NAMESPACE+ or +IMAP4rev2+ capability.</em>
  # - #status: Returns mailbox information, e.g. message count, unseen message
  #   count, +UIDVALIDITY+ and +UIDNEXT+.
  # - #append: Appends a message to the end of a mailbox.
  # - #idle: Allows the server to send updates to the client, without the client
  #   needing to poll using #noop.
  #   <em>Requires the +IDLE+ or +IMAP4rev2+ capability.</em>
  # - *Obsolete* #lsub: <em>Replaced by <tt>LIST-EXTENDED</tt> and removed from
  #   +IMAP4rev2+.</em>  Lists mailboxes in the "subscribed" set.
  #
  #   <em>*Note:* Net::IMAP hasn't implemented <tt>LIST-EXTENDED</tt> yet.</em>
  #
  # ==== Selected state
  #
  # In addition to the commands for any state and the +authenticated+
  # commands, the following commands are valid in the +selected+ state:
  #
  # - #close: Closes the mailbox and returns to the +authenticated+ state,
  #   expunging deleted messages, unless the mailbox was opened as read-only.
  # - #unselect: Closes the mailbox and returns to the +authenticated+ state,
  #   without expunging any messages.
  #   <em>Requires the +UNSELECT+ or +IMAP4rev2+ capability.</em>
  # - #expunge: Permanently removes messages which have the Deleted flag set.
  # - #uid_expunge: Restricts expunge to only remove the specified UIDs.
  #   <em>Requires the +UIDPLUS+ or +IMAP4rev2+ capability.</em>
  # - #search, #uid_search: Returns sequence numbers or UIDs of messages that
  #   match the given searching criteria.
  # - #fetch, #uid_fetch: Returns data associated with a set of messages,
  #   specified by sequence number or UID.
  # - #store, #uid_store: Alters a message's flags.
  # - #copy, #uid_copy: Copies the specified messages to the end of the
  #   specified destination mailbox.
  # - #move, #uid_move: Moves the specified messages to the end of the
  #   specified destination mailbox, expunging them from the current mailbox.
  #   <em>Requires the +MOVE+ or +IMAP4rev2+ capability.</em>
  # - #check: <em>*Obsolete:* removed from +IMAP4rev2+.</em>
  #   Can be replaced with #noop or #idle.
  #
  # ==== Logout state
  #
  # No \IMAP commands are valid in the +logout+ state.  If the socket is still
  # open, Net::IMAP will close it after receiving server confirmation.
  # Exceptions will be raised by \IMAP commands that have already started and
  # are waiting for a response, as well as any that are called after logout.
  #
  # === \IMAP extension support
  #
  # ==== RFC9051: +IMAP4rev2+
  #
  # Although IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] is not supported
  # yet, Net::IMAP supports several extensions that have been folded into it:
  # +ENABLE+, +IDLE+, +MOVE+, +NAMESPACE+, +SASL-IR+, +UIDPLUS+, +UNSELECT+,
  # <tt>STATUS=SIZE</tt>, and the fetch side of +BINARY+.
  # Commands for these extensions are listed with the {Core IMAP
  # commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above.
  #
  # >>>
  #   <em>The following are folded into +IMAP4rev2+ but are currently
  #   unsupported or incompletely supported by</em> Net::IMAP<em>: RFC4466
  #   extensions, +SEARCHRES+, +LIST-EXTENDED+, +LIST-STATUS+,
  #   +LITERAL-+, and +SPECIAL-USE+.</em>
  #
  # ==== RFC2087: +QUOTA+
  # - #getquota: returns the resource usage and limits for a quota root
  # - #getquotaroot: returns the list of quota roots for a mailbox, as well as
  #   their resource usage and limits.
  # - #setquota: sets the resource limits for a given quota root.
  #
  # ==== RFC2177: +IDLE+
  # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
  # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
  # - #idle: Allows the server to send updates to the client, without the client
  #   needing to poll using #noop.
  #
  # ==== RFC2342: +NAMESPACE+
  # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
  # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
  # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters.
  #
  # ==== RFC2971: +ID+
  # - #id: exchanges client and server implementation information.
  #
  # ==== RFC3516: +BINARY+
  # The fetch side of +BINARY+ has been folded into
  # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051].
  # - Updates #fetch and #uid_fetch with the +BINARY+, +BINARY.PEEK+, and
  #   +BINARY.SIZE+ items.  See FetchData#binary and FetchData#binary_size.
  #
  # >>>
  #   *NOTE:* The binary extension the #append command is _not_ supported yet.
  #
  # ==== RFC3691: +UNSELECT+
  # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
  # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
  # - #unselect: Closes the mailbox and returns to the +authenticated+ state,
  #   without expunging any messages.
  #
  # ==== RFC4314: +ACL+
  # - #getacl: lists the authenticated user's access rights to a mailbox.
  # - #setacl: sets the access rights for a user on a mailbox
  # >>>
  #   *NOTE:* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet.
  #
  # ==== RFC4315: +UIDPLUS+
  # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
  # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
  # - #uid_expunge: Restricts #expunge to only remove the specified UIDs.
  # - Updates #select, #examine with the +UIDNOTSTICKY+ ResponseCode
  # - Updates #append with the +APPENDUID+ ResponseCode
  # - Updates #copy, #move with the +COPYUID+ ResponseCode
  #
  # ==== RFC4731: +ESEARCH+
  # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051].
  # - Updates #search, #uid_search with +return+ options and ESearchResult.
  #
  # ==== RFC4959: +SASL-IR+
  # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051].
  # - Updates #authenticate with the option to send an initial response.
  #
  # ==== RFC5161: +ENABLE+
  # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
  # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
  # - #enable: Enables backwards incompatible server extensions.
  #
  # ==== RFC5256: +SORT+
  # - #sort, #uid_sort: An alternate version of #search or #uid_search which
  #   sorts the results by specified keys.
  # ==== RFC5256: +THREAD+
  # - #thread, #uid_thread: An alternate version of #search or #uid_search,
  #   which arranges the results into ordered groups or threads according to a
  #   chosen algorithm.
  #
  # ==== +X-GM-EXT-1+
  # +X-GM-EXT-1+ is a non-standard Gmail extension.  See {Google's
  # documentation}[https://developers.google.com/gmail/imap/imap-extensions].
  # - Updates #fetch and #uid_fetch with support for +X-GM-MSGID+ (unique
  #   message ID), +X-GM-THRID+ (thread ID), and +X-GM-LABELS+ (Gmail labels).
  # - Updates #search with the +X-GM-RAW+ search attribute.
  # - #xlist: replaced by +SPECIAL-USE+ attributes in #list responses.
  #
  # *NOTE:* The +OBJECTID+ extension should replace +X-GM-MSGID+ and
  # +X-GM-THRID+, but Gmail does not support it (as of 2023-11-10).
  #
  # ==== RFC6851: +MOVE+
  # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
  # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
  # - #move, #uid_move: Moves the specified messages to the end of the
  #   specified destination mailbox, expunging them from the current mailbox.
  #
  # ==== RFC6855: <tt>UTF8=ACCEPT</tt>, <tt>UTF8=ONLY</tt>
  #
  # - See #enable for information about support for UTF-8 string encoding.
  #
  # ==== RFC7162: +CONDSTORE+
  #
  # - Updates #enable with +CONDSTORE+ parameter.  +CONDSTORE+ will also be
  #   enabled by using any of the extension's command parameters, listed below.
  # - Updates #status with the +HIGHESTMODSEQ+ status attribute.
  # - Updates #select and #examine with the +condstore+ modifier, and adds
  #   either a +HIGHESTMODSEQ+ or +NOMODSEQ+ ResponseCode to the responses.
  # - Updates #search, #uid_search, #sort, and #uid_sort with the +MODSEQ+
  #   search criterion, and adds SearchResult#modseq to the search response.
  # - Updates #thread and #uid_thread with the +MODSEQ+ search criterion
  #   <em>(but thread responses are unchanged)</em>.
  # - Updates #fetch and #uid_fetch with the +changedsince+ modifier and
  #   +MODSEQ+ FetchData attribute.
  # - Updates #store and #uid_store with the +unchangedsince+ modifier and adds
  #   the +MODIFIED+ ResponseCode to the tagged response.
  #
  # ==== RFC8438: <tt>STATUS=SIZE</tt>
  # - Updates #status with the +SIZE+ status attribute.
  #
  # ==== RFC8474: +OBJECTID+
  # - Adds +MAILBOXID+ ResponseCode to #create tagged response.
  # - Adds +MAILBOXID+ ResponseCode to #select and #examine untagged response.
  # - Updates #fetch and #uid_fetch with the +EMAILID+ and +THREADID+ items.
  #   See FetchData#emailid and FetchData#emailid.
  # - Updates #status with support for the +MAILBOXID+ status attribute.
  #
  # ==== RFC9394: +PARTIAL+
  # - Updates #search, #uid_search with the +PARTIAL+ return option which adds
  #   ESearchResult#partial return data.
  # - Updates #uid_fetch with the +partial+ modifier.
  #
  # ==== RFC9586: +UIDONLY+
  # - Updates #enable with +UIDONLY+ parameter.
  # - Updates #uid_fetch and #uid_store to return +UIDFETCH+ response.
  # - Updates #expunge and #uid_expunge to return +VANISHED+ response.
  # - Prohibits use of message sequence numbers in responses or requests.
  #
  # == References
  #
  # [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]::
  #   Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - \VERSION 4rev1",
  #   RFC 3501, DOI 10.17487/RFC3501, March 2003,
  #   <https://www.rfc-editor.org/info/rfc3501>.
  #
  # [IMAP-ABNF-EXT[https://www.rfc-editor.org/rfc/rfc4466.html]]::
  #   Melnikov, A. and C. Daboo, "Collected Extensions to IMAP4 ABNF",
  #   RFC 4466, DOI 10.17487/RFC4466, April 2006,
  #   <https://www.rfc-editor.org/info/rfc4466>.
  #
  #   <em>Note: Net::IMAP cannot parse the entire RFC4466 grammar yet.</em>
  #
  # [{IMAP4rev2}[https://www.rfc-editor.org/rfc/rfc9051.html]]::
  #   Melnikov, A., Ed., and B. Leiba, Ed., "Internet Message Access Protocol
  #   (\IMAP) - Version 4rev2", RFC 9051, DOI 10.17487/RFC9051, August 2021,
  #   <https://www.rfc-editor.org/info/rfc9051>.
  #
  #   <em>Note: Net::IMAP is not fully compatible with IMAP4rev2 yet.</em>
  #
  # [IMAP-IMPLEMENTATION[https://www.rfc-editor.org/info/rfc2683]]::
  #   Leiba, B., "IMAP4 Implementation Recommendations",
  #   RFC 2683, DOI 10.17487/RFC2683, September 1999,
  #   <https://www.rfc-editor.org/info/rfc2683>.
  #
  # [IMAP-MULTIACCESS[https://www.rfc-editor.org/info/rfc2180]]::
  #   Gahrns, M., "IMAP4 Multi-Accessed Mailbox Practice", RFC 2180, DOI
  #   10.17487/RFC2180, July 1997, <https://www.rfc-editor.org/info/rfc2180>.
  #
  # [UTF7[https://www.rfc-editor.org/rfc/rfc2152]]::
  #   Goldsmith, D. and M. Davis, "UTF-7 A Mail-Safe Transformation Format of
  #   Unicode", RFC 2152, DOI 10.17487/RFC2152, May 1997,
  #   <https://www.rfc-editor.org/info/rfc2152>.
  #
  # === Message envelope and body structure
  #
  # [RFC5322[https://www.rfc-editor.org/rfc/rfc5322]]::
  #   Resnick, P., Ed., "Internet Message Format",
  #   RFC 5322, DOI 10.17487/RFC5322, October 2008,
  #   <https://www.rfc-editor.org/info/rfc5322>.
  #
  #   <em>Note: obsoletes</em>
  #   RFC-2822[https://www.rfc-editor.org/rfc/rfc2822]<em> (April 2001) and</em>
  #   RFC-822[https://www.rfc-editor.org/rfc/rfc822]<em> (August 1982).</em>
  #
  # [CHARSET[https://www.rfc-editor.org/rfc/rfc2978]]::
  #   Freed, N. and J. Postel, "IANA Charset Registration Procedures", BCP 19,
  #   RFC 2978, DOI 10.17487/RFC2978, October 2000,
  #   <https://www.rfc-editor.org/info/rfc2978>.
  #
  # [DISPOSITION[https://www.rfc-editor.org/rfc/rfc2183]]::
  #    Troost, R., Dorner, S., and K. Moore, Ed., "Communicating Presentation
  #    Information in Internet Messages: The Content-Disposition Header
  #    Field", RFC 2183, DOI 10.17487/RFC2183, August 1997,
  #    <https://www.rfc-editor.org/info/rfc2183>.
  #
  # [MIME-IMB[https://www.rfc-editor.org/rfc/rfc2045]]::
  #    Freed, N. and N. Borenstein, "Multipurpose Internet Mail Extensions
  #    (MIME) Part One: Format of Internet Message Bodies",
  #    RFC 2045, DOI 10.17487/RFC2045, November 1996,
  #    <https://www.rfc-editor.org/info/rfc2045>.
  #
  # [MIME-IMT[https://www.rfc-editor.org/rfc/rfc2046]]::
  #    Freed, N. and N. Borenstein, "Multipurpose Internet Mail Extensions
  #    (MIME) Part Two: Media Types", RFC 2046, DOI 10.17487/RFC2046,
  #    November 1996, <https://www.rfc-editor.org/info/rfc2046>.
  #
  # [MIME-HDRS[https://www.rfc-editor.org/rfc/rfc2047]]::
  #    Moore, K., "MIME (Multipurpose Internet Mail Extensions) Part Three:
  #    Message Header Extensions for Non-ASCII Text",
  #    RFC 2047, DOI 10.17487/RFC2047, November 1996,
  #    <https://www.rfc-editor.org/info/rfc2047>.
  #
  # [RFC2231[https://www.rfc-editor.org/rfc/rfc2231]]::
  #    Freed, N. and K. Moore, "MIME Parameter Value and Encoded Word
  #    Extensions: Character Sets, Languages, and Continuations",
  #    RFC 2231, DOI 10.17487/RFC2231, November 1997,
  #    <https://www.rfc-editor.org/info/rfc2231>.
  #
  # [I18n-HDRS[https://www.rfc-editor.org/rfc/rfc6532]]::
  #    Yang, A., Steele, S., and N. Freed, "Internationalized Email Headers",
  #    RFC 6532, DOI 10.17487/RFC6532, February 2012,
  #    <https://www.rfc-editor.org/info/rfc6532>.
  #
  # [LANGUAGE-TAGS[https://www.rfc-editor.org/info/rfc3282]]::
  #    Alvestrand, H., "Content Language Headers",
  #    RFC 3282, DOI 10.17487/RFC3282, May 2002,
  #    <https://www.rfc-editor.org/info/rfc3282>.
  #
  # [LOCATION[https://www.rfc-editor.org/info/rfc2557]]::
  #    Palme, J., Hopmann, A., and N. Shelness, "MIME Encapsulation of
  #    Aggregate Documents, such as HTML (MHTML)",
  #    RFC 2557, DOI 10.17487/RFC2557, March 1999,
  #    <https://www.rfc-editor.org/info/rfc2557>.
  #
  # [MD5[https://www.rfc-editor.org/rfc/rfc1864]]::
  #    Myers, J. and M. Rose, "The Content-MD5 Header Field",
  #    RFC 1864, DOI 10.17487/RFC1864, October 1995,
  #    <https://www.rfc-editor.org/info/rfc1864>.
  #
  # [RFC3503[https://www.rfc-editor.org/rfc/rfc3503]]::
  #    Melnikov, A., "Message Disposition Notification (MDN)
  #    profile for Internet Message Access Protocol (IMAP)",
  #    RFC 3503, DOI 10.17487/RFC3503, March 2003,
  #    <https://www.rfc-editor.org/info/rfc3503>.
  #
  # === \IMAP Extensions
  #
  # [QUOTA[https://www.rfc-editor.org/rfc/rfc9208]]::
  #   Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
  #   March 2022, <https://www.rfc-editor.org/info/rfc9208>.
  #
  #   <em>Note: obsoletes</em>
  #   RFC-2087[https://www.rfc-editor.org/rfc/rfc2087]<em> (January 1997)</em>.
  #   <em>Net::IMAP does not fully support the RFC9208 updates yet.</em>
  # [IDLE[https://www.rfc-editor.org/rfc/rfc2177]]::
  #   Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177,
  #   June 1997, <https://www.rfc-editor.org/info/rfc2177>.
  # [NAMESPACE[https://www.rfc-editor.org/rfc/rfc2342]]::
  #   Gahrns, M. and C. Newman, "IMAP4 Namespace", RFC 2342,
  #   DOI 10.17487/RFC2342, May 1998, <https://www.rfc-editor.org/info/rfc2342>.
  # [ID[https://www.rfc-editor.org/rfc/rfc2971]]::
  #   Showalter, T., "IMAP4 ID extension", RFC 2971, DOI 10.17487/RFC2971,
  #   October 2000, <https://www.rfc-editor.org/info/rfc2971>.
  # [BINARY[https://www.rfc-editor.org/rfc/rfc3516]]::
  #   Nerenberg, L., "IMAP4 Binary Content Extension", RFC 3516,
  #   DOI 10.17487/RFC3516, April 2003,
  #   <https://www.rfc-editor.org/info/rfc3516>.
  # [ACL[https://www.rfc-editor.org/rfc/rfc4314]]::
  #   Melnikov, A., "IMAP4 Access Control List (ACL) Extension", RFC 4314,
  #   DOI 10.17487/RFC4314, December 2005,
  #   <https://www.rfc-editor.org/info/rfc4314>.
  # [UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]]::
  #   Crispin, M., "Internet Message Access Protocol (\IMAP) - UIDPLUS
  #   extension", RFC 4315, DOI 10.17487/RFC4315, December 2005,
  #   <https://www.rfc-editor.org/info/rfc4315>.
  # [SORT[https://www.rfc-editor.org/rfc/rfc5256]]::
  #   Crispin, M. and K. Murchison, "Internet Message Access Protocol - SORT and
  #   THREAD Extensions", RFC 5256, DOI 10.17487/RFC5256, June 2008,
  #   <https://www.rfc-editor.org/info/rfc5256>.
  # [THREAD[https://www.rfc-editor.org/rfc/rfc5256]]::
  #   Crispin, M. and K. Murchison, "Internet Message Access Protocol - SORT and
  #   THREAD Extensions", RFC 5256, DOI 10.17487/RFC5256, June 2008,
  #   <https://www.rfc-editor.org/info/rfc5256>.
  # [RFC5530[https://www.rfc-editor.org/rfc/rfc5530.html]]::
  #   Gulbrandsen, A., "IMAP Response Codes", RFC 5530, DOI 10.17487/RFC5530,
  #   May 2009, <https://www.rfc-editor.org/info/rfc5530>.
  # [MOVE[https://www.rfc-editor.org/rfc/rfc6851]]::
  #   Gulbrandsen, A. and N. Freed, Ed., "Internet Message Access Protocol
  #   (\IMAP) - MOVE Extension", RFC 6851, DOI 10.17487/RFC6851, January 2013,
  #   <https://www.rfc-editor.org/info/rfc6851>.
  # [UTF8=ACCEPT[https://www.rfc-editor.org/rfc/rfc6855]]::
  # [UTF8=ONLY[https://www.rfc-editor.org/rfc/rfc6855]]::
  #   Resnick, P., Ed., Newman, C., Ed., and S. Shen, Ed.,
  #   "IMAP Support for UTF-8", RFC 6855, DOI 10.17487/RFC6855, March 2013,
  #   <https://www.rfc-editor.org/info/rfc6855>.
  # [CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162]]::
  # [QRESYNC[https://www.rfc-editor.org/rfc/rfc7162]]::
  #   Melnikov, A. and D. Cridland, "IMAP Extensions: Quick Flag Changes
  #   Resynchronization (CONDSTORE) and Quick Mailbox Resynchronization
  #   (QRESYNC)", RFC 7162, DOI 10.17487/RFC7162, May 2014,
  #   <https://www.rfc-editor.org/info/rfc7162>.
  # [OBJECTID[https://www.rfc-editor.org/rfc/rfc8474]]::
  #   Gondwana, B., Ed., "IMAP Extension for Object Identifiers",
  #   RFC 8474, DOI 10.17487/RFC8474, September 2018,
  #   <https://www.rfc-editor.org/info/rfc8474>.
  # [PARTIAL[https://www.rfc-editor.org/info/rfc9394]]::
  #   Melnikov, A., Achuthan, A., Nagulakonda, V., and L. Alves,
  #   "IMAP PARTIAL Extension for Paged SEARCH and FETCH", RFC 9394,
  #   DOI 10.17487/RFC9394, June 2023,
  #   <https://www.rfc-editor.org/info/rfc9394>.
  # [UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.pdf]]::
  #   Melnikov, A., Achuthan, A., Nagulakonda, V., Singh, A., and L. Alves,
  #   "\IMAP Extension for Using and Returning Unique Identifiers (UIDs) Only",
  #   RFC 9586, DOI 10.17487/RFC9586, May 2024,
  #   <https://www.rfc-editor.org/info/rfc9586>.
  #
  # === IANA registries
  # * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
  # * {IMAP Response Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml]
  # * {IMAP Mailbox Name Attributes}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]
  # * {IMAP and JMAP Keywords}[https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml]
  # * {IMAP Threading Algorithms}[https://www.iana.org/assignments/imap-threading-algorithms/imap-threading-algorithms.xhtml]
  # * {SASL Mechanisms and SASL SCRAM Family Mechanisms}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
  # * {Service Name and Transport Protocol Port Number Registry}[https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml]:
  #   +imap+: tcp/143, +imaps+: tcp/993
  # * {GSSAPI/Kerberos/SASL Service Names}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml]:
  #   +imap+
  # * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
  # ==== For currently unsupported features:
  # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
  # * {LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
  # * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
  # * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml]
  # * {IMAP URLAUTH Access Identifiers and Prefixes}[https://www.iana.org/assignments/urlauth-access-ids/urlauth-access-ids.xhtml]
  # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
  #
  class IMAP < Protocol
    VERSION = "0.6.2"

    # Aliases for supported capabilities, to be used with the #enable command.
    ENABLE_ALIASES = {
      utf8:          "UTF8=ACCEPT",
      "UTF8=ONLY" => "UTF8=ACCEPT",
    }.freeze

    dir = File.expand_path("imap", __dir__)
    autoload :ConnectionState,        "#{dir}/connection_state"
    autoload :ResponseReader,         "#{dir}/response_reader"
    autoload :SASL,                   "#{dir}/sasl"
    autoload :SASLAdapter,            "#{dir}/sasl_adapter"
    autoload :SequenceSet,            "#{dir}/sequence_set"
    autoload :StringPrep,             "#{dir}/stringprep"

    include MonitorMixin

    # :call-seq:
    #   Net::IMAP::SequenceSet(set = nil) -> SequenceSet
    #
    # Coerces +set+ into a SequenceSet, using either SequenceSet.try_convert or
    # SequenceSet.new.
    #
    # * When +set+ is a SequenceSet, that same set is returned.
    # * When +set+ responds to +to_sequence_set+, +set.to_sequence_set+ is
    #   returned.
    # * Otherwise, returns the result from calling SequenceSet.new with +set+.
    #
    # Related: SequenceSet.try_convert, SequenceSet.new, SequenceSet::[]
    def self.SequenceSet(set = nil)
      SequenceSet.try_convert(set) || SequenceSet.new(set)
    end

    # Returns the global Config object
    def self.config; Config.global end

    # Returns the global debug mode.
    # Delegates to {Net::IMAP.config.debug}[rdoc-ref:Config#debug].
    def self.debug; config.debug end

    # Sets the global debug mode.
    # Delegates to {Net::IMAP.config.debug=}[rdoc-ref:Config#debug=].
    def self.debug=(val)
      config.debug = val
    end

    # The default port for IMAP connections, port 143
    def self.default_port
      return PORT
    end

    # The default port for IMAPS connections, port 993
    def self.default_tls_port
      return SSL_PORT
    end

    class << self
      alias default_imap_port default_port
      alias default_imaps_port default_tls_port
      alias default_ssl_port default_tls_port
    end

    # Returns the initial greeting sent by the server, an UntaggedResponse.
    attr_reader :greeting

    # The client configuration.  See Net::IMAP::Config.
    #
    # By default, the client's local configuration inherits from the global
    # Net::IMAP.config.
    attr_reader :config

    ##
    # :attr_reader: open_timeout
    # Seconds to wait until a connection is opened.  Also used by #starttls.
    # Delegates to {config.open_timeout}[rdoc-ref:Config#open_timeout].

    ##
    # :attr_reader: idle_response_timeout
    # Seconds to wait until an IDLE response is received.
    # Delegates to {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout].

    ##
    # :attr_accessor: max_response_size
    #
    # The maximum allowed server response size, in bytes.
    # Delegates to {config.max_response_size}[rdoc-ref:Config#max_response_size].

    # :stopdoc:
    def open_timeout;           config.open_timeout            end
    def idle_response_timeout;  config.idle_response_timeout   end
    def max_response_size;      config.max_response_size       end
    def max_response_size=(val) config.max_response_size = val end
    # :startdoc:

    # The hostname this client connected to
    attr_reader :host

    # The port this client connected to
    attr_reader :port

    # Returns the
    # {SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]
    # used by the SSLSocket when TLS is attempted, even when the TLS handshake
    # is unsuccessful.  The context object will be frozen.
    #
    # Returns +nil+ for a plaintext connection.
    attr_reader :ssl_ctx

    # Returns the parameters that were sent to #ssl_ctx
    # {set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params]
    # when the connection tries to use TLS (even when unsuccessful).
    #
    # Returns +false+ for a plaintext connection.
    attr_reader :ssl_ctx_params

    # Returns the current connection state.
    #
    # Once an IMAP connection is established, the connection is in one of four
    # states: +not_authenticated+, +authenticated+, +selected+, and +logout+.
    # Most commands are valid only in certain states.
    #
    # The connection state object responds to +to_sym+ and +name+ with the name
    # of the current connection state, as a Symbol or String.  Future versions
    # of +net-imap+ may store additional information on the state object.
    #
    # From {RFC9051}[https://www.rfc-editor.org/rfc/rfc9051#section-3]:
    #                    +----------------------+
    #                    |connection established|
    #                    +----------------------+
    #                               ||
    #                               \/
    #             +--------------------------------------+
    #             |          server greeting             |
    #             +--------------------------------------+
    #                       || (1)       || (2)        || (3)
    #                       \/           ||            ||
    #             +-----------------+    ||            ||
    #             |Not Authenticated|    ||            ||
    #             +-----------------+    ||            ||
    #              || (7)   || (4)       ||            ||
    #              ||       \/           \/            ||
    #              ||     +----------------+           ||
    #              ||     | Authenticated  |<=++       ||
    #              ||     +----------------+  ||       ||
    #              ||       || (7)   || (5)   || (6)   ||
    #              ||       ||       \/       ||       ||
    #              ||       ||    +--------+  ||       ||
    #              ||       ||    |Selected|==++       ||
    #              ||       ||    +--------+           ||
    #              ||       ||       || (7)            ||
    #              \/       \/       \/                \/
    #             +--------------------------------------+
    #             |               Logout                 |
    #             +--------------------------------------+
    #                               ||
    #                               \/
    #                 +-------------------------------+
    #                 |both sides close the connection|
    #                 +-------------------------------+
    #
    # >>>
    #   Legend for the above diagram:
    #
    #   1. connection without pre-authentication (+OK+ #greeting)
    #   2. pre-authenticated connection (+PREAUTH+ #greeting)
    #   3. rejected connection (+BYE+ #greeting)
    #   4. successful #login or #authenticate command
    #   5. successful #select or #examine command
    #   6. #close or #unselect command, unsolicited +CLOSED+ response code, or
    #      failed #select or #examine command
    #   7. #logout command, server shutdown, or connection closed
    #
    # Before the server greeting, the state is +not_authenticated+.
    # After the connection closes, the state remains +logout+.
    attr_reader :connection_state

    # Creates a new Net::IMAP object and connects it to the specified
    # +host+.
    #
    # ==== Options
    #
    # Accepts the following options:
    #
    # [port]
    #   Port number.  Defaults to 993 when +ssl+ is truthy, and 143 otherwise.
    #
    # [ssl]
    #   If +true+, the connection will use TLS with the default params set by
    #   {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params].
    #   If +ssl+ is a hash, it's passed to
    #   {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
    #   the keys are names of attribute assignment methods on
    #   SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].  For example:
    #
    #   [{ca_file}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#attribute-i-ca_file]]
    #     The path to a file containing a PEM-format CA certificate.
    #   [{ca_path}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#attribute-i-ca_path]]
    #     The path to a directory containing CA certificates in PEM format.
    #   [{min_version}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-min_version-3D]]
    #     Sets the lower bound on the supported SSL/TLS protocol version. Set to
    #     an +OpenSSL+ constant such as +OpenSSL::SSL::TLS1_2_VERSION+,
    #   [{verify_mode}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#attribute-i-verify_mode]]
    #     SSL session verification mode.  Valid modes include
    #     +OpenSSL::SSL::VERIFY_PEER+ and +OpenSSL::SSL::VERIFY_NONE+.
    #
    #   See {OpenSSL::SSL::SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html] for other valid SSL context params.
    #
    #   See DeprecatedClientOptions.new for deprecated SSL arguments.
    #
    # [response_handlers]
    #   A list of response handlers to be added before the receiver thread is
    #   started.  This ensures every server response is handled, including the
    #   #greeting.  Note that the greeting is handled in the current thread, but
    #   all other responses are handled in the receiver thread.
    #
    # [config]
    #   A Net::IMAP::Config object to use as the basis for #config.  By default,
    #   the global Net::IMAP.config is used.
    #
    #   >>>
    #     *NOTE:* +config+ does not set #config directly---it sets the _parent_
    #     config for inheritance.  Every client creates its own unique #config.
    #
    #   All other keyword arguments are forwarded to Net::IMAP::Config.new, to
    #   initialize the client's #config. For example:
    #
    #   [{open_timeout}[rdoc-ref:Config#open_timeout]]
    #     Seconds to wait until a connection is opened
    #   [{idle_response_timeout}[rdoc-ref:Config#idle_response_timeout]]
    #     Seconds to wait until an IDLE response is received
    #
    #   See Net::IMAP::Config for other valid options.
    #
    # ==== Examples
    #
    # Connect to cleartext port 143 at mail.example.com and receive the server greeting:
    #   imap = Net::IMAP.new('mail.example.com', ssl: false) # => #<Net::IMAP:0x00007f79b0872bd0>
    #   imap.port          => 143
    #   imap.tls_verified? => false
    #   imap.greeting      => name: ("OK" | "PREAUTH") => status
    #   status # => "OK"
    #   # The client is connected in the "Not Authenticated" state.
    #
    # Connect with TLS to port 993
    #   imap = Net::IMAP.new('mail.example.com', ssl: true) # => #<Net::IMAP:0x00007f79b0872bd0>
    #   imap.port          => 993
    #   imap.tls_verified? => true
    #   imap.greeting      => name: (/OK/i | /PREAUTH/i) => status
    #   case status
    #   in /OK/i
    #     # The client is connected in the "Not Authenticated" state.
    #     imap.authenticate("PLAIN", "joe_user", "joes_password")
    #   in /PREAUTH/i
    #     # The client is connected in the "Authenticated" state.
    #   end
    #
    # Connect with prior authentication, for example using an SSL certificate:
    #   ssl_ctx_params = {
    #     cert: OpenSSL::X509::Certificate.new(File.read("client.crt")),
    #     key:  OpenSSL::PKey::EC.new(File.read('client.key')),
    #     extra_chain_cert: [
    #       OpenSSL::X509::Certificate.new(File.read("intermediate.crt")),
    #     ],
    #   }
    #   imap = Net::IMAP.new('mail.example.com', ssl: ssl_ctx_params)
    #   imap.port          => 993
    #   imap.tls_verified? => true
    #   imap.greeting      => name: "PREAUTH"
    #   # The client is connected in the "Authenticated" state.
    #
    # ==== Exceptions
    #
    # The most common errors are:
    #
    # [Errno::ECONNREFUSED]
    #   Connection refused by +host+ or an intervening firewall.
    # [Errno::ETIMEDOUT]
    #   Connection timed out (possibly due to packets being dropped by an
    #   intervening firewall).
    # [Errno::ENETUNREACH]
    #   There is no route to that network.
    # [SocketError]
    #   Hostname not known or other socket error.
    # [Net::IMAP::ByeResponseError]
    #   Connected to the host successfully, but it immediately said goodbye.
    #
    def initialize(host, port: nil, ssl: nil, response_handlers: nil,
                   config: Config.global, **config_options)
      super()
      # Config options
      @host = host
      @config = Config.new(config, **config_options)
      @port = port || (ssl ? SSL_PORT : PORT)
      @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(ssl)

      # Basic Client State
      @utf8_strings = false
      @debug_output_bol = true
      @exception = nil
      @greeting = nil
      @capabilities = nil
      @tls_verified = false
      @connection_state = ConnectionState::NotAuthenticated.new

      # Client Protocol Receiver
      @parser = ResponseParser.new(config: @config)
      @responses = Hash.new {|h, k| h[k] = [] }
      @response_handlers = []
      @receiver_thread = nil
      @receiver_thread_exception = nil
      @receiver_thread_terminating = false
      response_handlers&.each do add_response_handler(_1) end

      # Client Protocol Sender (including state for currently running commands)
      @tag_prefix = "RUBY"
      @tagno = 0
      @tagged_responses = {}
      @tagged_response_arrival = new_cond
      @continued_command_tag = nil
      @continuation_request_arrival = new_cond
      @continuation_request_exception = nil
      @idle_done_cond = nil
      @logout_command_tag = nil

      # Connection
      @sock = tcp_socket(@host, @port)
      @reader = ResponseReader.new(self, @sock)
      start_tls_session if ssl_ctx
      start_imap_connection
    end

    # Returns true after the TLS negotiation has completed and the remote
    # hostname has been verified.  Returns false when TLS has been established
    # but peer verification was disabled.
    def tls_verified?; @tls_verified end

    # Disconnects from the server.
    #
    # Waits for receiver thread to close before returning.  Slow or stuck
    # response handlers can cause #disconnect to hang until they complete.
    #
    # Related: #logout, #logout!
    def disconnect
      in_logout_state = try_state_logout?
      return if disconnected?
      begin
        @sock.to_io.shutdown
      rescue Errno::ENOTCONN
        # ignore `Errno::ENOTCONN: Socket is not connected' on some platforms.
      rescue Exception => e
        @receiver_thread.raise(e)
      end
      @sock.close
      @receiver_thread.join
      raise e if e
    ensure
      # Try again after shutting down the receiver thread.  With no reciever
      # left to wait for, any remaining locks should be _very_ brief.
      state_logout! unless in_logout_state
    end

    # Returns true if disconnected from the server.
    #
    # Related: #logout, #disconnect
    def disconnected?
      return @sock.closed?
    end

    # Returns whether the server supports a given +capability+.  When available,
    # cached #capabilities are used without sending a new #capability command to
    # the server.
    #
    # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
    # behaviour according to the server's advertised #capabilities.</em>
    #
    # See Net::IMAP@Capabilities for more about \IMAP capabilities.
    #
    # Related: #auth_capable?, #capabilities, #capability, #enable
    def capable?(capability) capabilities.include? capability.to_s.upcase end
    alias capability? capable?

    # Returns the server capabilities.  When available, cached capabilities are
    # used without sending a new #capability command to the server.
    #
    # To ensure a case-insensitive comparison, #capable? can be used instead.
    #
    # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
    # behaviour according to the server's advertised #capabilities.</em>
    #
    # See Net::IMAP@Capabilities for more about \IMAP capabilities.
    #
    # Related: #capable?, #auth_capable?, #auth_mechanisms, #capability, #enable
    def capabilities
      @capabilities || capability
    end

    # Returns the #authenticate mechanisms that the server claims to support.
    # These are derived from the #capabilities with an <tt>AUTH=</tt> prefix.
    #
    # This may be different when the connection is cleartext or using TLS.  Most
    # servers will drop all <tt>AUTH=</tt> mechanisms from #capabilities after
    # the connection has authenticated.
    #
    #    imap = Net::IMAP.new(hostname, ssl: false)
    #    imap.capabilities    # => ["IMAP4REV1", "LOGINDISABLED"]
    #    imap.auth_mechanisms # => []
    #
    #    imap.starttls
    #    imap.capabilities    # => ["IMAP4REV1", "AUTH=PLAIN", "AUTH=XOAUTH2",
    #                         #     "AUTH=OAUTHBEARER"]
    #    imap.auth_mechanisms # => ["PLAIN", "XOAUTH2", "OAUTHBEARER"]
    #
    #    imap.authenticate("XOAUTH2", username, oauth2_access_token)
    #    imap.auth_mechanisms # => []
    #
    # Related: #authenticate, #auth_capable?, #capabilities
    def auth_mechanisms
      capabilities
        .grep(/\AAUTH=/i)
        .map { _1.delete_prefix("AUTH=") }
    end

    # Returns whether the server supports a given SASL +mechanism+ for use with
    # the #authenticate command.  The +mechanism+ is supported when
    # #capabilities includes <tt>"AUTH=#{mechanism.to_s.upcase}"</tt>.  When
    # available, cached capabilities are used without sending a new #capability
    # command to the server.
    #
    #   imap.capable?      "AUTH=PLAIN"  # => true
    #   imap.auth_capable? "PLAIN"       # => true
    #   imap.auth_capable? "blurdybloop" # => false
    #
    # Related: #authenticate, #auth_mechanisms, #capable?, #capabilities
    def auth_capable?(mechanism)
      capable? "AUTH=#{mechanism}"
    end

    # Returns whether capabilities have been cached.  When true, #capable? and
    # #capabilities don't require sending a #capability command to the server.
    #
    # See Net::IMAP@Capabilities for more about \IMAP capabilities.
    #
    # Related: #capable?, #capability, #clear_cached_capabilities
    def capabilities_cached?
      !!@capabilities
    end

    # Clears capabilities that have been remembered by the Net::IMAP client.
    # This forces a #capability command to be sent the next time a #capabilities
    # query method is called.
    #
    # Net::IMAP automatically discards its cached capabilities when they can
    # change.  Explicitly calling this _should_ be unnecessary for well-behaved
    # servers.
    #
    # Related: #capable?, #capability, #capabilities_cached?
    def clear_cached_capabilities
      synchronize do
        clear_responses("CAPABILITY")
        @capabilities = nil
      end
    end

    # Sends a {CAPABILITY command [IMAP4rev1 §6.1.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.1]
    # and returns an array of capabilities that are supported by the server.
    # The result is stored for use by #capable? and #capabilities.
    #
    # <em>*NOTE:* Most Net::IMAP methods do not _currently_ modify their
    # behaviour according to the server's advertised #capabilities.</em>
    #
    # Net::IMAP automatically stores and discards capability data according to
    # the requirements and recommendations in
    # {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1],
    # {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and
    # {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1].
    # Use #capable?, #auth_capable?, or #capabilities to this cache and avoid
    # sending the #capability command unnecessarily.
    #
    # See Net::IMAP@Capabilities for more about \IMAP capabilities.
    #
    # Related: #capable?, #auth_capable?, #capability, #enable
    def capability
      synchronize do
        send_command("CAPABILITY")
        @capabilities = clear_responses("CAPABILITY").last.freeze
      end
    end

    # Sends an {ID command [RFC2971 §3.1]}[https://www.rfc-editor.org/rfc/rfc2971#section-3.1]
    # and returns a hash of the server's response, or nil if the server does not
    # identify itself.
    #
    # Note that the user should first check if the server supports the ID
    # capability. For example:
    #
    #    if capable?(:ID)
    #      id = imap.id(
    #        name: "my IMAP client (ruby)",
    #        version: MyIMAP::VERSION,
    #        "support-url": "mailto:bugs@example.com",
    #        os: RbConfig::CONFIG["host_os"],
    #      )
    #    end
    #
    # See [ID[https://www.rfc-editor.org/rfc/rfc2971]] for field definitions.
    #
    # ==== Capabilities
    #
    # The server's capabilities must include +ID+
    # [RFC2971[https://www.rfc-editor.org/rfc/rfc2971]].
    def id(client_id=nil)
      synchronize do
        send_command("ID", ClientID.new(client_id))
        clear_responses("ID").last
      end
    end

    # Sends a {NOOP command [IMAP4rev1 §6.1.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.2]
    # to the server.
    #
    # This allows the server to send unsolicited untagged EXPUNGE #responses,
    # but does not execute any client request.  \IMAP servers are permitted to
    # send unsolicited untagged responses at any time, except for +EXPUNGE+:
    #
    # * +EXPUNGE+ can only be sent while a command is in progress.
    # * +EXPUNGE+ must _not_ be sent during #fetch, #store, or #search.
    # * +EXPUNGE+ may be sent during #uid_fetch, #uid_store, or #uid_search.
    #
    # Related: #idle, #check
    def noop
      send_command("NOOP")
    end

    # Sends a {LOGOUT command [IMAP4rev1 §6.1.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.3]
    # to inform the command to inform the server that the client is done with
    # the connection.
    #
    # Related: #disconnect, #logout!
    def logout
      send_command("LOGOUT")
    end

    # Calls #logout then, after receiving the TaggedResponse for the +LOGOUT+,
    # calls #disconnect.  Returns the TaggedResponse from +LOGOUT+.  Returns
    # +nil+ when the client is already disconnected, in contrast to #logout
    # which raises an exception.
    #
    # If #logout raises a StandardError, a warning will be printed but the
    # exception will not be re-raised.
    #
    # This is useful in situations where the connection must be dropped, for
    # example for security or after tests.  If logout errors need to be handled,
    # use #logout and #disconnect instead.
    #
    # Related: #logout, #disconnect
    def logout!
      logout unless disconnected?
    rescue => ex
      warn "%s during <Net::IMAP %s:%s> logout!: %s" % [
        ex.class, host, port, ex
      ]
    ensure
      disconnect
    end

    # Sends a {STARTTLS command [IMAP4rev1 §6.2.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.1]
    # to start a TLS session.
    #
    # Any +options+ are forwarded directly to
    # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
    # the keys are names of attribute assignment methods on
    # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
    #
    # See DeprecatedClientOptions#starttls for deprecated arguments.
    #
    # This method returns after TLS negotiation and hostname verification are
    # both successful.  Any error indicates that the connection has not been
    # secured.
    #
    # After the server agrees to start a TLS connection, this method waits up to
    # {config.open_timeout}[rdoc-ref:Config#open_timeout] before raising
    # +Net::OpenTimeout+.
    #
    # *Note:*
    # >>>
    #   Any #response_handlers added before STARTTLS should be aware that the
    #   TaggedResponse to STARTTLS is sent clear-text, _before_ TLS negotiation.
    #   TLS starts immediately _after_ that response.  Any response code sent
    #   with the response (e.g. CAPABILITY) is insecure and cannot be trusted.
    #
    # Related: Net::IMAP.new, #login, #authenticate
    #
    # ==== Capability
    # Clients should not call #starttls unless the server advertises the
    # +STARTTLS+ capability.
    #
    # Server capabilities may change after #starttls, #login, and #authenticate.
    # Cached #capabilities will be cleared when this method completes.
    #
    def starttls(**options)
      @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
      error = nil
      ok = send_command("STARTTLS") do |resp|
        if resp.kind_of?(TaggedResponse) && resp.name == "OK"
          clear_cached_capabilities
          clear_responses
          start_tls_session
        end
      rescue Exception => error
        raise # note that the error backtrace is in the receiver_thread
      end
      if error
        disconnect
        raise error
      end
      ok
    end

    # :call-seq:
    #   authenticate(mechanism, *, sasl_ir: config.sasl_ir, registry: Net::IMAP::SASL.authenticators, **, &) -> ok_resp
    #
    # Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2]
    # to authenticate the client.  If successful, the connection enters the
    # "_authenticated_" state.
    #
    # +mechanism+ is the name of the \SASL authentication mechanism to be used.
    #
    # +sasl_ir+ allows or disallows sending an "initial response" (see the
    # +SASL-IR+ capability, below).  Defaults to the #config value for
    # {sasl_ir}[rdoc-ref:Config#sasl_ir], which defaults to +true+.
    #
    # The +registry+ kwarg can be used to select the mechanism implementation
    # from a custom registry.  See SASL.authenticator and SASL::Authenticators.
    #
    # All other arguments are forwarded to the registered SASL authenticator for
    # the requested mechanism.  <em>The documentation for each individual
    # mechanism must be consulted for its specific parameters.</em>
    #
    # Related: #login, #starttls, #auth_capable?, #auth_mechanisms
    #
    # ==== Mechanisms
    #
    # Each mechanism has different properties and requirements.  Please consult
    # the documentation for the specific mechanisms you are using:
    #
    # +ANONYMOUS+::
    #     See AnonymousAuthenticator[rdoc-ref:Net::IMAP::SASL::AnonymousAuthenticator].
    #
    #     Allows the user to gain access to public services or resources without
    #     authenticating or disclosing an identity.
    #
    # +EXTERNAL+::
    #     See ExternalAuthenticator[rdoc-ref:Net::IMAP::SASL::ExternalAuthenticator].
    #
    #     Authenticates using already established credentials, such as a TLS
    #     certificate or IPsec.
    #
    # +OAUTHBEARER+::
    #     See OAuthBearerAuthenticator[rdoc-ref:Net::IMAP::SASL::OAuthBearerAuthenticator].
    #
    #     Login using an OAuth2 Bearer token.  This is the standard mechanism
    #     for using OAuth2 with \SASL, but it is not yet deployed as widely as
    #     +XOAUTH2+.
    #
    # +PLAIN+::
    #     See PlainAuthenticator[rdoc-ref:Net::IMAP::SASL::PlainAuthenticator].
    #
    #     Login using clear-text username and password.
    #
    # +SCRAM-SHA-1+::
    # +SCRAM-SHA-256+::
    #     See ScramAuthenticator[rdoc-ref:Net::IMAP::SASL::ScramAuthenticator].
    #
    #     Login by username and password.  The password is not sent to the
    #     server but is used in a salted challenge/response exchange.
    #     +SCRAM-SHA-1+ and +SCRAM-SHA-256+ are directly supported by
    #     Net::IMAP::SASL.  New authenticators can easily be added for any other
    #     <tt>SCRAM-*</tt> mechanism if the digest algorithm is supported by
    #     OpenSSL::Digest.
    #
    # +XOAUTH2+::
    #     See XOAuth2Authenticator[rdoc-ref:Net::IMAP::SASL::XOAuth2Authenticator].
    #
    #     Login using a username and an OAuth2 access token.  Non-standard and
    #     obsoleted by +OAUTHBEARER+, but widely supported.
    #
    # See the {SASL mechanism
    # registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
    # for a list of all SASL mechanisms and their specifications.  To register
    # new authenticators, see Authenticators.
    #
    # ===== Deprecated mechanisms
    #
    # <em>Obsolete mechanisms should be avoided, but are still available for
    # backwards compatibility.  See</em> Net::IMAP::SASL@Deprecated+mechanisms.
    # <em>Using a deprecated mechanism will print a warning.</em>
    #
    # ==== Capabilities
    #
    # <tt>"AUTH=#{mechanism}"</tt> capabilities indicate server support for
    # mechanisms.  Use #auth_capable? or #auth_mechanisms to check for support
    # before using a particular mechanism.
    #
    #    if imap.auth_capable? "XOAUTH2"
    #      imap.authenticate "XOAUTH2", username, oauth2_access_token
    #    elsif imap.auth_capable? "PLAIN"
    #      imap.authenticate "PLAIN", username, password
    #    elsif !imap.capability? "LOGINDISABLED"
    #      imap.login username, password
    #    else
    #      raise "No acceptable authentication mechanism is available"
    #    end
    #
    # Although servers should list all supported \SASL mechanisms, they may
    # allow authentication with an unlisted +mechanism+.
    #
    # If [SASL-IR[https://www.rfc-editor.org/rfc/rfc4959.html]] is supported
    # and the appropriate <tt>"AUTH=#{mechanism}"</tt> capability is present,
    # an "initial response" may be sent as an argument to the +AUTHENTICATE+
    # command, saving a round-trip.  The SASL exchange allows for server
    # challenges and client responses, but many mechanisms expect the client to
    # "respond" first.  The initial response will only be sent for
    # "client-first" mechanisms.
    #
    # Server capabilities may change after #starttls, #login, and #authenticate.
    # Previously cached #capabilities will be cleared when this method
    # completes.  If the TaggedResponse to #authenticate includes updated
    # capabilities, they will be cached.
    def authenticate(*args, sasl_ir: config.sasl_ir, **props, &callback)
      sasl_ir = may_depend_on_capabilities_cached?(sasl_ir)
      sasl_adapter.authenticate(*args, sasl_ir: sasl_ir, **props, &callback)
        .tap do state_authenticated! _1 end
    end

    # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
    # to identify the client and carries the plaintext +password+ authenticating
    # this +user+.  If successful, the connection enters the "_authenticated_"
    # state.
    #
    # Using #authenticate {should be
    # preferred}[https://www.rfc-editor.org/rfc/rfc9051.html#name-login-command]
    # over #login.  The LOGIN command is not the same as #authenticate with the
    # "LOGIN" +mechanism+.
    #
    # A Net::IMAP::NoResponseError is raised if authentication fails.
    #
    # Related: #authenticate, #starttls
    #
    # ==== Capabilities
    #
    # An IMAP client MUST NOT call #login when the server advertises the
    # +LOGINDISABLED+ capability.  By default, Net::IMAP will raise a
    # LoginDisabledError when that capability is present.  See
    # Config#enforce_logindisabled.
    #
    # Server capabilities may change after #starttls, #login, and #authenticate.
    # Cached capabilities _must_ be invalidated after this method completes.
    # The TaggedResponse to #login may include updated capabilities in its
    # ResponseCode.
    #
    def login(user, password)
      if enforce_logindisabled? && capability?("LOGINDISABLED")
        raise LoginDisabledError
      end
      send_command("LOGIN", user, password)
        .tap do state_authenticated! _1 end
    end

    # Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1]
    # to select a +mailbox+ so that messages in the +mailbox+ can be accessed.
    #
    # After you have selected a mailbox, you may retrieve the number of items in
    # that mailbox from <tt>imap.responses("EXISTS", &:last)</tt>, and the
    # number of recent messages from <tt>imap.responses("RECENT", &:last)</tt>.
    # Note that these values can change if new messages arrive during a session
    # or when existing messages are expunged; see #add_response_handler for a
    # way to detect these events.
    #
    # When the +condstore+ keyword argument is true, the server is told to
    # enable the extension.  If +mailbox+ supports persistence of mod-sequences,
    # the +HIGHESTMODSEQ+ ResponseCode will be sent as an untagged response to
    # #select and all +FETCH+ responses will include FetchData#modseq.
    # Otherwise, the +NOMODSEQ+ ResponseCode will be sent.
    #
    # A Net::IMAP::NoResponseError is raised if the mailbox does not
    # exist or is for some reason non-selectable.
    #
    # Related: #examine
    #
    # ==== Capabilities
    #
    # If [UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]] is supported,
    # the server may return an untagged "NO" response with a "UIDNOTSTICKY"
    # response code indicating that the mailstore does not support persistent
    # UIDs:
    #   imap.responses("NO", &:last)&.code&.name == "UIDNOTSTICKY"
    #
    # If [CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162.html]] is supported,
    # the +condstore+ keyword parameter may be used.
    #   imap.select("mbox", condstore: true)
    #   modseq = imap.responses("HIGHESTMODSEQ", &:last)
    def select(mailbox, condstore: false)
      args = ["SELECT", mailbox]
      args << ["CONDSTORE"] if condstore
      synchronize do
        state_unselected! # implicitly closes current mailbox
        @responses.clear
        send_command(*args)
          .tap do state_selected! end
      end
    end

    # Sends a {EXAMINE command [IMAP4rev1 §6.3.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.2]
    # to select a +mailbox+ so that messages in the +mailbox+ can be accessed.
    # Behaves the same as #select, except that the selected +mailbox+ is
    # identified as read-only.
    #
    # A Net::IMAP::NoResponseError is raised if the mailbox does not
    # exist or is for some reason non-examinable.
    #
    # Related: #select
    def examine(mailbox, condstore: false)
      args = ["EXAMINE", mailbox]
      args << ["CONDSTORE"] if condstore
      synchronize do
        state_unselected! # implicitly closes current mailbox
        @responses.clear
        send_command(*args)
          .tap do state_selected! end
      end
    end

    # Sends a {CREATE command [IMAP4rev1 §6.3.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.3]
    # to create a new +mailbox+.
    #
    # A Net::IMAP::NoResponseError is raised if a mailbox with that name
    # cannot be created.
    #
    # Related: #rename, #delete
    def create(mailbox)
      send_command("CREATE", mailbox)
    end

    # Sends a {DELETE command [IMAP4rev1 §6.3.4]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.4]
    # to remove the +mailbox+.
    #
    # A Net::IMAP::NoResponseError is raised if a mailbox with that name
    # cannot be deleted, either because it does not exist or because the
    # client does not have permission to delete it.
    #
    # Related: #create, #rename
    def delete(mailbox)
      send_command("DELETE", mailbox)
    end

    # Sends a {RENAME command [IMAP4rev1 §6.3.5]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.5]
    # to change the name of the +mailbox+ to +newname+.
    #
    # A Net::IMAP::NoResponseError is raised if a mailbox with the
    # name +mailbox+ cannot be renamed to +newname+ for whatever
    # reason; for instance, because +mailbox+ does not exist, or
    # because there is already a mailbox with the name +newname+.
    #
    # Related: #create, #delete
    def rename(mailbox, newname)
      send_command("RENAME", mailbox, newname)
    end

    # Sends a {SUBSCRIBE command [IMAP4rev1 §6.3.6]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.6]
    # to add the specified +mailbox+ name to the server's set of "active" or
    # "subscribed" mailboxes as returned by #lsub.
    #
    # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be
    # subscribed to; for instance, because it does not exist.
    #
    # Related: #unsubscribe, #lsub, #list
    def subscribe(mailbox)
      send_command("SUBSCRIBE", mailbox)
    end

    # Sends an {UNSUBSCRIBE command [IMAP4rev1 §6.3.7]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.7]
    # to remove the specified +mailbox+ name from the server's set of "active"
    # or "subscribed" mailboxes.
    #
    # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be
    # unsubscribed from; for instance, because the client is not currently
    # subscribed to it.
    #
    # Related: #subscribe, #lsub, #list
    def unsubscribe(mailbox)
      send_command("UNSUBSCRIBE", mailbox)
    end

    # Sends a {LIST command [IMAP4rev1 §6.3.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.8]
    # and returns a subset of names from the complete set of all names available
    # to the client.  +refname+ provides a context (for instance, a base
    # directory in a directory-based mailbox hierarchy).  +mailbox+ specifies a
    # mailbox or (via wildcards) mailboxes under that context.  Two wildcards
    # may be used in +mailbox+: <tt>"*"</tt>, which matches all characters
    # *including* the hierarchy delimiter (for instance, "/" on a UNIX-hosted
    # directory-based mailbox hierarchy); and <tt>"%"</tt>, which matches all
    # characters *except* the hierarchy delimiter.
    #
    # If +refname+ is empty, +mailbox+ is used directly to determine
    # which mailboxes to match.  If +mailbox+ is empty, the root
    # name of +refname+ and the hierarchy delimiter are returned.
    #
    # The return value is an array of MailboxList.
    #
    # Related: #lsub, MailboxList
    #
    # ==== For example:
    #
    #   imap.create("foo/bar")
    #   imap.create("foo/baz")
    #   p imap.list("", "foo/%")
    #   #=> [#<Net::IMAP::MailboxList attr=[:Noselect], delim="/", name="foo/">, \\
    #        #<Net::IMAP::MailboxList attr=[:Noinferiors, :Marked], delim="/", name="foo/bar">, \\
    #        #<Net::IMAP::MailboxList attr=[:Noinferiors], delim="/", name="foo/baz">]
    #
    #--
    # TODO: support LIST-EXTENDED extension [RFC5258].  Needed for IMAP4rev2.
    #++
    def list(refname, mailbox)
      synchronize do
        send_command("LIST", refname, mailbox)
        clear_responses("LIST")
      end
    end

    # Sends a {NAMESPACE command [RFC2342 §5]}[https://www.rfc-editor.org/rfc/rfc2342#section-5]
    # and returns the namespaces that are available.  The NAMESPACE command
    # allows a client to discover the prefixes of namespaces used by a server
    # for personal mailboxes, other users' mailboxes, and shared mailboxes.
    #
    # The return value is a Namespaces object which has +personal+, +other+, and
    # +shared+ fields, each an array of Namespace objects.  These arrays will be
    # empty when the server responds with +nil+.
    #
    # Many \IMAP servers are configured with the default personal namespaces as
    # <tt>("" "/")</tt>: no prefix and the "+/+" hierarchy delimiter. In that
    # common case, the naive client may not have any trouble naming mailboxes.
    # But many servers are configured with the default personal namespace as
    # e.g.  <tt>("INBOX." ".")</tt>, placing all personal folders under INBOX,
    # with "+.+" as the hierarchy delimiter. If the client does not check for
    # this, but naively assumes it can use the same folder names for all
    # servers, then folder creation (and listing, moving, etc) can lead to
    # errors.
    #
    # From RFC2342[https://www.rfc-editor.org/rfc/rfc2342]:
    # >>>
    #    <em>Although typically a server will support only a single Personal
    #    Namespace, and a single Other User's Namespace, circumstances exist
    #    where there MAY be multiples of these, and a client MUST be prepared
    #    for them.  If a client is configured such that it is required to create
    #    a certain mailbox, there can be circumstances where it is unclear which
    #    Personal Namespaces it should create the mailbox in.  In these
    #    situations a client SHOULD let the user select which namespaces to
    #    create the mailbox in.</em>
    #
    # Related: #list, Namespaces, Namespace
    #
    # ==== For example:
    #
    #    if capable?("NAMESPACE")
    #      namespaces = imap.namespace
    #      if namespace = namespaces.personal.first
    #        prefix = namespace.prefix  # e.g. "" or "INBOX."
    #        delim  = namespace.delim   # e.g. "/" or "."
    #        # personal folders should use the prefix and delimiter
    #        imap.create(prefix + "foo")
    #        imap.create(prefix + "bar")
    #        imap.create(prefix + %w[path to my folder].join(delim))
    #      end
    #    end
    #
    # ==== Capabilities
    #
    # The server's capabilities must include either +IMAP4rev2+ or +NAMESPACE+
    # [RFC2342[https://www.rfc-editor.org/rfc/rfc2342]].
    def namespace
      synchronize do
        send_command("NAMESPACE")
        clear_responses("NAMESPACE").last
      end
    end

    # Sends a XLIST command, and returns a subset of names from
    # the complete set of all names available to the client.
    # +refname+ provides a context (for instance, a base directory
    # in a directory-based mailbox hierarchy).  +mailbox+ specifies
    # a mailbox or (via wildcards) mailboxes under that context.
    # Two wildcards may be used in +mailbox+: '*', which matches
    # all characters *including* the hierarchy delimiter (for instance,
    # '/' on a UNIX-hosted directory-based mailbox hierarchy); and '%',
    # which matches all characters *except* the hierarchy delimiter.
    #
    # If +refname+ is empty, +mailbox+ is used directly to determine
    # which mailboxes to match.  If +mailbox+ is empty, the root
    # name of +refname+ and the hierarchy delimiter are returned.
    #
    # The XLIST command is like the LIST command except that the flags
    # returned refer to the function of the folder/mailbox, e.g. :Sent
    #
    # The return value is an array of MailboxList objects. For example:
    #
    #   imap.create("foo/bar")
    #   imap.create("foo/baz")
    #   p imap.xlist("", "foo/%")
    #   #=> [#<Net::IMAP::MailboxList attr=[:Noselect], delim="/", name="foo/">, \\
    #        #<Net::IMAP::MailboxList attr=[:Noinferiors, :Marked], delim="/", name="foo/bar">, \\
    #        #<Net::IMAP::MailboxList attr=[:Noinferiors], delim="/", name="foo/baz">]
    #
    # Related: #list, MailboxList
    #
    # ==== Capabilities
    #
    # The server's capabilities must include +XLIST+,
    # a deprecated Gmail extension (replaced by +SPECIAL-USE+).
    #--
    # TODO: Net::IMAP doesn't yet have full SPECIAL-USE support.  Supporting
    # servers MAY return SPECIAL-USE attributes, but are not *required* to
    # unless the SPECIAL-USE return option is supplied.
    #++
    def xlist(refname, mailbox)
      synchronize do
        send_command("XLIST", refname, mailbox)
        clear_responses("XLIST")
      end
    end

    # Sends a {GETQUOTAROOT command [RFC2087 §4.3]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.3]
    # along with the specified +mailbox+.  This command is generally available
    # to both admin and user.  If this mailbox exists, it returns an array
    # containing objects of type MailboxQuotaRoot and MailboxQuota.
    #
    # Related: #getquota, #setquota, MailboxQuotaRoot, MailboxQuota
    #
    # ==== Capabilities
    #
    # The server's capabilities must include +QUOTA+
    # [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
    def getquotaroot(mailbox)
      synchronize do
        send_command("GETQUOTAROOT", mailbox)
        result = []
        result.concat(clear_responses("QUOTAROOT"))
        result.concat(clear_responses("QUOTA"))
        return result
      end
    end

    # Sends a {GETQUOTA command [RFC2087 §4.2]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.2]
    # along with specified +mailbox+.  If this mailbox exists, then an array
    # containing a MailboxQuota object is returned.  This command is generally
    # only available to server admin.
    #
    # Related: #getquotaroot, #setquota, MailboxQuota
    #
    # ==== Capabilities
    #
    # The server's capabilities must include +QUOTA+
    # [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
    def getquota(mailbox)
      synchronize do
        send_command("GETQUOTA", mailbox)
        clear_responses("QUOTA")
      end
    end

    # Sends a {SETQUOTA command [RFC2087 §4.1]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.1]
    # along with the specified +mailbox+ and +quota+.  If +quota+ is nil, then
    # +quota+ will be unset for that mailbox.  Typically one needs to be logged
    # in as a server admin for this to work.
    #
    # Related: #getquota, #getquotaroot
    #
    # ==== Capabilities
    #
    # The server's capabilities must include +QUOTA+
    # [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
    def setquota(mailbox, quota)
      if quota.nil?
        data = '()'
      else
        data = '(STORAGE ' + quota.to_s + ')'
      end
      send_command("SETQUOTA", mailbox, RawData.new(data))
    end

    # Sends a {SETACL command [RFC4314 §3.1]}[https://www.rfc-editor.org/rfc/rfc4314#section-3.1]
    # along with +mailbox+, +user+ and the +rights+ that user is to have on that
    # mailbox.  If +rights+ is nil, then that user will be stripped of any
    # rights to that mailbox.
    #
    # Related: #getacl
    #
    # ==== Capabilities
    #
    # The server's capabilities must include +ACL+
    # [RFC4314[https://www.rfc-editor.org/rfc/rfc4314]].
    def setacl(mailbox, user, rights)
      if rights.nil?
        send_command("SETACL", mailbox, user, "")
      else
        send_command("SETACL", mailbox, user, rights)
      end
    end

    # Sends a {GETACL command [RFC4314 §3.3]}[https://www.rfc-editor.org/rfc/rfc4314#section-3.3]
    # along with a specified +mailbox+.  If this mailbox exists, an array
    # containing objects of MailboxACLItem will be returned.
    #
    # Related: #setacl, MailboxACLItem
    #
    # ==== Capabilities
    #
    # The server's capabilities must include +ACL+
    # [RFC4314[https://www.rfc-editor.org/rfc/rfc4314]].
    def getacl(mailbox)
      synchronize do
        send_command("GETACL", mailbox)
        clear_responses("ACL").last
      end
    end

    # Sends a {LSUB command [IMAP4rev1 §6.3.9]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.9]
    # and returns a subset of names from the set of names that the user has
    # declared as being "active" or "subscribed."  +refname+ and +mailbox+ are
    # interpreted as for #list.
    #
    # The return value is an array of MailboxList objects.
    #
    # Related: #subscribe, #unsubscribe, #list, MailboxList
    def lsub(refname, mailbox)
      synchronize do
        send_command("LSUB", refname, mailbox)
        clear_responses("LSUB")
      end
    end

    # Sends a {STATUS command [IMAP4rev1 §6.3.10]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.10]
    # and returns the status of the indicated +mailbox+. +attr+ is a list of one
    # or more attributes whose statuses are to be requested.
    #
    # The return value is a hash of attributes.  Most status attributes return
    # integer values, but some return other value types (documented below).
    #
    # A Net::IMAP::NoResponseError is raised if status values
    # for +mailbox+ cannot be returned; for instance, because it
    # does not exist.
    #
    # ==== Supported attributes
    #
    # +MESSAGES+::    The number of messages in the mailbox.
    #
    # +UIDNEXT+::     The next unique identifier value of the mailbox.
    #
    # +UIDVALIDITY+:: The unique identifier validity value of the mailbox.
    #
    # +UNSEEN+::      The number of messages without the <tt>\Seen</tt> flag.
    #
    # +DELETED+::     The number of messages with the <tt>\Deleted</tt> flag.
    #
    # +SIZE+::
    #     The approximate size of the mailbox---must be greater than or equal to
    #     the sum of all messages' +RFC822.SIZE+ fetch item values.
    #
    # +HIGHESTMODSEQ+::
    #    The highest mod-sequence value of all messages in the mailbox.  See
    #    +CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
    #
    # +MAILBOXID+::
    #     A server-allocated unique _string_ identifier for the mailbox.  See
    #     +OBJECTID+ {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html].
    #
    # +RECENT+::
    #     The number of messages with the <tt>\Recent</tt> flag.
    #     _NOTE:_ +RECENT+ was removed from IMAP4rev2.
    #
    # Unsupported attributes may be requested.  The attribute value will be
    # either an Integer or an ExtensionData object.
    #
    # ==== For example:
    #
    #   p imap.status("inbox", ["MESSAGES", "RECENT"])
    #   #=> {"RECENT"=>0, "MESSAGES"=>44}
    #
    # ==== Capabilities
    #
    # +SIZE+ requires the server's capabilities to include either +IMAP4rev2+ or
    # <tt>STATUS=SIZE</tt>
    # {[RFC8483]}[https://www.rfc-editor.org/rfc/rfc8483.html].
    #
    # +DELETED+ requires the server's capabilities to include +IMAP4rev2+.
    #
    # +HIGHESTMODSEQ+ requires the server's capabilities to include +CONDSTORE+
    # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
    #
    # +MAILBOXID+ requires the server's capabilities to include +OBJECTID+
    # {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html].
    def status(mailbox, attr)
      synchronize do
        send_command("STATUS", mailbox, attr)
        clear_responses("STATUS").last&.attr
      end
    end

    # Sends an {APPEND command [IMAP4rev1 §6.3.11]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.11]
    # to append the +message+ to the end of the +mailbox+. The optional +flags+
    # argument is an array of flags initially passed to the new message.  The
    # optional +date_time+ argument specifies the creation time to assign to the
    # new message; it defaults to the current time.
    #
    # For example:
    #
    #   imap.append("inbox", <<EOF.gsub(/\n/, "\r\n"), [:Seen], Time.now)
    #   Subject: hello
    #   From: shugo@ruby-lang.org
    #   To: shugo@ruby-lang.org
    #
    #   hello world
    #   EOF
    #
    # A Net::IMAP::NoResponseError is raised if the mailbox does
    # not exist (it is not created automatically), or if the flags,
    # date_time, or message arguments contain errors.
    #
    # ==== Capabilities
    #
    # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
    # supported and the destination supports persistent UIDs, the server's
    # response should include an +APPENDUID+ response code with AppendUIDData.
    # This will report the UIDVALIDITY of the destination mailbox and the
    # assigned UID of the appended message.
    #
    #--
    # TODO: add MULTIAPPEND support
    #++
    def append(mailbox, message, flags = nil, date_time = nil)
      args = []
      if flags
        args.push(flags)
      end
      args.push(date_time) if date_time
      args.push(Literal.new(message))
      send_command("APPEND", mailbox, *args)
    end

    # Sends a {CHECK command [IMAP4rev1 §6.4.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.1]
    # to request a checkpoint of the currently selected mailbox.  This performs
    # implementation-specific housekeeping; for instance, reconciling the
    # mailbox's in-memory and on-disk state.
    #
    # Related: #idle, #noop
    def check
      send_command("CHECK")
    end

    # Sends a {CLOSE command [IMAP4rev1 §6.4.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.2]
    # to close the currently selected mailbox.  The CLOSE command permanently
    # removes from the mailbox all messages that have the <tt>\\Deleted</tt>
    # flag set.
    #
    # Related: #unselect
    def close
      send_command("CLOSE")
        .tap do state_authenticated! end
    end

    # Sends an {UNSELECT command [RFC3691 §2]}[https://www.rfc-editor.org/rfc/rfc3691#section-3]
    # {[IMAP4rev2 §6.4.2]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.2]
    # to free the session resources for a mailbox and return to the
    # "_authenticated_" state.  This is the same as #close, except that
    # <tt>\\Deleted</tt> messages are not removed from the mailbox.
    #
    # Related: #close
    #
    # ==== Capabilities
    #
    # The server's capabilities must include either +IMAP4rev2+ or +UNSELECT+
    # [RFC3691[https://www.rfc-editor.org/rfc/rfc3691]].
    def unselect
      send_command("UNSELECT")
        .tap do state_authenticated! end
    end

    # call-seq:
    #   expunge -> array of message sequence numbers
    #   expunge -> VanishedData of UIDs
    #
    # Sends an {EXPUNGE command [IMAP4rev1 §6.4.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.3]
    # to permanently remove all messages with the +\Deleted+ flag from the
    # currently selected mailbox.
    #
    # Returns either an array of expunged message <em>sequence numbers</em> or
    # (when the appropriate capability is enabled) VanishedData of expunged
    # UIDs.  Previously unhandled +EXPUNGE+ or +VANISHED+ responses are merged
    # with the direct response to this command.  <tt>VANISHED (EARLIER)</tt>
    # responses will _not_ be merged.
    #
    # When no messages have been expunged, an empty array is returned,
    # regardless of which extensions are enabled.  In a future release, an empty
    # VanishedData may be returned, based on the currently enabled extensions.
    #
    # Related: #uid_expunge
    #
    # ==== Capabilities
    #
    # When either QRESYNC[https://www.rfc-editor.org/rfc/rfc7162] or
    # UIDONLY[https://www.rfc-editor.org/rfc/rfc9586] are enabled, #expunge
    # returns VanishedData, which contains UIDs---<em>not message sequence
    # numbers</em>.
    def expunge
      expunge_internal("EXPUNGE")
    end

    # call-seq:
    #   uid_expunge(uid_set) -> array of message sequence numbers
    #   uid_expunge(uid_set) -> VanishedData of UIDs
    #
    # Sends a {UID EXPUNGE command [RFC4315 §2.1]}[https://www.rfc-editor.org/rfc/rfc4315#section-2.1]
    # {[IMAP4rev2 §6.4.9]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.9]
    # to permanently remove all messages that have both the <tt>\\Deleted</tt>
    # flag set and a UID that is included in +uid_set+.
    #
    # Returns the same result type as #expunge.
    #
    # By using #uid_expunge instead of #expunge when resynchronizing with
    # the server, the client can ensure that it does not inadvertantly
    # remove any messages that have been marked as <tt>\\Deleted</tt> by other
    # clients between the time that the client was last connected and
    # the time the client resynchronizes.
    #
    # Related: #expunge
    #
    # ==== Capabilities
    #
    # The server's capabilities must include either +IMAP4rev2+ or +UIDPLUS+
    # [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]].
    #
    # Otherwise, #uid_expunge is updated by extensions in the same way as
    # #expunge.
    def uid_expunge(uid_set)
      expunge_internal("UID EXPUNGE", SequenceSet.new(uid_set))
    end

    # :call-seq:
    #   search(criteria, charset = nil) -> result
    #   search(criteria, charset: nil, return: nil) -> result
    #
    # Sends a {SEARCH command [IMAP4rev1 §6.4.4]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.4]
    # to search the mailbox for messages that match the given search +criteria+,
    # and returns either a SearchResult or an ESearchResult.  SearchResult
    # inherits from Array (for backward compatibility) but adds
    # SearchResult#modseq when the +CONDSTORE+ capability has been enabled.
    # ESearchResult also implements {#to_a}[rdoc-ref:ESearchResult#to_a], for
    # compatibility with SearchResult.
    #
    # +criteria+ is one or more search keys and their arguments, which may be
    # provided as an array or a string.
    # See {"Argument translation"}[rdoc-ref:#search@Argument+translation]
    # and {"Search criteria"}[rdoc-ref:#search@Search+criteria], below.
    #
    # +return+ options control what kind of information is returned about
    # messages matching the search +criteria+.  Specifying +return+ should force
    # the server to return an ESearchResult instead of a SearchResult, but some
    # servers disobey this requirement.  <em>Requires an extended search
    # capability, such as +ESEARCH+ or +IMAP4rev2+.</em>
    # See {"Argument translation"}[rdoc-ref:#search@Argument+translation] and
    # {"Supported return options"}[rdoc-ref:#search@Supported+return+options],
    # below.
    #
    # +charset+ is the name of the {registered character
    # set}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
    # used by strings in the search +criteria+.  When +charset+ isn't specified,
    # either <tt>"US-ASCII"</tt> or <tt>"UTF-8"</tt> is assumed, depending on
    # the server's capabilities.
    #
    # _NOTE:_ Return options and charset may be sent as part of +criteria+.  Do
    # not use the +return+ or +charset+ arguments when either return options or
    # charset are embedded in +criteria+.
    #
    # Related: #uid_search
    #
    # ==== For example:
    #
    #   imap.search(["SUBJECT", "hello", "NOT", "SEEN"])
    #   #=> [1, 6, 7, 8]
    #
    # The following assumes the server supports +ESEARCH+ and +CONDSTORE+:
    #
    #   result = imap.uid_search(["UID", 12345.., "MODSEQ", 620_162_338],
    #                            return: %w(all count min max))
    #   # => #<data Net::IMAP::ESearchResult tag="RUBY0123", uid=true,
    #   #       data=[["ALL", Net::IMAP::SequenceSet["12346:12349,22222:22230"]],
    #   #             ["COUNT", 13], ["MIN", 12346], ["MAX", 22230],
    #   #             ["MODSEQ", 917162488]]>
    #   result.to_a   # => [12346, 12347, 12348, 12349, 22222, 22223, 22224,
    #                 #     22225, 22226, 22227, 22228, 22229, 22230]
    #   result.uid?   # => true
    #   result.count  # => 13
    #   result.min    # => 12346
    #   result.max    # => 22230
    #   result.modseq # => 917162488
    #
    # Using +return+ options to limit the result to only min, max, and count:
    #
    #   result = imap.uid_search(["UID", 12345..,], return: %w(count min max))
    #   # => #<data Net::IMAP::ESearchResult tag="RUBY0124", uid=true,
    #   #       data=[["COUNT", 13], ["MIN", 12346], ["MAX", 22230]]>
    #   result.to_a   # => []
    #   result.count  # => 13
    #   result.min    # => 12346
    #   result.max    # => 22230
    #
    # Return options and charset may be sent as keyword args or embedded in the
    # +criteria+ arg, but they must be in the correct order: <tt>"RETURN (...)
    # CHARSET ... criteria..."</tt>.  The following searches
    # send the exact same command to the server:
    #
    #    # Return options and charset as keyword arguments (preferred)
    #    imap.search(%w(OR UNSEEN FLAGGED), return: %w(MIN MAX), charset: "UTF-8")
    #    # Embedding return and charset in the criteria array
    #    imap.search(["RETURN", %w(MIN MAX), "CHARSET", "UTF-8", *%w(OR UNSEEN FLAGGED)])
    #    # Embedding return and charset in the criteria string
    #    imap.search("RETURN (MIN MAX) CHARSET UTF-8 OR UNSEEN FLAGGED")
    #
    # Sending charset as the second positional argument is supported for
    # backward compatibility.  Future versions may print a deprecation warning:
    #    imap.search(%w(OR UNSEEN FLAGGED), "UTF-8", return: %w(MIN MAX))
    #
    # ==== Argument translation
    #
    # [+return+ options]
    #   Must be an Array.  Return option names may be either strings or symbols.
    #   +Range+ elements which begin and end with negative integers are encoded
    #   for use with +PARTIAL+--any other ranges are converted to SequenceSet.
    #   Unlike +criteria+, other return option arguments are not automatically
    #   converted to SequenceSet.
    #
    # [When +criteria+ is an Array]
    #   When the array begins with <tt>"RETURN"</tt> (case insensitive), the
    #   second array element is translated like the +return+ parameter (as
    #   described above).
    #
    #   Every other member is a +SEARCH+ command argument:
    #   [SequenceSet]
    #     Encoded as an \IMAP +sequence-set+ with SequenceSet#valid_string.
    #   [Set, Range, <tt>-1</tt>, +:*+, responds to +#to_sequence_set+]
    #     Converted to SequenceSet for validation and encoding.
    #   [nested sequence-set +Array+]
    #     When every element in a nested array is one of the above types, a
    #     positive +Integer+, a sequence-set formatted +String+, or a deeply
    #     nested +Array+ of these same types, the array will be converted to
    #     SequenceSet for validation and encoding.
    #   [Any other nested +Array+]
    #     Otherwise, a nested array is encoded as a parenthesized list, to
    #     combine multiple search keys (e.g., for use with +OR+ and +NOT+).
    #   [+String+]
    #     Sent verbatim when it is a valid \IMAP +atom+, and encoded as an \IMAP
    #     +quoted+ or +literal+ string otherwise.  Every standard search key
    #     name is a valid \IMAP +atom+ and every standard search key string
    #     argument is an +astring+ which may be encoded as +atom+, +quoted+, or
    #     +literal+.
    #
    #     *Note:* <tt>*</tt> is not a valid \IMAP +atom+ character.  Any string
    #     containing <tt>*</tt> will be encoded as a +quoted+ string, _not_ a
    #     +sequence-set+.
    #   [+Integer+ (except for <tt>-1</tt>)]
    #     Encoded using +#to_s+.
    #   [+Date+]
    #     Encoded as an \IMAP date (see ::encode_date).
    #
    # [When +criteria+ is a String]
    #   +criteria+ will be sent directly to the server <em>without any
    #   validation or encoding</em>.
    #
    #   <em>*WARNING:* This is vulnerable to injection attacks when external
    #   inputs are used.</em>
    #
    # ==== Supported return options
    #
    # For full definitions of the standard return options and return data, see
    # the relevant RFCs.
    #
    # [+ALL+]
    #    Returns ESearchResult#all with a SequenceSet of all matching sequence
    #    numbers or UIDs.  This is the default, when return options are empty.
    #
    #    For compatibility with SearchResult, ESearchResult#to_a returns an
    #    Array of message sequence numbers or UIDs.
    #
    #    <em>Requires either the +ESEARCH+ or +IMAP4rev2+ capabability.</em>
    #    {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731]
    #    {[RFC9051]}[https://rfc-editor.org/rfc/rfc9051]
    #
    # [+COUNT+]
    #    Returns ESearchResult#count with the number of matching messages.
    #
    #    <em>Requires either the +ESEARCH+ or +IMAP4rev2+ capabability.</em>
    #    {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731]
    #    {[RFC9051]}[https://rfc-editor.org/rfc/rfc9051]
    #
    # [+MAX+]
    #    Returns ESearchResult#max with the highest matching sequence number or
    #    UID.
    #
    #    <em>Requires either the +ESEARCH+ or +IMAP4rev2+ capabability.</em>
    #    {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731]
    #    {[RFC9051]}[https://rfc-editor.org/rfc/rfc9051]
    #
    # [+MIN+]
    #    Returns ESearchResult#min with the lowest matching sequence number or
    #    UID.
    #
    #    <em>Requires either the +ESEARCH+ or +IMAP4rev2+ capabability.</em>
    #    {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731]
    #    {[RFC9051]}[https://rfc-editor.org/rfc/rfc9051]
    #
    # [+PARTIAL+ _range_]
    #    Returns ESearchResult#partial with a SequenceSet of a subset of
    #    matching sequence numbers or UIDs, as selected by _range_.  As with
    #    sequence numbers, the first result is +1+: <tt>1..500</tt> selects the
    #    first 500 search results (in mailbox order), <tt>501..1000</tt> the
    #    second 500, and so on.  _range_ may also be negative: <tt>-500..-1</tt>
    #    selects the last 500 search results.
    #
    #    <em>Requires either the <tt>CONTEXT=SEARCH</tt> or +PARTIAL+ capabability.</em>
    #    {[RFC5267]}[https://rfc-editor.org/rfc/rfc5267]
    #    {[RFC9394]}[https://rfc-editor.org/rfc/rfc9394]
    #
    # ===== +MODSEQ+ return data
    #
    # ESearchResult#modseq return data does not have a corresponding return
    # option.  Instead, it is returned if the +MODSEQ+ search key is used or
    # when the +CONDSTORE+ extension is enabled for the selected mailbox.
    # See [{RFC4731 §3.2}[https://www.rfc-editor.org/rfc/rfc4731#section-3.2]]
    # or [{RFC7162 §2.1.5}[https://www.rfc-editor.org/rfc/rfc7162#section-3.1.5]].
    #
    # ===== +RFC4466+ compatible extensions
    #
    # {RFC4466 §2.6}[https://www.rfc-editor.org/rfc/rfc4466.html#section-2.6]
    # defines standard syntax for search extensions.  Net::IMAP allows sending
    # unsupported search return options and will parse unsupported search
    # extensions' return values into ExtensionData.  Please note that this is an
    # intentionally _unstable_ API.  Future releases may return different
    # (incompatible) objects, <em>without deprecation or warning</em>.
    #
    # ==== Search keys
    #
    # For full definitions of the standard search +criteria+,
    # see [{IMAP4rev1 §6.4.4}[https://www.rfc-editor.org/rfc/rfc3501.html#section-6.4.4]],
    # or  [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]],
    # in addition to documentation for
    # any #capabilities which may define additional search filters, such as
    # +CONDSTORE+, +WITHIN+, +FILTERS+, <tt>SEARCH=FUZZY</tt>, +OBJECTID+, or
    # +SAVEDATE+.
    #
    # With the exception of <em>sequence-set</em> and <em>parenthesized
    # list</em>, all search keys are composed of prefix label with zero or more
    # arguments.  The number and type of arguments is specific to each search
    # key.
    #
    # ===== Search keys that match all messages
    #
    # [+ALL+]
    #   The default initial key.  Matches every message in the mailbox.
    #
    # [+SAVEDATESUPPORTED+]
    #   Matches every message in the mailbox when the mailbox supports the save
    #   date attribute.  Otherwise, it matches no messages.
    #
    #   <em>Requires +SAVEDATE+ capability</em>.
    #   {[RFC8514]}[https://www.rfc-editor.org/rfc/rfc8514.html#section-4.3]
    #
    # ===== Sequence set search keys
    #
    # [_sequence-set_]
    #   Matches messages with message sequence numbers in _sequence-set_.
    #
    #   _Note:_ this search key has no label.
    #
    #   <em>+UIDONLY+ must *not* be enabled.</em>
    #   {[RFC9586]}[https://www.rfc-editor.org/rfc/rfc9586.html]
    #
    # [+UID+ _sequence-set_]
    #   Matches messages with a UID in _sequence-set_.
    #
    # ===== Compound search keys
    #
    # [(_search-key_ _search-key_...)]
    #   Combines one or more _search-key_ arguments to match
    #   messages which match all contained search keys.  Useful for +OR+, +NOT+,
    #   and other search keys with _search-key_ arguments.
    #
    #   _Note:_ this search key has no label.
    #
    # [+OR+ _search-key_ _search-key_]
    #   Matches messages which match either _search-key_ argument.
    #
    # [+NOT+ _search-key_]
    #   Matches messages which do not match _search-key_.
    #
    # [+FUZZY+ _search-key_]
    #   Uses fuzzy matching for the specified search key.
    #
    #   <em>Requires <tt>SEARCH=FUZZY</tt> capability.</em>
    #   {[RFC6203]}[https://www.rfc-editor.org/rfc/rfc6203.html#section-6].
    #
    # ===== Flags search keys
    #
    # [+ANSWERED+, +UNANSWERED+]
    #   Matches messages with or without the <tt>\\Answered</tt> flag.
    # [+DELETED+, +UNDELETED+]
    #   Matches messages with or without the <tt>\\Deleted</tt> flag.
    # [+DRAFT+, +UNDRAFT+]
    #   Matches messages with or without the <tt>\\Draft</tt> flag.
    # [+FLAGGED+, +UNFLAGGED+]
    #   Matches messages with or without the <tt>\\Flagged</tt> flag.
    # [+SEEN+, +UNSEEN+]
    #   Matches messages with or without the <tt>\\Seen</tt> flag.
    # [+KEYWORD+ _keyword_, +UNKEYWORD+ _keyword_]
    #   Matches messages with or without the specified _keyword_.
    #
    # [+RECENT+, +UNRECENT+]
    #   Matches messages with or without the <tt>\\Recent</tt> flag.
    #
    #   *NOTE:* The <tt>\\Recent</tt> flag has been removed from +IMAP4rev2+.
    # [+NEW+]
    #   Equivalent to <tt>(RECENT UNSEEN)</tt>.
    #
    #   *NOTE:* The <tt>\\Recent</tt> flag has been removed from +IMAP4rev2+.
    #
    # ===== Header field substring search keys
    #
    # [+BCC+ _substring_]
    #   Matches when _substring_ is in the envelope's +BCC+ field.
    # [+CC+ _substring_]
    #   Matches when _substring_ is in the envelope's +CC+ field.
    # [+FROM+ _substring_]
    #   Matches when _substring_ is in the envelope's +FROM+ field.
    # [+SUBJECT+ _substring_]
    #   Matches when _substring_ is in the envelope's +SUBJECT+ field.
    # [+TO+ _substring_]
    #   Matches when _substring_ is in the envelope's +TO+ field.
    #
    # [+HEADER+ _field_ _substring_]
    #   Matches when _substring_ is in the specified header _field_.
    #
    # ===== Body text search keys
    # [+BODY+ _string_]
    #   Matches when _string_ is in the body of the message.
    #   Does not match on header fields.
    #
    #   The server _may_ use flexible matching, rather than simple substring
    #   matches.  For example, this may use stemming or match only full words.
    #
    # [+TEXT+ _string_]
    #   Matches when _string_ is in the header or body of the message.
    #
    #   The server _may_ use flexible matching, rather than simple substring
    #   matches.  For example, this may use stemming or match only full words.
    #
    # ===== Date/Time search keys
    #
    # [+SENTBEFORE+ _date_]
    # [+SENTON+ _date_]
    # [+SENTSINCE+ _date_]
    #   Matches when the +Date+ header is earlier than, on, or later than _date_.
    #
    # [+BEFORE+ _date_]
    # [+ON+ _date_]
    # [+SINCE+ _date_]
    #   Matches when the +INTERNALDATE+ is earlier than, on, or later than
    #   _date_.
    #
    # [+OLDER+ _interval_]
    # [+YOUNGER+ _interval_]
    #   Matches when the +INTERNALDATE+ is more/less than _interval_ seconds ago.
    #
    #   <em>Requires +WITHIN+ capability</em>.
    #   {[RFC5032]}[https://www.rfc-editor.org/rfc/rfc5032.html]
    #
    # [+SAVEDBEFORE+ _date_]
    # [+SAVEDON+ _date_]
    # [+SAVEDSINCE+ _date_]
    #   Matches when the save date is earlier than, on, or later than _date_.
    #
    #   <em>Requires +SAVEDATE+ capability.</em>
    #   {[RFC8514]}[https://www.rfc-editor.org/rfc/rfc8514.html#section-4.3]
    #
    # ===== Other message attribute search keys
    #
    # [+SMALLER+ _bytes_]
    # [+LARGER+ _bytes_]
    #   Matches when +RFC822.SIZE+ is smaller or larger than _bytes_.
    #
    # [+ANNOTATION+ _entry_ _attr_ _value_]
    #   Matches messages that have annotations with entries matching _entry_,
    #   attributes matching _attr_, and _value_ in the attribute's values.
    #
    #   <em>Requires +ANNOTATE-EXPERIMENT-1+ capability</em>.
    #   {[RFC5257]}[https://www.rfc-editor.org/rfc/rfc5257.html].
    #
    # [+FILTER+ _filter_]
    #   References a _filter_ that is stored on the server and matches all
    #   messages which would be matched by that filter's search criteria.
    #
    #   <em>Requires +FILTERS+ capability</em>.
    #   {[RFC5466]}[https://www.rfc-editor.org/rfc/rfc5466.html#section-3.1]
    #
    # [+MODSEQ+ _modseq_]
    #   Matches when +MODSEQ+ is greater than or equal to _modseq_.
    #
    #   <em>Requires +CONDSTORE+ capability</em>.
    #   {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1.5].
    #
    # [+MODSEQ+ _entry_ _entry-type_ _modseq_]
    #   Matches when a specific metadata _entry_ has been updated since
    #   _modseq_.
    #
    #   For flags, the corresponding _entry_ name is
    #   <tt>"/flags/#{flag_name}"</tt>, where _flag_name_ includes the
    #   <tt>\\</tt> prefix.  _entry-type_ can be one of <tt>"shared"</tt>,
    #   <tt>"priv"</tt> (private), or <tt>"all"</tt>.
    #
    #   <em>Requires +CONDSTORE+ capability</em>.
    #   {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1.5].
    #
    # [+EMAILID+ _objectid_]
    # [+THREADID+ _objectid_]
    #   Matches when +EMAILID+/+THREADID+ is equal to _objectid_
    #   (substring matches are not supported).
    #
    #   <em>Requires +OBJECTID+ capability</em>.
    #   {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html#section-6]
    #
    # ==== Capabilities
    #
    # Return options should only be specified when the server supports
    # +IMAP4rev2+ or an extension that allows them, such as +ESEARCH+
    # [RFC4731[https://rfc-editor.org/rfc/rfc4731#section-3.1]].
    #
    # When +IMAP4rev2+ is enabled, or when the server supports +IMAP4rev2+ but
    # not +IMAP4rev1+, ESearchResult is always returned instead of SearchResult.
    #
    # If CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162.html] is supported
    # and enabled for the selected mailbox, a non-empty SearchResult will
    # include a +MODSEQ+ value.
    #   imap.select("mbox", condstore: true)
    #   result = imap.search(["SUBJECT", "hi there", "not", "new"])
    #   #=> Net::IMAP::SearchResult[1, 6, 7, 8, modseq: 5594]
    #   result.modseq # => 5594
    #
    # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled,
    # the +SEARCH+ command is prohibited.  Use #uid_search instead.
    def search(...)
      search_internal("SEARCH", ...)
    end

    # :call-seq:
    #   uid_search(criteria, charset = nil) -> result
    #   uid_search(criteria, charset: nil, return: nil) -> result
    #
    # Sends a {UID SEARCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
    # to search the mailbox for messages that match the given searching
    # criteria, and returns unique identifiers (<tt>UID</tt>s).
    #
    # Returns a SearchResult object.  SearchResult inherits from Array (for
    # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+
    # capability has been enabled.
    #
    # See #search for documentation of parameters.
    #
    # ==== Capabilities
    #
    # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled,
    # #uid_search must be used instead of #search, and the <tt><message
    # set></tt> search criterion is prohibited.  Use +ALL+ or <tt>UID
    # sequence-set</tt> instead.
    #
    # Otherwise, #uid_search is updated by extensions in the same way as
    # #search.
    def uid_search(...)
      search_internal("UID SEARCH", ...)
    end

    # :call-seq:
    #   fetch(set, attr, changedsince: nil) -> array of FetchData
    #
    # Sends a {FETCH command [IMAP4rev1 §6.4.5]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.5]
    # to retrieve data associated with a message in the mailbox.
    #
    # +set+ is the message sequence numbers to fetch, and may be any valid input
    # to {SequenceSet[...]}[rdoc-ref:SequenceSet@Creating+sequence+sets].
    # (For UIDs, use #uid_fetch instead.)
    #
    # +attr+ is a list of attributes to fetch; see FetchStruct documentation for
    # a list of supported attributes.
    #
    # +changedsince+ is an optional integer mod-sequence.  It limits results to
    # messages with a mod-sequence greater than +changedsince+.
    #
    # The return value is an array of FetchData.
    #
    # Related: #uid_fetch, FetchData
    #
    # ==== For example:
    #
    #   p imap.fetch(6..8, "UID")
    #   #=> [#<Net::IMAP::FetchData seqno=6, attr={"UID"=>98}>, \\
    #        #<Net::IMAP::FetchData seqno=7, attr={"UID"=>99}>, \\
    #        #<Net::IMAP::FetchData seqno=8, attr={"UID"=>100}>]
    #   p imap.fetch(6, "BODY[HEADER.FIELDS (SUBJECT)]")
    #   #=> [#<Net::IMAP::FetchData seqno=6, attr={"BODY[HEADER.FIELDS (SUBJECT)]"=>"Subject: test\r\n\r\n"}>]
    #   data = imap.uid_fetch(98, ["RFC822.SIZE", "INTERNALDATE"])[0]
    #   p data.seqno
    #   #=> 6
    #   p data.attr["RFC822.SIZE"]
    #   #=> 611
    #   p data.attr["INTERNALDATE"]
    #   #=> "12-Oct-2000 22:40:59 +0900"
    #   p data.attr["UID"]
    #   #=> 98
    #
    # ==== Capabilities
    #
    # Many extensions define new message +attr+ names.  See FetchStruct for a
    # list of supported extension fields.
    #
    # The server's capabilities must include +CONDSTORE+
    # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162] in order to use the
    # +changedsince+ argument.  Using +changedsince+ implicitly enables the
    # +CONDSTORE+ extension.
    #
    # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled, the
    # +FETCH+ command is prohibited.  Use #uid_fetch instead.
    def fetch(...)
      fetch_internal("FETCH", ...)
    end

    # :call-seq:
    #   uid_fetch(set, attr, changedsince: nil, partial: nil) -> array of FetchData (or UIDFetchData)
    #
    # Sends a {UID FETCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
    # to retrieve data associated with a message in the mailbox.
    #
    # +set+ is the message UIDs to fetch, and may be any valid input to
    # {SequenceSet[...]}[rdoc-ref:SequenceSet@Creating+sequence+sets].
    # (For message sequence numbers, use #fetch instead.)
    #
    # +attr+ behaves the same as with #fetch.
    # >>>
    #   *Note:* Servers _MUST_ implicitly include the +UID+ message data item as
    #   part of any +FETCH+ response caused by a +UID+ command, regardless of
    #   whether a +UID+ was specified as a message data item to the +FETCH+.
    #
    # +changedsince+ (optional) behaves the same as with #fetch.
    #
    # +partial+ is an optional range to limit the number of results returned.
    # It's useful when +set+ contains an unknown number of messages.
    # <tt>1..500</tt> returns the first 500 messages in +set+ (in mailbox
    # order), <tt>501..1000</tt> the second 500, and so on.  +partial+ may also
    # be negative: <tt>-500..-1</tt> selects the last 500 messages in +set+.
    # <em>Requires the +PARTIAL+ capabability.</em>
    # {[RFC9394]}[https://rfc-editor.org/rfc/rfc9394]
    #
    # For example:
    #
    #   # Without partial, the size of the results may be unknown beforehand:
    #   results = imap.uid_fetch(next_uid_to_fetch.., %w(UID FLAGS))
    #   # ... maybe wait for a long time ... and allocate a lot of memory ...
    #   results.size # => 0..2**32-1
    #   process results # may also take a long time and use a lot of memory...
    #
    #   # Using partial, the results may be paginated:
    #   loop do
    #     results = imap.uid_fetch(next_uid_to_fetch.., %w(UID FLAGS),
    #                              partial: 1..500)
    #     # fetch should return quickly and allocate little memory
    #     results.size # => 0..500
    #     break if results.empty?
    #     results.sort_by!(&:uid) # server may return results out of order
    #     next_uid_to_fetch = results.last.uid + 1
    #     process results
    #   end
    #
    # Related: #fetch, FetchData
    #
    # ==== Capabilities
    #
    # The server's capabilities must include +PARTIAL+
    # {[RFC9394]}[https://rfc-editor.org/rfc/rfc9394] in order to use the
    # +partial+ argument.
    #
    # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled,
    # #uid_fetch must be used instead of #fetch, and UIDFetchData will be
    # returned instead of FetchData.
    #
    # Otherwise, #uid_fetch is updated by extensions in the same way as #fetch.
    def uid_fetch(...)
      fetch_internal("UID FETCH", ...)
    end

    # :call-seq:
    #   store(set, attr, value, unchangedsince: nil) -> array of FetchData
    #
    # Sends a {STORE command [IMAP4rev1 §6.4.6]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.6]
    # to alter data associated with messages in the mailbox, in particular their
    # flags.
    #
    # +set+ is a number, an array of numbers, or a Range object.  Each number is
    # a message sequence number.
    #
    # +attr+ is the name of a data item to store.  The semantics of +value+
    # varies based on +attr+:
    # * When +attr+ is <tt>"FLAGS"</tt>, the flags in +value+ replace the
    #   message's flag list.
    # * When +attr+ is <tt>"+FLAGS"</tt>, the flags in +value+ are added to
    #   the flags for the message.
    # * When +attr+ is <tt>"-FLAGS"</tt>, the flags in +value+ are removed
    #   from the message.
    #
    # +unchangedsince+ is an optional integer mod-sequence.  It prohibits any
    # changes to messages with +mod-sequence+ greater than the specified
    # +unchangedsince+ value.  A SequenceSet of any messages that fail this
    # check will be returned in a +MODIFIED+ ResponseCode.
    #
    # The return value is an array of FetchData.
    #
    # Related: #uid_store
    #
    # ==== For example:
    #
    #   p imap.store(6..8, "+FLAGS", [:Deleted])
    #   #=> [#<Net::IMAP::FetchData seqno=6, attr={"FLAGS"=>[:Seen, :Deleted]}>,
    #        #<Net::IMAP::FetchData seqno=7, attr={"FLAGS"=>[:Seen, :Deleted]}>,
    #        #<Net::IMAP::FetchData seqno=8, attr={"FLAGS"=>[:Seen, :Deleted]}>]
    #
    # ==== Capabilities
    #
    # Extensions may define new data items to be used with #store.
    #
    # The server's capabilities must include +CONDSTORE+
    # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162] in order to use the
    # +unchangedsince+ argument.  Using +unchangedsince+ implicitly enables the
    # +CONDSTORE+ extension.
    #
    # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled, the
    # +STORE+ command is prohibited.  Use #uid_store instead.
    def store(set, attr, flags, unchangedsince: nil)
      store_internal("STORE", set, attr, flags, unchangedsince: unchangedsince)
    end

    # :call-seq:
    #   uid_store(set, attr, value, unchangedsince: nil) -> array of FetchData (or UIDFetchData)
    #
    # Sends a {UID STORE command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
    # to alter data associated with messages in the mailbox, in particular their
    # flags.
    #
    # Similar to #store, but +set+ contains unique identifiers instead of
    # message sequence numbers.
    #
    # Related: #store
    #
    # ==== Capabilities
    #
    # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled,
    # #uid_store must be used instead of #store, and UIDFetchData will be
    # returned instead of FetchData.
    #
    # Otherwise, #uid_store is updated by extensions in the same way as #store.
    def uid_store(set, attr, flags, unchangedsince: nil)
      store_internal("UID STORE", set, attr, flags, unchangedsince: unchangedsince)
    end

    # Sends a {COPY command [IMAP4rev1 §6.4.7]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.7]
    # to copy the specified message(s) to the end of the specified destination
    # +mailbox+. The +set+ parameter is a number, an array of numbers, or a
    # Range object.  The number is a message sequence number.
    #
    # Related: #uid_copy
    #
    # ==== Capabilities
    #
    # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
    # supported, the server's response should include a +COPYUID+ response code
    # with CopyUIDData.  This will report the UIDVALIDITY of the destination
    # mailbox, the UID set of the source messages, and the assigned UID set of
    # the moved messages.
    #
    # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled, the
    # +COPY+ command is prohibited.  Use #uid_copy instead.
    def copy(set, mailbox)
      copy_internal("COPY", set, mailbox)
    end

    # Sends a {UID COPY command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
    # to copy the specified message(s) to the end of the specified destination
    # +mailbox+.
    #
    # Similar to #copy, but +set+ contains unique identifiers.
    #
    # ==== Capabilities
    #
    # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] in enabled,
    # #uid_copy must be used instead of #copy.
    #
    # Otherwise, #uid_copy is updated by extensions in the same way as #copy.
    def uid_copy(set, mailbox)
      copy_internal("UID COPY", set, mailbox)
    end

    # Sends a {MOVE command [RFC6851 §3.1]}[https://www.rfc-editor.org/rfc/rfc6851#section-3.1]
    # {[IMAP4rev2 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.8]
    # to move the specified message(s) to the end of the specified destination
    # +mailbox+. The +set+ parameter is a number, an array of numbers, or a
    # Range object. The number is a message sequence number.
    #
    # Related: #uid_move
    #
    # ==== Capabilities
    #
    # The server's capabilities must include either +IMAP4rev2+ or +MOVE+
    # [RFC6851[https://www.rfc-editor.org/rfc/rfc6851]].
    #
    # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
    # supported, the server's response should include a +COPYUID+ response code
    # with CopyUIDData.  This will report the UIDVALIDITY of the destination
    # mailbox, the UID set of the source messages, and the assigned UID set of
    # the moved messages.
    #
    # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled, the
    # +MOVE+ command is prohibited.  Use #uid_move instead.
    def move(set, mailbox)
      copy_internal("MOVE", set, mailbox)
    end

    # Sends a {UID MOVE command [RFC6851 §3.2]}[https://www.rfc-editor.org/rfc/rfc6851#section-3.2]
    # {[IMAP4rev2 §6.4.9]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.9]
    # to move the specified message(s) to the end of the specified destination
    # +mailbox+.
    #
    # Similar to #move, but +set+ contains unique identifiers.
    #
    # Related: #move
    #
    # ==== Capabilities
    #
    # The server's capabilities must include either +IMAP4rev2+ or +MOVE+
    # [RFC6851[https://www.rfc-editor.org/rfc/rfc6851]].
    #
    # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled,
    # #uid_move must be used instead of #move.
    #
    # Otherwise, #uid_move is updated by extensions in the same way as #move.
    def uid_move(set, mailbox)
      copy_internal("UID MOVE", set, mailbox)
    end

    # Sends a {SORT command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3]
    # to search a mailbox for messages that match +search_keys+ and return an
    # array of message sequence numbers, sorted by +sort_keys+.  +search_keys+
    # are interpreted the same as for #search.
    #
    #--
    # TODO: describe +sort_keys+
    #++
    #
    # Related: #uid_sort, #search, #uid_search, #thread, #uid_thread
    #
    # ==== For example:
    #
    #   p imap.sort(["FROM"], ["ALL"], "US-ASCII")
    #   #=> [1, 2, 3, 5, 6, 7, 8, 4, 9]
    #   p imap.sort(["DATE"], ["SUBJECT", "hello"], "US-ASCII")
    #   #=> [6, 7, 8, 1]
    #
    # ==== Capabilities
    #
    # The server's capabilities must include +SORT+
    # [RFC5256[https://www.rfc-editor.org/rfc/rfc5256]].
    def sort(sort_keys, search_keys, charset)
      return sort_internal("SORT", sort_keys, search_keys, charset)
    end

    # Sends a {UID SORT command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3]
    # to search a mailbox for messages that match +search_keys+ and return an
    # array of unique identifiers, sorted by +sort_keys+.  +search_keys+ are
    # interpreted the same as for #search.
    #
    # Related: #sort, #search, #uid_search, #thread, #uid_thread
    #
    # ==== Capabilities
    #
    # The server's capabilities must include +SORT+
    # [RFC5256[https://www.rfc-editor.org/rfc/rfc5256]].
    def uid_sort(sort_keys, search_keys, charset)
      return sort_internal("UID SORT", sort_keys, search_keys, charset)
    end

    # Sends a {THREAD command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3]
    # to search a mailbox and return message sequence numbers in threaded
    # format, as a ThreadMember tree.  +search_keys+ are interpreted the same as
    # for #search.
    #
    # The supported algorithms are:
    #
    # ORDEREDSUBJECT:: split into single-level threads according to subject,
    #                  ordered by date.
    # REFERENCES:: split into threads by parent/child relationships determined
    #              by which message is a reply to which.
    #
    # Unlike #search, +charset+ is a required argument.  US-ASCII
    # and UTF-8 are sample values.
    #
    # Related: #uid_thread, #search, #uid_search, #sort, #uid_sort
    #
    # ==== Capabilities
    #
    # The server's capabilities must include +THREAD+
    # [RFC5256[https://www.rfc-editor.org/rfc/rfc5256]].
    def thread(algorithm, search_keys, charset)
      return thread_internal("THREAD", algorithm, search_keys, charset)
    end

    # Sends a {UID THREAD command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3]
    # Similar to #thread, but returns unique identifiers instead of
    # message sequence numbers.
    #
    # Related: #thread, #search, #uid_search, #sort, #uid_sort
    #
    # ==== Capabilities
    #
    # The server's capabilities must include +THREAD+
    # [RFC5256[https://www.rfc-editor.org/rfc/rfc5256]].
    def uid_thread(algorithm, search_keys, charset)
      return thread_internal("UID THREAD", algorithm, search_keys, charset)
    end

    # Sends an {ENABLE command [RFC5161 §3.2]}[https://www.rfc-editor.org/rfc/rfc5161#section-3.1]
    # {[IMAP4rev2 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.1]
    # to enable the specified server +capabilities+.  Each capability may be an
    # array, string, or symbol.  Returns a list of the capabilities that were
    # enabled.
    #
    # The +ENABLE+ command is only valid in the _authenticated_ state, before
    # any mailbox is selected.
    #
    # Related: #capable?, #capabilities, #capability
    #
    # ==== Capabilities
    #
    # The server's capabilities must include
    # +ENABLE+ [RFC5161[https://www.rfc-editor.org/rfc/rfc5161]]
    # or +IMAP4REV2+ [RFC9051[https://www.rfc-editor.org/rfc/rfc9051]].
    #
    # Additionally, the server capabilities must include a capability matching
    # each enabled extension (usually the same name as the enabled extension).
    # The following capabilities may be enabled:
    #
    # [+CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]]
    #
    #   Updates various commands to return +CONDSTORE+ extension responses.  It
    #   is not necessary to explicitly enable +CONDSTORE+—using any of the
    #   command parameters defined by the extension will implicitly enable it.
    #   See {[RFC7162 §3.1]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1].
    #
    # [+QRESYNC+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]]
    #   *NOTE:* Enabling QRESYNC will replace +EXPUNGE+ with +VANISHED+, but
    #   the extension arguments to #select, #examine, and #uid_fetch are not
    #   supported yet.
    #
    #   Adds quick resynchronization options to #select, #examine, and
    #   #uid_fetch.  +QRESYNC+ _must_ be explicitly enabled before using any of
    #   the extension's command parameters.  All +EXPUNGE+ responses will be
    #   replaced with +VANISHED+ responses.  Enabling +QRESYNC+ implicitly
    #   enables +CONDSTORE+ as well.
    #   See {[RFC7162 §3.2]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.2].
    #
    # [+:utf8+ --- an alias for <tt>"UTF8=ACCEPT"</tt>]
    #
    #   In a future release, <tt>enable(:utf8)</tt> will enable either
    #   <tt>"UTF8=ACCEPT"</tt> or <tt>"IMAP4rev2"</tt>, depending on server
    #   capabilities.
    #
    # [<tt>"UTF8=ACCEPT"</tt> [RFC6855[https://www.rfc-editor.org/rfc/rfc6855]]]
    #
    #   The server's capabilities must include <tt>UTF8=ACCEPT</tt> _or_
    #   <tt>UTF8=ONLY</tt>.
    #
    #   This allows the server to send strings encoded as UTF-8 which might
    #   otherwise need to use a 7-bit encoding, such as {modified
    #   UTF-7}[::decode_utf7] for mailbox names, or RFC2047 encoded-words for
    #   message headers.
    #
    #   *Note:* <em>A future update may set string encodings slightly
    #   differently</em>, e.g: "US-ASCII" when UTF-8 is not enabled, and "UTF-8"
    #   when it is.  Currently, the encoding of strings sent as "quoted" or
    #   "text" will _always_ be "UTF-8", even when only ASCII characters are
    #   used (e.g. "Subject: Agenda") And currently, string "literals" sent
    #   by the server will always have an "ASCII-8BIT" (binary)
    #   encoding, even if they generally contain UTF-8 data, if they are
    #   text at all.
    #
    # [<tt>"UTF8=ONLY"</tt> [RFC6855[https://www.rfc-editor.org/rfc/rfc6855]]]
    #
    #   A server that reports the <tt>UTF8=ONLY</tt> capability _requires_ that
    #   the client <tt>enable("UTF8=ACCEPT")</tt> before any mailboxes may be
    #   selected.  For convenience, <tt>enable("UTF8=ONLY")</tt> is aliased to
    #   <tt>enable("UTF8=ACCEPT")</tt>.
    #
    # [+UIDONLY+ {[RFC9586]}[https://www.rfc-editor.org/rfc/rfc9586.pdf]]
    #
    #   When UIDONLY is enabled, the #fetch, #store, #search, #copy, and #move
    #   commands are prohibited and result in a tagged BAD response. Clients
    #   should instead use uid_fetch, uid_store, uid_search, uid_copy, or
    #   uid_move, respectively. All +FETCH+ responses that would be returned are
    #   replaced by +UIDFETCH+ responses. All +EXPUNGED+ responses that would be
    #   returned are replaced by +VANISHED+ responses. The "<sequence set>"
    #   uid_search criterion is prohibited.
    #
    # ===== Unsupported capabilities
    #
    # *Note:* Some extensions that use ENABLE permit the server to send syntax
    # that Net::IMAP cannot parse, which may raise an exception and disconnect.
    # Some extensions may work, but the support may be incomplete, untested, or
    # experimental.
    #
    # Until a capability is documented here as supported, enabling it may result
    # in undocumented behavior and a future release may update with incompatible
    # behavior <em>without warning or deprecation</em>.
    #
    # <em>Caution is advised.</em>
    #
    def enable(*capabilities)
      capabilities = capabilities
        .flatten
        .map {|e| ENABLE_ALIASES[e] || e }
        .uniq
        .join(' ')
      synchronize do
        send_command("ENABLE #{capabilities}")
        result = clear_responses("ENABLED").last || []
        @utf8_strings ||= result.include? "UTF8=ACCEPT"
        @utf8_strings ||= result.include? "IMAP4REV2"
        result
      end
    end

    # Sends an {IDLE command [RFC2177 §3]}[https://www.rfc-editor.org/rfc/rfc6851#section-3]
    # {[IMAP4rev2 §6.3.13]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.13]
    # that waits for notifications of new or expunged messages.  Yields
    # responses from the server during the IDLE.
    #
    # Use #idle_done to leave IDLE.
    #
    # If +timeout+ is given, this method returns after +timeout+ seconds passed.
    # +timeout+ can be used for keep-alive.  For example, the following code
    # checks the connection for each 60 seconds.
    #
    #   loop do
    #     imap.idle(60) do |response|
    #       do_something_with(response)
    #       imap.idle_done if some_condition?(response)
    #     end
    #   end
    #
    # Returns the server's response to indicate the IDLE state has ended.
    # Returns +nil+ if the server does not respond to #idle_done within
    # {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout]
    # seconds.
    #
    # Related: #idle_done, #noop, #check
    #
    # ==== Capabilities
    #
    # The server's capabilities must include either +IMAP4rev2+ or +IDLE+
    # [RFC2177[https://www.rfc-editor.org/rfc/rfc2177]].
    def idle(timeout = nil, &response_handler)
      raise LocalJumpError, "no block given" unless response_handler

      response = nil

      synchronize do
        tag = Thread.current[:net_imap_tag] = generate_tag
        put_string("#{tag} IDLE#{CRLF}")

        begin
          add_response_handler(&response_handler)
          @idle_done_cond = new_cond
          @idle_done_cond.wait(timeout)
          @idle_done_cond = nil
          if @receiver_thread_terminating
            raise @exception || Net::IMAP::Error.new("connection closed")
          end
        ensure
          remove_response_handler(response_handler)
          unless @receiver_thread_terminating
            put_string("DONE#{CRLF}")
            response = get_tagged_response(tag, "IDLE", idle_response_timeout)
          end
        end
      end

      return response
    end

    # Leaves IDLE, allowing #idle to return.
    #
    # If the server does not respond within
    # {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout]
    # seconds, #idle will return +nil+.
    #
    # Related: #idle
    def idle_done
      synchronize do
        if @idle_done_cond.nil?
          raise Net::IMAP::Error, "not during IDLE"
        end
        @idle_done_cond.signal
      end
    end

    RESPONSES_DEPRECATION_MSG =
      "Pass a type or block to #responses, " \
      "set config.responses_without_block to :frozen_dup " \
      "or :silence_deprecation_warning, " \
      "or use #extract_responses or #clear_responses."
    private_constant :RESPONSES_DEPRECATION_MSG

    # :call-seq:
    #   responses       -> hash of {String => Array} (see config.responses_without_block)
    #   responses(type) -> frozen array
    #   responses       {|hash|  ...} -> block result
    #   responses(type) {|array| ...} -> block result
    #
    # Yields or returns unhandled server responses.  Unhandled responses are
    # stored in a hash, with arrays of UntaggedResponse#data keyed by
    # UntaggedResponse#name and <em>non-+nil+</em> untagged ResponseCode#data
    # keyed by ResponseCode#name.
    #
    # When a block is given, yields unhandled responses and returns the block's
    # result.  Without a block, returns the unhandled responses.
    #
    # [With +type+]
    #   Yield or return only the array of responses for that +type+.
    #   When no block is given, the returned array is a frozen copy.
    # [Without +type+]
    #   Yield or return the entire responses hash.
    #
    #   When no block is given, the behavior is determined by
    #   Config#responses_without_block:
    #   >>>
    #     [+:silence_deprecation_warning+ <em>(original behavior)</em>]
    #       Returns the mutable responses hash (without any warnings).
    #       <em>This is not thread-safe.</em>
    #
    #     [+:warn+ <em>(default since +v0.5+)</em>]
    #       Prints a warning and returns the mutable responses hash.
    #       <em>This is not thread-safe.</em>
    #
    #     [+:frozen_dup+ <em>(planned default for +v0.6+)</em>]
    #       Returns a frozen copy of the unhandled responses hash, with frozen
    #       array values.
    #
    #     [+:raise+]
    #       Raise an +ArgumentError+ with the deprecation warning.
    #
    # For example:
    #
    #   imap.select("inbox")
    #   p imap.responses("EXISTS").last
    #   #=> 2
    #   p imap.responses("UIDNEXT", &:last)
    #   #=> 123456
    #   p imap.responses("UIDVALIDITY", &:last)
    #   #=> 968263756
    #   p imap.responses {|responses|
    #     {
    #       exists:      responses.delete("EXISTS").last,
    #       uidnext:     responses.delete("UIDNEXT").last,
    #       uidvalidity: responses.delete("UIDVALIDITY").last,
    #     }
    #   }
    #   #=> {:exists=>2, :uidnext=>123456, :uidvalidity=>968263756}
    #   # "EXISTS", "UIDNEXT", and "UIDVALIDITY" have been removed:
    #   p imap.responses(&:keys)
    #   #=> ["FLAGS", "OK", "PERMANENTFLAGS", "RECENT", "HIGHESTMODSEQ"]
    #
    # Related: #extract_responses, #clear_responses, #response_handlers, #greeting
    #
    # ==== Thread safety
    # >>>
    #   *Note:* Access to the responses hash is synchronized for thread-safety.
    #   The receiver thread and response_handlers cannot process new responses
    #   until the block completes.  Accessing either the response hash or its
    #   response type arrays outside of the block is unsafe.  They can be safely
    #   updated inside the block.  Consider using #clear_responses or
    #   #extract_responses instead.
    #
    #   Net::IMAP will add and remove responses from the responses hash and its
    #   array values, in the calling threads for commands and in the receiver
    #   thread, but will not modify any responses after adding them to the
    #   responses hash.
    #
    # ==== Clearing responses
    #
    # Previously unhandled responses are automatically cleared before entering a
    # mailbox with #select or #examine.  Long-lived connections can receive many
    # unhandled server responses, which must be pruned or they will continually
    # consume more memory.  Update or clear the responses hash or arrays inside
    # the block, or remove responses with #extract_responses, #clear_responses,
    # or #add_response_handler.
    #
    # ==== Missing responses
    #
    # Only non-+nil+ data is stored.  Many important response codes have no data
    # of their own, but are used as "tags" on the ResponseText object they are
    # attached to.  ResponseText will be accessible by its response types:
    # "+OK+", "+NO+", "+BAD+", "+BYE+", or "+PREAUTH+".
    #
    # TaggedResponse#data is not saved to #responses, nor is any
    # ResponseCode#data on tagged responses.  Although some command methods do
    # return the TaggedResponse directly, #add_response_handler must be used to
    # handle all response codes.
    def responses(type = nil)
      if block_given?
        synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) }
      elsif type
        synchronize { @responses[type.to_s.upcase].dup.freeze }
      else
        case config.responses_without_block
        when :raise
          raise ArgumentError, RESPONSES_DEPRECATION_MSG
        when :warn
          warn(RESPONSES_DEPRECATION_MSG, uplevel: 1, category: :deprecated)
        when :frozen_dup
          synchronize {
            responses = @responses.transform_values { _1.dup.freeze }
            responses.default_proc = nil
            responses.default = [].freeze
            return responses.freeze
          }
        end
        @responses
      end
    end

    # :call-seq:
    #   clear_responses       -> hash
    #   clear_responses(type) -> array
    #
    # Clears and returns the unhandled #responses hash or the unhandled
    # responses array for a single response +type+.
    #
    # Clearing responses is synchronized with other threads.  The lock is
    # released before returning.
    #
    # Related: #extract_responses, #responses, #response_handlers
    def clear_responses(type = nil)
      synchronize {
        if type
          @responses.delete(type) || []
        else
          @responses.dup.transform_values(&:freeze)
            .tap { _1.default = [].freeze }
            .tap { @responses.clear }
        end
      }
        .freeze
    end

    # :call-seq:
    #   extract_responses(type) {|response| ... } -> array
    #
    # Yields all of the unhandled #responses for a single response +type+.
    # Removes and returns the responses for which the block returns a true
    # value.
    #
    # Extracting responses is synchronized with other threads.  The lock is
    # released before returning.
    #
    # Related: #responses, #clear_responses
    def extract_responses(type)
      type = String.try_convert(type) or
        raise ArgumentError, "type must be a string"
      raise ArgumentError, "must provide a block" unless block_given?
      extracted = []
      responses(type) do |all|
        all.reject! do |response|
          extracted << response if yield response
        end
      end
      extracted
    end

    # Returns all response handlers, including those that are added internally
    # by commands.  Each response handler will be called with every new
    # UntaggedResponse, TaggedResponse, and ContinuationRequest.
    #
    # Response handlers are called with a mutex inside the receiver thread.  New
    # responses cannot be processed and commands from other threads must wait
    # until all response_handlers return.  An exception will shut-down the
    # receiver thread and close the connection.
    #
    # For thread-safety, the returned array is a frozen copy of the internal
    # array.
    #
    # Related: #add_response_handler, #remove_response_handler
    def response_handlers
      synchronize { @response_handlers.clone.freeze }
    end

    # Adds a response handler. For example, to detect when
    # the server sends a new EXISTS response (which normally
    # indicates new messages being added to the mailbox),
    # add the following handler after selecting the
    # mailbox:
    #
    #   imap.add_response_handler { |resp|
    #     if resp.kind_of?(Net::IMAP::UntaggedResponse) and resp.name == "EXISTS"
    #       puts "Mailbox now has #{resp.data} messages"
    #     end
    #   }
    #
    # Response handlers can also be added when the client is created before the
    # receiver thread is started, by the +response_handlers+ argument to ::new.
    # This ensures every server response is handled, including the #greeting.
    #
    # Related: #remove_response_handler, #response_handlers
    def add_response_handler(handler = nil, &block)
      raise ArgumentError, "two Procs are passed" if handler && block
      synchronize do
        @response_handlers.push(block || handler)
      end
    end

    # Removes the response handler.
    #
    # Related: #add_response_handler, #response_handlers
    def remove_response_handler(handler)
      synchronize do
        @response_handlers.delete(handler)
      end
    end

    private

    CRLF = "\r\n"      # :nodoc:
    PORT = 143         # :nodoc:
    SSL_PORT = 993   # :nodoc:

    def start_imap_connection
      @greeting        = get_server_greeting
      @capabilities    = capabilities_from_resp_code @greeting
      @response_handlers.each do |handler| handler.call(@greeting) end
      @receiver_thread = start_receiver_thread
    rescue Exception
      state_logout!
      @sock.close
      raise
    end

    def get_server_greeting
      greeting = get_response
      raise Error, "No server greeting - connection closed" unless greeting
      record_untagged_response_code greeting
      case greeting.name
      when "PREAUTH" then state_authenticated!
      when "BYE"     then state_logout!; raise ByeResponseError, greeting
      end
      greeting
    end

    def start_receiver_thread
      Thread.start do
        receive_responses
      rescue Exception => ex
        @receiver_thread_exception = ex
        # don't exit the thread with an exception
      end
    end

    def tcp_socket(host, port)
      s = Socket.tcp(host, port, :connect_timeout => open_timeout)
      s.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, true)
      s
    rescue Errno::ETIMEDOUT
      raise Net::OpenTimeout, "Timeout to open TCP connection to " +
        "#{host}:#{port} (exceeds #{open_timeout} seconds)"
    end

    def receive_responses
      connection_closed = false
      until connection_closed
        synchronize do
          @exception = nil
        end
        begin
          resp = get_response
        rescue Exception => e
          synchronize do
            state_logout!
            @sock.close
            @exception = e
          end
          break
        end
        unless resp
          synchronize do
            @exception = EOFError.new("end of file reached")
          end
          break
        end
        begin
          synchronize do
            case resp
            when TaggedResponse
              @tagged_responses[resp.tag] = resp
              @tagged_response_arrival.broadcast
              case resp.tag
              when @logout_command_tag
                state_logout!
                return
              when @continued_command_tag
                @continuation_request_exception =
                  RESPONSE_ERRORS[resp.name].new(resp)
                @continuation_request_arrival.signal
              end
            when UntaggedResponse
              record_untagged_response(resp)
              if resp.name == "BYE" && @logout_command_tag.nil?
                state_logout!
                @sock.close
                @exception = ByeResponseError.new(resp)
                connection_closed = true
              end
            when ContinuationRequest
              @continuation_request_arrival.signal
            end
            state_unselected! if resp in {data: {code: {name: "CLOSED"}}}
            @response_handlers.each do |handler|
              handler.call(resp)
            end
          end
        rescue Exception => e
          @exception = e
          synchronize do
            @tagged_response_arrival.broadcast
            @continuation_request_arrival.broadcast
          end
        end
      end
      synchronize do
        @receiver_thread_terminating = true
        @tagged_response_arrival.broadcast
        @continuation_request_arrival.broadcast
        if @idle_done_cond
          @idle_done_cond.signal
        end
      end
    ensure
      state_logout!
    end

    def get_tagged_response(tag, cmd, timeout = nil)
      if timeout
        deadline = Time.now + timeout
      end
      until @tagged_responses.key?(tag)
        raise @exception if @exception
        if timeout
          timeout = deadline - Time.now
          if timeout <= 0
            return nil
          end
        end
        @tagged_response_arrival.wait(timeout)
      end
      resp = @tagged_responses.delete(tag)
      case resp.name
      when /\A(?:OK)\z/ni
        return resp
      when /\A(?:NO)\z/ni
        raise NoResponseError, resp
      when /\A(?:BAD)\z/ni
        raise BadResponseError, resp
      else
        disconnect
        raise InvalidResponseError, "invalid tagged resp: %p" % [resp.raw.chomp]
      end
    end

    def get_response
      buff = @reader.read_response_buffer
      return nil if buff.length == 0
      $stderr.print(buff.gsub(/^/n, "S: ")) if config.debug?
      @parser.parse(buff)
    end

    #############################
    # built-in response handlers

    # store name => [..., data]
    def record_untagged_response(resp)
      @responses[resp.name] << resp.data
      record_untagged_response_code resp
    end

    # store code.name => [..., code.data]
    def record_untagged_response_code(resp)
      return unless resp.data.is_a?(ResponseText)
      return unless (code = resp.data.code)
      @responses[code.name] << code.data
    end

    # NOTE: only call this for greeting, login, and authenticate
    def capabilities_from_resp_code(resp)
      return unless %w[PREAUTH OK].any? { _1.casecmp? resp.name }
      return unless (code = resp.data.code)
      return unless code.name.casecmp?("CAPABILITY")
      code.data.freeze
    end

    #############################

    # Calls send_command, yielding the text of each ContinuationRequest and
    # responding with each block result.  Returns TaggedResponse.  Raises
    # NoResponseError or BadResponseError.
    def send_command_with_continuations(cmd, *args)
      send_command(cmd, *args) do |server_response|
        if server_response.instance_of?(ContinuationRequest)
          client_response = yield server_response.data.text
          put_string(client_response + CRLF)
        end
      end
    end

    def send_command(cmd, *args, &block)
      synchronize do
        args.each do |i|
          validate_data(i)
        end
        tag = generate_tag
        put_string(tag + " " + cmd)
        args.each do |i|
          put_string(" ")
          send_data(i, tag)
        end
        put_string(CRLF)
        if cmd == "LOGOUT"
          @logout_command_tag = tag
        end
        if block
          add_response_handler(&block)
        end
        begin
          return get_tagged_response(tag, cmd)
        ensure
          if block
            remove_response_handler(block)
          end
        end
      end
    end

    def generate_tag
      @tagno += 1
      return format("%s%04d", @tag_prefix, @tagno)
    end

    def put_string(str)
      @sock.print(str)
      if config.debug?
        if @debug_output_bol
          $stderr.print("C: ")
        end
        $stderr.print(str.gsub(/\n/n) { $'.empty? ? $& : "\nC: " })
        if /\n\z/n.match(str)
          @debug_output_bol = true
        else
          @debug_output_bol = false
        end
      end
    end

    def enforce_logindisabled?
      may_depend_on_capabilities_cached?(config.enforce_logindisabled)
    end

    def may_depend_on_capabilities_cached?(value)
      value == :when_capabilities_cached ? capabilities_cached? : value
    end

    def expunge_internal(...)
      synchronize do
        send_command(...)
        expunged_array = clear_responses("EXPUNGE")
        vanished_array = extract_responses("VANISHED") { !_1.earlier? }
        if vanished_array.empty?
          expunged_array
        elsif vanished_array.length == 1
          vanished_array.first
        else
          merged_uids = SequenceSet[*vanished_array.map(&:uids)]
          VanishedData[uids: merged_uids, earlier: false]
        end
      end
    end

    RETURN_WHOLE = /\ARETURN\z/i
    RETURN_START = /\ARETURN\b/i
    private_constant :RETURN_WHOLE, :RETURN_START

    def search_args(keys, charset_arg = nil, return: nil, charset: nil)
      {return:} => {return: return_kw}
      case [return_kw, keys]
      in [nil, Array[RETURN_WHOLE, return_opts, *keys]]
        return_opts = convert_return_opts(return_opts)
        esearch = true
      in [nil => return_opts, RETURN_START]
        esearch = true
      in [nil => return_opts, keys]
        esearch = false
      in [_, Array[RETURN_WHOLE, _, *] | RETURN_START]
        raise ArgumentError, "conflicting return options"
      in [_, Array[RETURN_WHOLE, _, *]] # workaround for https://bugs.ruby-lang.org/issues/20956
        raise ArgumentError, "conflicting return options"
      in [_, RETURN_START]              # workaround for https://bugs.ruby-lang.org/issues/20956
        raise ArgumentError, "conflicting return options"
      in [return_opts, keys]
        return_opts = convert_return_opts(return_opts)
        esearch = true
      end
      if charset && charset_arg
        raise ArgumentError, "multiple charset arguments"
      end
      charset ||= charset_arg
      # NOTE: not handling combined RETURN and CHARSET for raw strings
      if charset && keys in /\ACHARSET\b/i | Array[/\ACHARSET\z/i, *]
        raise ArgumentError, "multiple charset arguments"
      end
      args = normalize_searching_criteria(keys)
      args.prepend("CHARSET", charset)     if charset
      args.prepend("RETURN",  return_opts) if return_opts
      return args, esearch
    end

    def convert_return_opts(unconverted)
      return_opts = Array.try_convert(unconverted) or
        raise TypeError, "expected return options to be Array, got %s" % [
          unconverted.class
        ]
      return_opts.map {|opt|
        case opt
        when Symbol                 then opt.to_s
        when PartialRange::Negative then PartialRange[opt]
        when Range                  then SequenceSet[opt]
        else                             opt
        end
      }
    end

    def search_internal(cmd, ...)
      args, esearch = search_args(...)
      synchronize do
        tagged = send_command(cmd, *args)
        tag    = tagged.tag
        # Only the last ESEARCH or SEARCH is used.  Excess results are ignored.
        esearch_result = extract_responses("ESEARCH") {|response|
          response in ESearchResult(tag: ^tag)
        }.last
        search_result = clear_responses("SEARCH").last
        if esearch_result
          # silently ignore SEARCH results, if any
          esearch_result
        elsif search_result
          # warn EXPECTED_ESEARCH_RESULT if esearch
          search_result
        elsif esearch
          # warn NO_SEARCH_RESPONSE
          ESearchResult[tag:, uid: cmd.start_with?("UID ")]
        else
          # warn NO_SEARCH_RESPONSE
          SearchResult[]
        end
      end
    end

    def fetch_internal(cmd, set, attr, mod = nil, partial: nil, changedsince: nil)
      if partial && !cmd.start_with?("UID ")
        raise ArgumentError, "partial can only be used with uid_fetch"
      end
      set = SequenceSet[set]
      if partial
        mod ||= []
        mod << "PARTIAL" << PartialRange[partial]
      end
      if changedsince
        mod ||= []
        mod << "CHANGEDSINCE" << Integer(changedsince)
      end
      case attr
      when String then
        attr = RawData.new(attr)
      when Array then
        attr = attr.map { |arg|
          arg.is_a?(String) ? RawData.new(arg) : arg
        }
      end

      args = [cmd, set, attr]
      args << mod if mod
      send_command_returning_fetch_results(*args)
    end

    def store_internal(cmd, set, attr, flags, unchangedsince: nil)
      attr = RawData.new(attr) if attr.instance_of?(String)
      args = [SequenceSet.new(set)]
      args << ["UNCHANGEDSINCE", Integer(unchangedsince)] if unchangedsince
      args << attr << flags
      send_command_returning_fetch_results(cmd, *args)
    end

    def send_command_returning_fetch_results(...)
      synchronize do
        clear_responses("FETCH")
        clear_responses("UIDFETCH")
        send_command(...)
        fetches    = clear_responses("FETCH")
        uidfetches = clear_responses("UIDFETCH")
        uidfetches.any? ? uidfetches : fetches
      end
    end

    def copy_internal(cmd, set, mailbox)
      send_command(cmd, SequenceSet.new(set), mailbox)
    end

    def sort_internal(cmd, sort_keys, search_keys, charset)
      search_keys = normalize_searching_criteria(search_keys)
      synchronize do
        send_command(cmd, sort_keys, charset, *search_keys)
        clear_responses("SORT").last || []
      end
    end

    def thread_internal(cmd, algorithm, search_keys, charset)
      search_keys = normalize_searching_criteria(search_keys)
      synchronize do
        send_command(cmd, algorithm, charset, *search_keys)
        clear_responses("THREAD").last || []
      end
    end

    def normalize_searching_criteria(criteria)
      return [RawData.new(criteria)] if criteria.is_a?(String)
      criteria.map {|i|
        if coerce_search_arg_to_seqset?(i)
          SequenceSet[i]
        else
          i
        end
      }
    end

    def coerce_search_arg_to_seqset?(obj)
      case obj
      when Set, -1, :* then true
      when Range       then true
      when Array       then obj.all? { coerce_search_array_arg_to_seqset? _1 }
      else                  obj.respond_to?(:to_sequence_set)
      end
    end

    def coerce_search_array_arg_to_seqset?(obj)
      case obj
      when Integer then obj.positive? || obj == -1
      when String  then ResponseParser::Patterns::SEQUENCE_SET_STR.match?(obj.b)
      else
        coerce_search_arg_to_seqset?(obj)
      end
    end

    def build_ssl_ctx(ssl)
      if ssl
        params = (Hash.try_convert(ssl) || {}).freeze
        context = OpenSSL::SSL::SSLContext.new
        context.set_params(params)
        context.freeze
        [params, context]
      else
        false
      end
    end

    def start_tls_session
      raise "SSL extension not installed" unless defined?(OpenSSL::SSL)
      raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
      raise "cannot start TLS without SSLContext" unless ssl_ctx
      @sock = OpenSSL::SSL::SSLSocket.new(@sock, ssl_ctx)
      @reader = ResponseReader.new(self, @sock)
      @sock.sync_close = true
      @sock.hostname = @host if @sock.respond_to? :hostname=
      ssl_socket_connect(@sock, open_timeout)
      if ssl_ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE
        @sock.post_connection_check(@host)
        @tls_verified = true
      end
    end

    def state_authenticated!(resp = nil)
      synchronize do
        @capabilities = capabilities_from_resp_code resp if resp
        @connection_state = ConnectionState::Authenticated.new
      end
    end

    def state_selected!
      synchronize do
        @connection_state = ConnectionState::Selected.new
      end
    end

    def state_unselected!
      synchronize do
        state_authenticated! if connection_state.to_sym == :selected
      end
    end

    def state_logout!
      return true if connection_state in [:logout, *]
      synchronize do
        return true if connection_state in [:logout, *]
        @connection_state = ConnectionState::Logout.new
      end
    end

    # don't wait to aqcuire the lock
    def try_state_logout?
      return true if connection_state in [:logout, *]
      return false unless acquired_lock = mon_try_enter
      state_logout!
      true
    ensure
      mon_exit if acquired_lock
    end

    def sasl_adapter
      SASLAdapter.new(self, &method(:send_command_with_continuations))
    end

    #--
    # We could get the saslprep method by extending the SASLprep module
    # directly.  It's done indirectly, so SASLprep can be lazily autoloaded,
    # because most users won't need it.
    #++
    # Delegates to Net::IMAP::StringPrep::SASLprep#saslprep.
    def self.saslprep(string, **opts)
      Net::IMAP::StringPrep::SASLprep.saslprep(string, **opts)
    end

  end
end

require_relative "imap/errors"
require_relative "imap/config"
require_relative "imap/command_data"
require_relative "imap/data_encoding"
require_relative "imap/flags"
require_relative "imap/response_data"
require_relative "imap/response_parser"
require_relative "imap/authenticators"

require_relative "imap/deprecated_client_options"
Net::IMAP.prepend Net::IMAP::DeprecatedClientOptions
