//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once

#include "td/telegram/secret_api.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"

#include "td/actor/MultiPromise.h"
#include "td/actor/PromiseFuture.h"
#include "td/actor/SignalSlot.h"
#include "td/actor/Timeout.h"

#include "td/db/binlog/BinlogEvent.h"

#include "td/telegram/AccessRights.h"
#include "td/telegram/CallDiscardReason.h"
#include "td/telegram/ChannelId.h"
#include "td/telegram/ChatId.h"
#include "td/telegram/Contact.h"
#include "td/telegram/DialogId.h"
#include "td/telegram/DialogParticipant.h"
#include "td/telegram/DocumentsManager.h"
#include "td/telegram/files/FileId.h"
#include "td/telegram/Game.h"
#include "td/telegram/Global.h"
#include "td/telegram/Location.h"
#include "td/telegram/MessageEntity.h"
#include "td/telegram/MessageId.h"
#include "td/telegram/MessagesDb.h"
#include "td/telegram/net/NetQuery.h"
#include "td/telegram/Payments.h"
#include "td/telegram/Photo.h"
#include "td/telegram/ReplyMarkup.h"
#include "td/telegram/SecretChatId.h"
#include "td/telegram/SecretInputMedia.h"
#include "td/telegram/UserId.h"
#include "td/telegram/WebPageId.h"

#include "td/utils/buffer.h"
#include "td/utils/ChangesProcessor.h"
#include "td/utils/common.h"
#include "td/utils/Heap.h"
#include "td/utils/Hints.h"
#include "td/utils/logging.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/tl_storers.h"

#include <array>
#include <functional>
#include <limits>
#include <map>
#include <memory>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>

namespace td {

class Td;

class MultiSequenceDispatcher;

// Do not forget to update MessagesManager::update_message_content when one of the inheritors of this class changes
class MessageContent {
 public:
  virtual int32 get_id() const = 0;
  virtual ~MessageContent() = default;
};

class MessageText : public MessageContent {
 public:
  FormattedText text;
  WebPageId web_page_id;

  MessageText() = default;
  MessageText(FormattedText text, WebPageId web_page_id) : text(std::move(text)), web_page_id(web_page_id) {
  }

  static const int32 ID = 0;
  int32 get_id() const override {
    return ID;
  }
};

class MessageAnimation : public MessageContent {
 public:
  FileId file_id;

  FormattedText caption;

  MessageAnimation() = default;
  MessageAnimation(FileId file_id, FormattedText &&caption) : file_id(file_id), caption(std::move(caption)) {
  }

  static const int32 ID = 1;
  int32 get_id() const override {
    return ID;
  }
};

class MessageAudio : public MessageContent {
 public:
  FileId file_id;

  FormattedText caption;

  MessageAudio() = default;
  MessageAudio(FileId file_id, FormattedText &&caption) : file_id(file_id), caption(std::move(caption)) {
  }

  static const int32 ID = 2;
  int32 get_id() const override {
    return ID;
  }
};

class MessageDocument : public MessageContent {
 public:
  FileId file_id;

  FormattedText caption;

  MessageDocument() = default;
  MessageDocument(FileId file_id, FormattedText &&caption) : file_id(file_id), caption(std::move(caption)) {
  }

  static const int32 ID = 3;
  int32 get_id() const override {
    return ID;
  }
};

class MessagePhoto : public MessageContent {
 public:
  Photo photo;

  FormattedText caption;

  MessagePhoto() = default;
  MessagePhoto(Photo &&photo, FormattedText &&caption) : photo(std::move(photo)), caption(std::move(caption)) {
  }

  static const int32 ID = 4;
  int32 get_id() const override {
    return ID;
  }
};

class MessageSticker : public MessageContent {
 public:
  FileId file_id;

  MessageSticker() = default;
  explicit MessageSticker(FileId file_id) : file_id(file_id) {
  }

  static const int32 ID = 5;
  int32 get_id() const override {
    return ID;
  }
};

class MessageVideo : public MessageContent {
 public:
  FileId file_id;

  FormattedText caption;

  MessageVideo() = default;
  MessageVideo(FileId file_id, FormattedText &&caption) : file_id(file_id), caption(std::move(caption)) {
  }

  static const int32 ID = 6;
  int32 get_id() const override {
    return ID;
  }
};

class MessageVoiceNote : public MessageContent {
 public:
  FileId file_id;

  FormattedText caption;
  bool is_listened;

  MessageVoiceNote() = default;
  MessageVoiceNote(FileId file_id, FormattedText &&caption, bool is_listened)
      : file_id(file_id), caption(std::move(caption)), is_listened(is_listened) {
  }

  static const int32 ID = 7;
  int32 get_id() const override {
    return ID;
  }
};

class MessageContact : public MessageContent {
 public:
  Contact contact;

  MessageContact() = default;
  explicit MessageContact(Contact &&contact) : contact(std::move(contact)) {
  }

  static const int32 ID = 8;
  int32 get_id() const override {
    return ID;
  }
};

class MessageLocation : public MessageContent {
 public:
  Location location;

  MessageLocation() = default;
  explicit MessageLocation(Location &&location) : location(std::move(location)) {
  }

  static const int32 ID = 9;
  int32 get_id() const override {
    return ID;
  }
};

class MessageVenue : public MessageContent {
 public:
  Venue venue;

  MessageVenue() = default;
  explicit MessageVenue(Venue &&venue) : venue(std::move(venue)) {
  }

  static const int32 ID = 10;
  int32 get_id() const override {
    return ID;
  }
};

class MessageChatCreate : public MessageContent {
 public:
  string title;
  vector<UserId> participant_user_ids;

  MessageChatCreate() = default;
  MessageChatCreate(string &&title, vector<UserId> &&participant_user_ids)
      : title(std::move(title)), participant_user_ids(std::move(participant_user_ids)) {
  }

  static const int32 ID = 11;
  int32 get_id() const override {
    return ID;
  }
};

class MessageChatChangeTitle : public MessageContent {
 public:
  string title;

  MessageChatChangeTitle() = default;
  explicit MessageChatChangeTitle(string &&title) : title(std::move(title)) {
  }

  static const int32 ID = 12;
  int32 get_id() const override {
    return ID;
  }
};

class MessageChatChangePhoto : public MessageContent {
 public:
  Photo photo;

  MessageChatChangePhoto() = default;
  explicit MessageChatChangePhoto(Photo &&photo) : photo(std::move(photo)) {
  }

  static const int32 ID = 13;
  int32 get_id() const override {
    return ID;
  }
};

class MessageChatDeletePhoto : public MessageContent {
 public:
  static const int32 ID = 14;
  int32 get_id() const override {
    return ID;
  }
};

class MessageChatDeleteHistory : public MessageContent {
 public:
  static const int32 ID = 15;
  int32 get_id() const override {
    return ID;
  }
};

class MessageChatAddUsers : public MessageContent {
 public:
  vector<UserId> user_ids;

  MessageChatAddUsers() = default;
  explicit MessageChatAddUsers(vector<UserId> &&user_ids) : user_ids(std::move(user_ids)) {
  }

  static const int32 ID = 16;
  int32 get_id() const override {
    return ID;
  }
};

class MessageChatJoinedByLink : public MessageContent {
 public:
  static const int32 ID = 17;
  int32 get_id() const override {
    return ID;
  }
};

class MessageChatDeleteUser : public MessageContent {
 public:
  UserId user_id;

  MessageChatDeleteUser() = default;
  explicit MessageChatDeleteUser(UserId user_id) : user_id(user_id) {
  }

  static const int32 ID = 18;
  int32 get_id() const override {
    return ID;
  }
};

class MessageChatMigrateTo : public MessageContent {
 public:
  ChannelId migrated_to_channel_id;

  MessageChatMigrateTo() = default;
  explicit MessageChatMigrateTo(ChannelId migrated_to_channel_id) : migrated_to_channel_id(migrated_to_channel_id) {
  }

  static const int32 ID = 19;
  int32 get_id() const override {
    return ID;
  }
};

class MessageChannelCreate : public MessageContent {
 public:
  string title;

  MessageChannelCreate() = default;
  explicit MessageChannelCreate(string &&title) : title(std::move(title)) {
  }

  static const int32 ID = 20;
  int32 get_id() const override {
    return ID;
  }
};

class MessageChannelMigrateFrom : public MessageContent {
 public:
  string title;
  ChatId migrated_from_chat_id;

  MessageChannelMigrateFrom() = default;
  MessageChannelMigrateFrom(string &&title, ChatId migrated_from_chat_id)
      : title(std::move(title)), migrated_from_chat_id(migrated_from_chat_id) {
  }

  static const int32 ID = 21;
  int32 get_id() const override {
    return ID;
  }
};

class MessagePinMessage : public MessageContent {
 public:
  MessageId message_id;

  MessagePinMessage() = default;
  explicit MessagePinMessage(MessageId message_id) : message_id(message_id) {
  }

  static const int32 ID = 22;
  int32 get_id() const override {
    return ID;
  }
};

class MessageGame : public MessageContent {
 public:
  Game game;

  MessageGame() = default;
  explicit MessageGame(Game &&game) : game(std::move(game)) {
  }

  static const int32 ID = 23;
  int32 get_id() const override {
    return ID;
  }
};

class MessageGameScore : public MessageContent {
 public:
  MessageId game_message_id;
  int64 game_id;
  int32 score;

  MessageGameScore() = default;
  MessageGameScore(MessageId game_message_id, int64 game_id, int32 score)
      : game_message_id(game_message_id), game_id(game_id), score(score) {
  }

  static const int32 ID = 24;
  int32 get_id() const override {
    return ID;
  }
};

class MessageScreenshotTaken : public MessageContent {
 public:
  static const int32 ID = 25;
  int32 get_id() const override {
    return ID;
  }
};

class MessageChatSetTtl : public MessageContent {
 public:
  int32 ttl;

  MessageChatSetTtl() = default;
  explicit MessageChatSetTtl(int32 ttl) : ttl(ttl) {
  }

  static const int32 ID = 26;
  int32 get_id() const override {
    return ID;
  }
};

class MessageUnsupported
    : public MessageContent {  // TODO save a layer in which the message was received to
                               // automatically reget it if the layer changes
 public:
  static const int32 ID = 27;
  int32 get_id() const override {
    return ID;
  }
};

class MessageCall : public MessageContent {
 public:
  int64 call_id;
  int32 duration;
  CallDiscardReason discard_reason;

  MessageCall() = default;
  MessageCall(int64 call_id, int32 duration, CallDiscardReason discard_reason)
      : call_id(call_id), duration(duration), discard_reason(discard_reason) {
  }

  static const int32 ID = 28;
  int32 get_id() const override {
    return ID;
  }
};

class MessageInvoice : public MessageContent {
 public:
  string title;
  string description;
  Photo photo;
  string start_parameter;

  // InputMessageInvoice
  Invoice invoice;
  string payload;
  string provider_token;
  string provider_data;

  // MessageInvoice
  int64 total_amount = 0;
  MessageId receipt_message_id;

  MessageInvoice() = default;
  MessageInvoice(string &&title, string &&description, Photo &&photo, string &&start_parameter, int64 total_amount,
                 string &&currency, bool is_test, bool need_shipping_address, MessageId receipt_message_id)
      : title(std::move(title))
      , description(std::move(description))
      , photo(std::move(photo))
      , start_parameter(std::move(start_parameter))
      , invoice(std::move(currency), is_test, need_shipping_address)
      , payload()
      , provider_token()
      , provider_data()
      , total_amount(total_amount)
      , receipt_message_id(receipt_message_id) {
  }

  static const int32 ID = 29;
  int32 get_id() const override {
    return ID;
  }
};

class MessagePaymentSuccessful : public MessageContent {
 public:
  MessageId invoice_message_id;
  string currency;
  int64 total_amount = 0;

  // bots only part
  string invoice_payload;
  string shipping_option_id;
  unique_ptr<OrderInfo> order_info;
  string telegram_payment_charge_id;
  string provider_payment_charge_id;

  MessagePaymentSuccessful() = default;
  MessagePaymentSuccessful(MessageId invoice_message_id, string &&currency, int64 total_amount)
      : invoice_message_id(invoice_message_id), currency(std::move(currency)), total_amount(total_amount) {
  }

  static const int32 ID = 30;
  int32 get_id() const override {
    return ID;
  }
};

class MessageVideoNote : public MessageContent {
 public:
  FileId file_id;

  bool is_viewed = false;

  MessageVideoNote() = default;
  MessageVideoNote(FileId file_id, bool is_viewed) : file_id(file_id), is_viewed(is_viewed) {
  }

  static const int32 ID = 31;
  int32 get_id() const override {
    return ID;
  }
};

class MessageContactRegistered : public MessageContent {
 public:
  static const int32 ID = 32;
  int32 get_id() const override {
    return ID;
  }
};

class MessageExpiredPhoto : public MessageContent {
 public:
  MessageExpiredPhoto() = default;

  static const int32 ID = 33;
  int32 get_id() const override {
    return ID;
  }
};

class MessageExpiredVideo : public MessageContent {
 public:
  MessageExpiredVideo() = default;

  static const int32 ID = 34;
  int32 get_id() const override {
    return ID;
  }
};

class MessageLiveLocation : public MessageContent {
 public:
  Location location;
  int32 period;

  MessageLiveLocation() = default;
  MessageLiveLocation(Location &&location, int32 period) : location(std::move(location)), period(period) {
  }

  static const int32 ID = 35;
  int32 get_id() const override {
    return ID;
  }
};

class MessageCustomServiceAction : public MessageContent {
 public:
  string message;

  MessageCustomServiceAction() = default;
  explicit MessageCustomServiceAction(string &&message) : message(std::move(message)) {
  }

  static const int32 ID = 36;
  int32 get_id() const override {
    return ID;
  }
};

class InputMessageText {
 public:
  FormattedText text;
  bool disable_web_page_preview = false;
  bool clear_draft = false;
  InputMessageText() = default;
  InputMessageText(FormattedText text, bool disable_web_page_preview, bool clear_draft)
      : text(std::move(text)), disable_web_page_preview(disable_web_page_preview), clear_draft(clear_draft) {
  }
};

bool operator==(const InputMessageText &lhs, const InputMessageText &rhs);
bool operator!=(const InputMessageText &lhs, const InputMessageText &rhs);

class DraftMessage {
 public:
  int32 date;
  MessageId reply_to_message_id;
  InputMessageText input_message_text;
};

using NotificationSettingsScope = int64;
constexpr NotificationSettingsScope NOTIFICATION_SETTINGS_FOR_PRIVATE_CHATS =
    std::numeric_limits<NotificationSettingsScope>::min();
constexpr NotificationSettingsScope NOTIFICATION_SETTINGS_FOR_GROUP_CHATS =
    std::numeric_limits<NotificationSettingsScope>::min() + 1;
constexpr NotificationSettingsScope NOTIFICATION_SETTINGS_FOR_ALL_CHATS =
    std::numeric_limits<NotificationSettingsScope>::min() + 2;

class NotificationSettings {
 public:
  int32 mute_until = 0;
  string sound = "default";
  bool show_preview = true;
  bool silent_send_message = false;
  bool is_synchronized = false;

  NotificationSettings() = default;

  NotificationSettings(int32 mute_until, string sound, bool show_preview, bool silent_send_message)
      : mute_until(mute_until)
      , sound(std::move(sound))
      , show_preview(show_preview)
      , silent_send_message(silent_send_message)
      , is_synchronized(true) {
  }
};

inline constexpr size_t search_messages_filter_size() {
  return static_cast<int32>(SearchMessagesFilter::Size) - 1;
}

inline int32 search_messages_filter_index(SearchMessagesFilter filter) {
  CHECK(filter != SearchMessagesFilter::Empty);
  return static_cast<int32>(filter) - 1;
}

inline int32 search_messages_filter_index_mask(SearchMessagesFilter filter) {
  if (filter == SearchMessagesFilter::Empty) {
    return 0;
  }
  return 1 << search_messages_filter_index(filter);
}

inline int32 search_calls_filter_index(SearchMessagesFilter filter) {
  CHECK(filter == SearchMessagesFilter::Call || filter == SearchMessagesFilter::MissedCall);
  return static_cast<int32>(filter) - static_cast<int32>(SearchMessagesFilter::Call);
}

class DialogDate {
  int64 order;
  DialogId dialog_id;

 public:
  DialogDate(int64 order, DialogId dialog_id) : order(order), dialog_id(dialog_id) {
  }

  bool operator<(const DialogDate &other) const {
    return order > other.order || (order == other.order && dialog_id.get() > other.dialog_id.get());
  }

  bool operator<=(const DialogDate &other) const {
    return order >= other.order && (order != other.order || dialog_id.get() >= other.dialog_id.get());
  }

  bool operator==(const DialogDate &other) const {
    return order == other.order && dialog_id == other.dialog_id;
  }

  bool operator!=(const DialogDate &other) const {
    return order != other.order || dialog_id != other.dialog_id;
  }

  int64 get_order() const {
    return order;
  }
  DialogId get_dialog_id() const {
    return dialog_id;
  }
  int32 get_date() const {
    return static_cast<int32>((order >> 32) & 0x7FFFFFFF);
  }
  MessageId get_message_id() const {
    return MessageId(ServerMessageId(static_cast<int32>(order & 0x7FFFFFFF)));
  }

  friend struct DialogDateHash;

  friend StringBuilder &operator<<(StringBuilder &string_builder, DialogDate dialog_date);
};

const DialogDate MIN_DIALOG_DATE(std::numeric_limits<int64>::max(), DialogId());
const DialogDate MAX_DIALOG_DATE(0, DialogId());
const int64 DEFAULT_ORDER = -1;

struct DialogDateHash {
  std::size_t operator()(const DialogDate &dialog_date) const {
    return std::hash<int64>()(dialog_date.order) * 2023654985u + DialogIdHash()(dialog_date.dialog_id);
  }
};

inline StringBuilder &operator<<(StringBuilder &string_builder, DialogDate dialog_date) {
  return string_builder << "[" << dialog_date.order << ", " << dialog_date.dialog_id.get() << "]";
}

class dummyUpdate : public telegram_api::Update {
 public:
  static constexpr int32 ID = 1234567891;
  int32 get_id() const override {
    return ID;
  }

  void store(TlStorerUnsafe &s) const override {
    UNREACHABLE();
  }

  void store(TlStorerCalcLength &s) const override {
    UNREACHABLE();
  }

  void store(TlStorerToString &s, const char *field_name) const override;
};

class Dependencies {
 public:
  std::unordered_set<UserId, UserIdHash> user_ids;
  std::unordered_set<ChatId, ChatIdHash> chat_ids;
  std::unordered_set<ChannelId, ChannelIdHash> channel_ids;
  std::unordered_set<SecretChatId, SecretChatIdHash> secret_chat_ids;
  std::unordered_set<DialogId, DialogIdHash> dialog_ids;
  std::unordered_set<WebPageId, WebPageIdHash> web_page_ids;
};

struct CallsDbState {
  std::array<MessageId, 2> first_calls_database_message_id_by_index;
  std::array<int32, 2> message_count_by_index;
};

class MessagesManager : public Actor {
 public:
  //  static constexpr int32 MESSAGE_FLAG_IS_UNREAD = 1 << 0;
  static constexpr int32 MESSAGE_FLAG_IS_OUT = 1 << 1;
  static constexpr int32 MESSAGE_FLAG_IS_FORWARDED = 1 << 2;
  static constexpr int32 MESSAGE_FLAG_IS_REPLY = 1 << 3;
  static constexpr int32 MESSAGE_FLAG_HAS_MENTION = 1 << 4;
  static constexpr int32 MESSAGE_FLAG_HAS_UNREAD_CONTENT = 1 << 5;
  static constexpr int32 MESSAGE_FLAG_HAS_REPLY_MARKUP = 1 << 6;
  static constexpr int32 MESSAGE_FLAG_HAS_ENTITIES = 1 << 7;
  static constexpr int32 MESSAGE_FLAG_HAS_FROM_ID = 1 << 8;
  static constexpr int32 MESSAGE_FLAG_HAS_MEDIA = 1 << 9;
  static constexpr int32 MESSAGE_FLAG_HAS_VIEWS = 1 << 10;
  static constexpr int32 MESSAGE_FLAG_IS_SENT_VIA_BOT = 1 << 11;
  static constexpr int32 MESSAGE_FLAG_IS_SILENT = 1 << 13;
  static constexpr int32 MESSAGE_FLAG_IS_POST = 1 << 14;
  static constexpr int32 MESSAGE_FLAG_HAS_EDIT_DATE = 1 << 15;
  static constexpr int32 MESSAGE_FLAG_HAS_AUTHOR_SIGNATURE = 1 << 16;
  static constexpr int32 MESSAGE_FLAG_HAS_MEDIA_ALBUM_ID = 1 << 17;

  static constexpr int32 MESSAGE_FORWARD_HEADER_FLAG_HAS_AUTHOR_ID = 1 << 0;
  static constexpr int32 MESSAGE_FORWARD_HEADER_FLAG_HAS_CHANNEL_ID = 1 << 1;
  static constexpr int32 MESSAGE_FORWARD_HEADER_FLAG_HAS_MESSAGE_ID = 1 << 2;
  static constexpr int32 MESSAGE_FORWARD_HEADER_FLAG_HAS_AUTHOR_SIGNATURE = 1 << 3;
  static constexpr int32 MESSAGE_FORWARD_HEADER_FLAG_HAS_SAVED_FROM = 1 << 4;

  static constexpr int32 SEND_MESSAGE_FLAG_IS_REPLY = 1 << 0;
  static constexpr int32 SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW = 1 << 1;
  static constexpr int32 SEND_MESSAGE_FLAG_HAS_REPLY_MARKUP = 1 << 2;
  static constexpr int32 SEND_MESSAGE_FLAG_HAS_ENTITIES = 1 << 3;
  //  static constexpr int32 SEND_MESSAGE_FLAG_IS_POST = 1 << 4;
  static constexpr int32 SEND_MESSAGE_FLAG_DISABLE_NOTIFICATION = 1 << 5;
  static constexpr int32 SEND_MESSAGE_FLAG_FROM_BACKGROUND = 1 << 6;
  static constexpr int32 SEND_MESSAGE_FLAG_CLEAR_DRAFT = 1 << 7;
  static constexpr int32 SEND_MESSAGE_FLAG_WITH_MY_SCORE = 1 << 8;
  static constexpr int32 SEND_MESSAGE_FLAG_GROUP_MEDIA = 1 << 9;
  static constexpr int32 SEND_MESSAGE_FLAG_HAS_MESSAGE = 1 << 11;

  MessagesManager(Td *td, ActorShared<> parent);
  MessagesManager(const MessagesManager &) = delete;
  MessagesManager &operator=(const MessagesManager &) = delete;
  MessagesManager(MessagesManager &&) = delete;
  MessagesManager &operator=(MessagesManager &&) = delete;
  ~MessagesManager() override;

  static vector<MessageId> get_message_ids(const vector<int64> &input_message_ids);

  static vector<int32> get_server_message_ids(const vector<MessageId> &message_ids);

  static tl_object_ptr<telegram_api::InputMessage> get_input_message(MessageId message_id);

  static MessageId get_message_id(const tl_object_ptr<telegram_api::Message> &message_ptr);

  DialogId get_message_dialog_id(const tl_object_ptr<telegram_api::Message> &message_ptr) const;

  FullMessageId get_full_message_id(const tl_object_ptr<telegram_api::Message> &message_ptr) const;

  static int32 get_message_date(const tl_object_ptr<telegram_api::Message> &message_ptr);

  tl_object_ptr<telegram_api::InputPeer> get_input_peer(DialogId dialog_id, AccessRights access_rights) const;

  vector<tl_object_ptr<telegram_api::InputPeer>> get_input_peers(const vector<DialogId> &dialog_ids,
                                                                 AccessRights access_rights) const;

  tl_object_ptr<telegram_api::inputEncryptedChat> get_input_encrypted_chat(DialogId dialog_id,
                                                                           AccessRights access_rights) const;

  bool have_input_peer(DialogId dialog_id, AccessRights access_rights) const;

  void on_get_messages(vector<tl_object_ptr<telegram_api::Message>> &&messages, bool is_channel_message,
                       const char *source);

  void on_get_history(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit, bool from_the_end,
                      vector<tl_object_ptr<telegram_api::Message>> &&messages);

  void on_get_public_dialogs_search_result(const string &query, vector<tl_object_ptr<telegram_api::Peer>> &&my_peers,
                                           vector<tl_object_ptr<telegram_api::Peer>> &&peers);
  void on_failed_public_dialogs_search(const string &query, Status &&error);

  void on_get_dialog_messages_search_result(DialogId dialog_id, const string &query, UserId sender_user_id,
                                            MessageId from_message_id, int32 offset, int32 limit,
                                            SearchMessagesFilter filter, int64 random_id, int32 total_count,
                                            vector<tl_object_ptr<telegram_api::Message>> &&messages);
  void on_failed_dialog_messages_search(DialogId dialog_id, int64 random_id);

  void on_get_messages_search_result(const string &query, int32 offset_date, DialogId offset_dialog_id,
                                     MessageId offset_message_id, int32 limit, int64 random_id, int32 total_count,
                                     vector<tl_object_ptr<telegram_api::Message>> &&messages);
  void on_failed_messages_search(int64 random_id);

  void on_get_recent_locations(DialogId dialog_id, int32 limit, int64 random_id, int32 total_count,
                               vector<tl_object_ptr<telegram_api::Message>> &&messages);
  void on_get_recent_locations_failed(int64 random_id);

  // if message is from_update, flags have_previous and have_next are ignored and should be both true
  FullMessageId on_get_message(tl_object_ptr<telegram_api::Message> message_ptr, bool from_update,
                               bool is_channel_message, bool have_previous, bool have_next, const char *source);

  void open_secret_message(SecretChatId secret_chat_id, int64 random_id, Promise<>);

  void on_send_secret_message_success(int64 random_id, MessageId message_id, int32 date,
                                      tl_object_ptr<telegram_api::EncryptedFile> file_ptr, Promise<> promise);
  void on_send_secret_message_error(int64 random_id, Status error, Promise<> promise);

  void delete_secret_messages(SecretChatId secret_chat_id, std::vector<int64> random_ids, Promise<> promise);

  void delete_secret_chat_history(SecretChatId secret_chat_id, MessageId last_message_id, Promise<> promise);

  void read_secret_chat_outbox(SecretChatId secret_chat_id, int32 up_to_date, int32 read_date);

  void on_get_secret_message(SecretChatId secret_chat_id, UserId user_id, MessageId message_id, int32 date,
                             tl_object_ptr<telegram_api::encryptedFile> file,
                             tl_object_ptr<secret_api::decryptedMessage> message, Promise<> promise);

  void on_secret_chat_screenshot_taken(SecretChatId secret_chat_id, UserId user_id, MessageId message_id, int32 date,
                                       int64 random_id, Promise<> promise);

  void on_secret_chat_ttl_changed(SecretChatId secret_chat_id, UserId user_id, MessageId message_id, int32 date,
                                  int32 ttl, int64 random_id, Promise<> promise);

  void on_update_sent_text_message(int64 random_id, tl_object_ptr<telegram_api::MessageMedia> message_media,
                                   vector<tl_object_ptr<telegram_api::MessageEntity>> &&entities);

  void on_update_message_web_page(FullMessageId full_message_id, bool have_web_page);

  void on_get_dialogs(vector<tl_object_ptr<telegram_api::dialog>> &&dialogs, int32 total_count,
                      vector<tl_object_ptr<telegram_api::Message>> &&messages, Promise<Unit> &&promise);

  void on_get_common_dialogs(UserId user_id, vector<tl_object_ptr<telegram_api::Chat>> &&chats, int32 total_count);

  bool on_update_message_id(int64 random_id, MessageId new_message_id, const string &source);

  void on_update_dialog_draft_message(DialogId dialog_id, tl_object_ptr<telegram_api::DraftMessage> &&draft_message);

  void on_update_dialog_pinned(DialogId dialog_id, bool is_pinned);

  void on_update_pinned_dialogs();

  void on_update_service_notification(tl_object_ptr<telegram_api::updateServiceNotification> &&update);

  void on_update_contact_registered(tl_object_ptr<telegram_api::updateContactRegistered> &&update);

  void on_update_new_channel_message(tl_object_ptr<telegram_api::updateNewChannelMessage> &&update);

  void on_update_edit_channel_message(tl_object_ptr<telegram_api::updateEditChannelMessage> &&update);

  void on_update_read_channel_inbox(tl_object_ptr<telegram_api::updateReadChannelInbox> &&update);

  void on_update_read_channel_outbox(tl_object_ptr<telegram_api::updateReadChannelOutbox> &&update);

  void on_update_read_channel_messages_contents(
      tl_object_ptr<telegram_api::updateChannelReadMessagesContents> &&update);

  void on_update_channel_too_long(tl_object_ptr<telegram_api::updateChannelTooLong> &&update, bool force_apply);

  void on_update_message_views(FullMessageId full_message_id, int32 views);

  void on_read_channel_inbox(ChannelId channel_id, MessageId max_message_id, int32 server_unread_count);

  void on_read_channel_outbox(ChannelId channel_id, MessageId max_message_id);

  void on_update_channel_max_unavailable_message_id(ChannelId channel_id, MessageId max_unavailable_message_id);

  void delete_messages(DialogId dialog_id, const vector<MessageId> &message_ids, bool revoke, Promise<Unit> &&promise);

  void delete_dialog_history(DialogId dialog_id, bool remove_from_dialog_list, Promise<Unit> &&promise);

  void delete_dialog_messages_from_user(DialogId dialog_id, UserId user_id, Promise<Unit> &&promise);

  void delete_dialog(DialogId dialog_id);

  void read_all_dialog_mentions(DialogId dialog_id, Promise<Unit> &&promise);

  Status add_recently_found_dialog(DialogId dialog_id) TD_WARN_UNUSED_RESULT;

  Status remove_recently_found_dialog(DialogId dialog_id) TD_WARN_UNUSED_RESULT;

  void clear_recently_found_dialogs();

  DialogId resolve_dialog_username(const string &username);

  DialogId search_public_dialog(const string &username_to_search, bool force, Promise<Unit> &&promise);

  Result<FormattedText> process_input_caption(DialogId dialog_id, tl_object_ptr<td_api::formattedText> &&text,
                                              bool is_bot) const;

  Result<InputMessageText> process_input_message_text(
      DialogId dialog_id, tl_object_ptr<td_api::InputMessageContent> &&input_message_content, bool is_bot,
      bool for_draft = false) const TD_WARN_UNUSED_RESULT;

  static Result<std::pair<Location, int32>> process_input_message_location(
      tl_object_ptr<td_api::InputMessageContent> &&input_message_content) TD_WARN_UNUSED_RESULT;

  static Result<Venue> process_input_message_venue(tl_object_ptr<td_api::InputMessageContent> &&input_message_content)
      TD_WARN_UNUSED_RESULT;

  static Result<Contact> process_input_message_contact(
      tl_object_ptr<td_api::InputMessageContent> &&input_message_content) TD_WARN_UNUSED_RESULT;

  Result<Game> process_input_message_game(tl_object_ptr<td_api::InputMessageContent> &&input_message_content) const
      TD_WARN_UNUSED_RESULT;

  bool need_skip_bot_commands(DialogId dialog_id, bool is_bot) const;

  FormattedText get_message_text(string message_text,
                                 vector<tl_object_ptr<telegram_api::MessageEntity>> &&server_entities,
                                 int32 send_date) const;

  static Status fix_text_message(string &text, vector<MessageEntity> &entities, bool allow_empty,
                                 bool skip_new_entities, bool skip_bot_commands, bool for_draft) TD_WARN_UNUSED_RESULT;

  Result<MessageId> send_message(DialogId dialog_id, MessageId reply_to_message_id, bool disable_notification,
                                 bool from_background, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
                                 tl_object_ptr<td_api::InputMessageContent> &&input_message_content)
      TD_WARN_UNUSED_RESULT;

  Result<vector<MessageId>> send_message_group(
      DialogId dialog_id, MessageId reply_to_message_id, bool disable_notification, bool from_background,
      vector<tl_object_ptr<td_api::InputMessageContent>> &&input_message_contents) TD_WARN_UNUSED_RESULT;

  Result<MessageId> send_bot_start_message(UserId bot_user_id, DialogId dialog_id,
                                           const string &parameter) TD_WARN_UNUSED_RESULT;

  Result<MessageId> send_inline_query_result_message(DialogId dialog_id, MessageId reply_to_message_id,
                                                     bool disable_notification, bool from_background, int64 query_id,
                                                     const string &result_id) TD_WARN_UNUSED_RESULT;

  Result<vector<MessageId>> forward_messages(DialogId to_dialog_id, DialogId from_dialog_id,
                                             vector<MessageId> message_ids, bool disable_notification,
                                             bool from_background, bool in_game_share,
                                             bool as_album) TD_WARN_UNUSED_RESULT;

  Result<MessageId> send_dialog_set_ttl_message(DialogId dialog_id, int32 ttl);

  Status send_screenshot_taken_notification_message(DialogId dialog_id);

  void edit_message_text(FullMessageId full_message_id, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
                         tl_object_ptr<td_api::InputMessageContent> &&input_message_content, Promise<Unit> &&promise);

  void edit_message_live_location(FullMessageId full_message_id, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
                                  tl_object_ptr<td_api::location> &&input_location, Promise<Unit> &&promise);

  void edit_message_caption(FullMessageId full_message_id, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
                            tl_object_ptr<td_api::formattedText> &&input_caption, Promise<Unit> &&promise);

  void edit_message_reply_markup(FullMessageId full_message_id, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
                                 Promise<Unit> &&promise);

  void edit_inline_message_text(const string &inline_message_id, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
                                tl_object_ptr<td_api::InputMessageContent> &&input_message_content,
                                Promise<Unit> &&promise);

  void edit_inline_message_live_location(const string &inline_message_id,
                                         tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
                                         tl_object_ptr<td_api::location> &&input_location, Promise<Unit> &&promise);

  void edit_inline_message_caption(const string &inline_message_id, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
                                   tl_object_ptr<td_api::formattedText> &&input_caption, Promise<Unit> &&promise);

  void edit_inline_message_reply_markup(const string &inline_message_id,
                                        tl_object_ptr<td_api::ReplyMarkup> &&reply_markup, Promise<Unit> &&promise);

  void set_game_score(FullMessageId full_message_id, bool edit_message, UserId user_id, int32 score, bool force,
                      Promise<Unit> &&promise);

  void set_inline_game_score(const string &inline_message_id, bool edit_message, UserId user_id, int32 score,
                             bool force, Promise<Unit> &&promise);

  int64 get_game_high_scores(FullMessageId full_message_id, UserId user_id, Promise<Unit> &&promise);

  int64 get_inline_game_high_scores(const string &inline_message_id, UserId user_id, Promise<Unit> &&promise);

  void on_get_game_high_scores(int64 random_id, tl_object_ptr<telegram_api::messages_highScores> &&high_scores);

  tl_object_ptr<td_api::gameHighScores> get_game_high_scores_object(int64 random_id);

  void send_dialog_action(DialogId dialog_id, const tl_object_ptr<td_api::ChatAction> &action, Promise<Unit> &&promise);

  void set_dialog_photo(DialogId dialog_id, const tl_object_ptr<td_api::InputFile> &photo, Promise<Unit> &&promise);

  void set_dialog_title(DialogId dialog_id, const string &title, Promise<Unit> &&promise);

  void add_dialog_participant(DialogId dialog_id, UserId user_id, int32 forward_limit, Promise<Unit> &&promise);

  void add_dialog_participants(DialogId dialog_id, const vector<UserId> &user_ids, Promise<Unit> &&promise);

  void set_dialog_participant_status(DialogId dialog_id, UserId user_id,
                                     const tl_object_ptr<td_api::ChatMemberStatus> &chat_member_status,
                                     Promise<Unit> &&promise);

  DialogParticipant get_dialog_participant(DialogId dialog_id, UserId user_id, int64 &random_id, bool force,
                                           Promise<Unit> &&promise);

  std::pair<int32, vector<DialogParticipant>> search_dialog_participants(DialogId dialog_id, const string &query,
                                                                         int32 limit, int64 &random_id, bool force,
                                                                         Promise<Unit> &&promise);

  vector<UserId> get_dialog_administrators(DialogId dialog_id, int left_tries, Promise<Unit> &&promise);

  void export_dialog_invite_link(DialogId dialog_id, Promise<Unit> &&promise);

  string get_dialog_invite_link(DialogId dialog_id);

  int64 get_dialog_event_log(DialogId dialog_id, const string &query, int64 from_event_id, int32 limit,
                             const tl_object_ptr<td_api::chatEventLogFilters> &filters, const vector<UserId> &user_ids,
                             Promise<Unit> &&promise);

  void on_get_event_log(int64 random_id, tl_object_ptr<telegram_api::channels_adminLogResults> &&events);

  tl_object_ptr<td_api::chatEvents> get_chat_events_object(int64 random_id);

  bool have_dialog(DialogId dialog_id) const;
  bool have_dialog_force(DialogId dialog_id);

  bool load_dialog(DialogId dialog_id, int left_tries, Promise<Unit> &&promise);

  void load_dialogs(vector<DialogId> dialog_ids, Promise<Unit> &&promise);

  vector<DialogId> get_dialogs(DialogDate offset, int32 limit, bool force, Promise<Unit> &&promise);

  vector<DialogId> search_public_dialogs(const string &query, Promise<Unit> &&promise);

  std::pair<size_t, vector<DialogId>> search_dialogs(const string &query, int32 limit, Promise<Unit> &&promise);

  vector<DialogId> search_dialogs_on_server(const string &query, int32 limit, Promise<Unit> &&promise);

  vector<DialogId> get_common_dialogs(UserId user_id, DialogId offset_dialog_id, int32 limit, bool force,
                                      Promise<Unit> &&promise);

  bool have_message(FullMessageId full_message_id);

  void get_message(FullMessageId full_message_id, Promise<Unit> &&promise);

  MessageId get_replied_message(DialogId dialog_id, MessageId message_id, bool force, Promise<Unit> &&promise);

  void get_dialog_pinned_message(DialogId dialog_id, Promise<MessageId> &&promise);

  bool get_messages(DialogId dialog_id, const vector<MessageId> &message_ids, Promise<Unit> &&promise);

  void get_messages_from_server(vector<FullMessageId> &&message_ids, Promise<Unit> &&promise,
                                tl_object_ptr<telegram_api::InputMessage> input_message = nullptr);

  bool is_message_edited_recently(FullMessageId full_message_id, int32 seconds);

  std::pair<string, string> get_public_message_link(FullMessageId full_message_id, bool for_group,
                                                    Promise<Unit> &&promise);

  void on_get_public_message_link(FullMessageId full_message_id, bool for_group, string url, string html);

  Status delete_dialog_reply_markup(DialogId dialog_id, MessageId message_id) TD_WARN_UNUSED_RESULT;

  Status set_dialog_draft_message(DialogId dialog_id,
                                  tl_object_ptr<td_api::draftMessage> &&draft_message) TD_WARN_UNUSED_RESULT;

  Status toggle_dialog_is_pinned(DialogId dialog_id, bool is_pinned) TD_WARN_UNUSED_RESULT;

  Status set_pinned_dialogs(vector<DialogId> dialog_ids) TD_WARN_UNUSED_RESULT;

  Status set_dialog_client_data(DialogId dialog_id, string &&client_data) TD_WARN_UNUSED_RESULT;

  void create_dialog(DialogId dialog_id, bool force, Promise<Unit> &&promise);

  DialogId create_new_group_chat(const vector<UserId> &user_ids, const string &title, int64 &random_id,
                                 Promise<Unit> &&promise);

  DialogId create_new_channel_chat(const string &title, bool is_megagroup, const string &description, int64 &random_id,
                                   Promise<Unit> &&promise);

  void create_new_secret_chat(UserId user_id, Promise<SecretChatId> &&promise);

  DialogId migrate_dialog_to_megagroup(DialogId dialog_id, Promise<Unit> &&promise);

  Status open_dialog(DialogId dialog_id) TD_WARN_UNUSED_RESULT;

  Status close_dialog(DialogId dialog_id) TD_WARN_UNUSED_RESULT;

  Status view_messages(DialogId dialog_id, const vector<MessageId> &message_ids, bool force_read) TD_WARN_UNUSED_RESULT;

  Status open_message_content(FullMessageId full_message_id) TD_WARN_UNUSED_RESULT;

  static tl_object_ptr<td_api::NotificationSettingsScope> get_notification_settings_scope_object(
      NotificationSettingsScope scope);

  static tl_object_ptr<td_api::notificationSettings> get_notification_settings_object(
      const NotificationSettings *notification_settings);

  const NotificationSettings *get_notification_settings(NotificationSettingsScope scope, Promise<Unit> &&promise);

  Status set_notification_settings(NotificationSettingsScope scope,
                                   tl_object_ptr<td_api::notificationSettings> &&notification_settings)
      TD_WARN_UNUSED_RESULT;

  void reset_all_notification_settings();

  tl_object_ptr<td_api::chat> get_chat_object(DialogId dialog_id);

  static tl_object_ptr<td_api::chats> get_chats_object(const vector<DialogId> &dialogs);

  tl_object_ptr<td_api::messages> get_dialog_history(DialogId dialog_id, MessageId from_message_id, int32 offset,
                                                     int32 limit, int left_tries, bool only_local,
                                                     Promise<Unit> &&promise);

  std::pair<int32, vector<MessageId>> search_dialog_messages(DialogId dialog_id, const string &query,
                                                             UserId sender_user_id, MessageId from_message_id,
                                                             int32 offset, int32 limit,
                                                             const tl_object_ptr<td_api::SearchMessagesFilter> &filter,
                                                             int64 &random_id, bool use_db, Promise<Unit> &&promise);

  std::pair<int64, vector<FullMessageId>> offline_search_messages(
      DialogId dialog_id, const string &query, int64 from_search_id, int32 limit,
      const tl_object_ptr<td_api::SearchMessagesFilter> &filter, int64 &random_id, Promise<> &&promise);

  std::pair<int32, vector<FullMessageId>> search_messages(const string &query, int32 offset_date,
                                                          DialogId offset_dialog_id, MessageId offset_message_id,
                                                          int32 limit, int64 &random_id, Promise<Unit> &&promise);

  std::pair<int32, vector<FullMessageId>> search_call_messages(MessageId from_message_id, int32 limit, bool only_missed,
                                                               int64 &random_id, bool use_db, Promise<Unit> &&promise);

  std::pair<int32, vector<MessageId>> search_dialog_recent_location_messages(DialogId dialog_id, int32 limit,
                                                                             int64 &random_id, Promise<Unit> &&promise);

  vector<FullMessageId> get_active_live_location_messages(Promise<Unit> &&promise);

  int64 get_dialog_message_by_date(DialogId dialog_id, int32 date, Promise<Unit> &&promise);

  void on_get_dialog_message_by_date_success(DialogId dialog_id, int32 date, int64 random_id,
                                             vector<tl_object_ptr<telegram_api::Message>> &&messages);

  void on_get_dialog_message_by_date_fail(int64 random_id);

  tl_object_ptr<td_api::message> get_dialog_message_by_date_object(int64 random_id);

  tl_object_ptr<td_api::message> get_message_object(FullMessageId full_message_id);

  tl_object_ptr<td_api::messages> get_messages_object(int32 total_count, DialogId dialog_id,
                                                      const vector<MessageId> &message_ids);

  tl_object_ptr<td_api::messages> get_messages_object(int32 total_count, const vector<FullMessageId> &full_message_ids);

  void add_pending_update(tl_object_ptr<telegram_api::Update> &&update, int32 new_pts, int32 pts_count,
                          bool force_apply, const char *source);

  void add_pending_channel_update(DialogId dialog_id, tl_object_ptr<telegram_api::Update> &&update, int32 new_pts,
                                  int32 pts_count, const char *source, bool is_postponed_update = false);

  bool is_update_about_username_change_received(DialogId dialog_id) const;

  void on_dialog_photo_updated(DialogId dialog_id);
  void on_dialog_title_updated(DialogId dialog_id);
  void on_dialog_username_updated(DialogId dialog_id, const string &old_username, const string &new_username);

  void on_resolved_username(const string &username, DialogId dialog_id);
  void drop_username(const string &username);

  static tl_object_ptr<telegram_api::MessagesFilter> get_input_messages_filter(SearchMessagesFilter filter);

  static SearchMessagesFilter get_search_messages_filter(const tl_object_ptr<td_api::SearchMessagesFilter> &filter);

  tl_object_ptr<telegram_api::InputNotifyPeer> get_input_notify_peer(NotificationSettingsScope scope) const;

  NotificationSettingsScope get_notification_settings_scope(
      tl_object_ptr<telegram_api::NotifyPeer> &&notify_peer_ptr) const;

  NotificationSettingsScope get_notification_settings_scope(
      const tl_object_ptr<td_api::NotificationSettingsScope> &scope) const;

  void on_update_notify_settings(NotificationSettingsScope scope,
                                 tl_object_ptr<telegram_api::PeerNotifySettings> &&peer_notify_settings);

  bool get_dialog_report_spam_state(DialogId dialog_id, Promise<Unit> &&promise);

  void change_dialog_report_spam_state(DialogId dialog_id, bool is_spam_dialog, Promise<Unit> &&promise);

  void report_dialog(DialogId dialog_id, const tl_object_ptr<td_api::ChatReportReason> &reason,
                     Promise<Unit> &&promise);

  void on_get_peer_settings(DialogId dialog_id, tl_object_ptr<telegram_api::peerSettings> &&peer_settings);

  void before_get_difference();

  void after_get_difference();

  bool on_get_dialog_error(DialogId dialog_id, const Status &status, const string &source);

  void on_send_message_get_quick_ack(int64 random_id);

  void check_send_message_result(int64 random_id, DialogId dialog_id, const telegram_api::Updates *updates_ptr,
                                 const char *source);

  FullMessageId on_send_message_success(int64 random_id, MessageId new_message_id, int32 date, FileId new_file_id,
                                        const char *source);

  void on_send_message_file_part_missing(int64 random_id, int bad_part);

  void on_send_message_fail(int64 random_id, Status error);

  void on_upload_message_media_success(DialogId dialog_id, MessageId message_id,
                                       tl_object_ptr<telegram_api::MessageMedia> &&media);

  void on_upload_message_media_file_part_missing(DialogId dialog_id, MessageId message_id, int bad_part);

  void on_upload_message_media_fail(DialogId dialog_id, MessageId message_id, Status error);

  void on_create_new_dialog_success(int64 random_id, tl_object_ptr<telegram_api::Updates> &&updates,
                                    DialogType expected_type, Promise<Unit> &&promise);

  void on_create_new_dialog_fail(int64 random_id, Status error, Promise<Unit> &&promise);

  void on_get_channel_difference(DialogId dialog_id, int32 request_pts, int32 request_limit,
                                 tl_object_ptr<telegram_api::updates_ChannelDifference> &&difference_ptr);

  void force_create_dialog(DialogId dialog_id, const char *source, bool force_update_dialog_pos = false);

  void on_get_dialog_success(DialogId dialog_id);

  void on_get_dialog_fail(DialogId dialog_id, Status &&error);

  void on_binlog_events(vector<BinlogEvent> &&events);

  void get_payment_form(FullMessageId full_message_id, Promise<tl_object_ptr<td_api::paymentForm>> &&promise);

  void validate_order_info(FullMessageId full_message_id, tl_object_ptr<td_api::orderInfo> order_info, bool allow_save,
                           Promise<tl_object_ptr<td_api::validatedOrderInfo>> &&promise);

  void send_payment_form(FullMessageId full_message_id, const string &order_info_id, const string &shipping_option_id,
                         const tl_object_ptr<td_api::InputCredentials> &credentials,
                         Promise<tl_object_ptr<td_api::paymentResult>> &&promise);

  void get_payment_receipt(FullMessageId full_message_id, Promise<tl_object_ptr<td_api::paymentReceipt>> &&promise);

  ActorOwn<MultiSequenceDispatcher> sequence_dispatcher_;

 private:
  class PendingPtsUpdate {
   public:
    tl_object_ptr<telegram_api::Update> update;
    int32 pts;
    int32 pts_count;

    PendingPtsUpdate(tl_object_ptr<telegram_api::Update> &&update, int32 pts, int32 pts_count)
        : update(std::move(update)), pts(pts), pts_count(pts_count) {
    }
  };

  struct MessageInfo {
    DialogId dialog_id;
    MessageId message_id;
    UserId sender_user_id;
    int32 date;
    int32 ttl = 0;
    int64 random_id = 0;
    tl_object_ptr<telegram_api::messageFwdHeader> forward_header;
    MessageId reply_to_message_id;
    UserId via_bot_user_id;
    int32 views = 0;
    int32 flags = 0;
    int32 edit_date = 0;
    string author_signature;
    int64 media_album_id = 0;

    unique_ptr<MessageContent> content;
    tl_object_ptr<telegram_api::ReplyMarkup> reply_markup;
  };

  struct MessageForwardInfo {
    UserId sender_user_id;
    int32 date = 0;
    DialogId dialog_id;
    MessageId message_id;
    string author_signature;
    DialogId from_dialog_id;
    MessageId from_message_id;

    MessageForwardInfo() = default;

    MessageForwardInfo(UserId sender_user_id, int32 date, DialogId dialog_id, MessageId message_id,
                       string author_signature, DialogId from_dialog_id, MessageId from_message_id)
        : sender_user_id(sender_user_id)
        , date(date)
        , dialog_id(dialog_id)
        , message_id(message_id)
        , author_signature(std::move(author_signature))
        , from_dialog_id(from_dialog_id)
        , from_message_id(from_message_id) {
    }

    bool operator==(const MessageForwardInfo &rhs) const {
      return sender_user_id == rhs.sender_user_id && date == rhs.date && dialog_id == rhs.dialog_id &&
             message_id == rhs.message_id && author_signature == rhs.author_signature &&
             from_dialog_id == rhs.from_dialog_id && from_message_id == rhs.from_message_id;
    }

    bool operator!=(const MessageForwardInfo &rhs) const {
      return !(*this == rhs);
    }

    friend StringBuilder &operator<<(StringBuilder &string_builder, const MessageForwardInfo &forward_info) {
      return string_builder << "MessageForwardInfo[sender " << forward_info.sender_user_id << "("
                            << forward_info.author_signature << "), source " << forward_info.dialog_id << ", source "
                            << forward_info.message_id << ", from " << forward_info.from_dialog_id << ", from "
                            << forward_info.from_message_id << " at " << forward_info.date << "]";
    }
  };

  // Do not forget to update MessagesManager::update_message when this class is changed
  struct Message {
    int32 random_y;

    MessageId message_id;
    UserId sender_user_id;
    int32 date = 0;
    int32 edit_date = 0;

    int64 random_id = 0;

    unique_ptr<MessageForwardInfo> forward_info;

    MessageId reply_to_message_id;
    int64 reply_to_random_id = 0;  // for send_message

    UserId via_bot_user_id;

    string author_signature;

    bool is_channel_post = false;
    bool is_outgoing = false;
    bool is_failed_to_send = false;  // TODO replace with error_code
    bool disable_notification = false;
    bool contains_mention = false;
    bool contains_unread_mention = false;
    bool had_reply_markup = false;  // had non-inline reply markup?

    bool from_background = false;           // for send_message
    bool disable_web_page_preview = false;  // for send_message
    bool clear_draft = false;               // for send_message
    bool in_game_share = false;             // for send_message

    bool have_previous = false;
    bool have_next = false;
    bool from_database = false;

    int32 views = 0;

    int32 ttl = 0;
    double ttl_expires_at = 0;

    int64 media_album_id = 0;

    unique_ptr<MessageContent> content;

    unique_ptr<ReplyMarkup> reply_markup;

    unique_ptr<Message> left;
    unique_ptr<Message> right;

    int32 last_access_date = 0;

    uint64 send_message_logevent_id = 0;

    NetQueryRef send_query_ref;

    DialogId debug_forward_from;

    template <class StorerT>
    void store(StorerT &storer) const;

    template <class ParserT>
    void parse(ParserT &parser);
  };

  struct Dialog {
    DialogId dialog_id;
    MessageId last_new_message_id;  // identifier of the last known server message received from update, there should be
                                    // no server messages after it
    MessageId last_message_id;      // identifier of the message after which currently there is no any message, i.e. a
                                    // message without a gap after it, memory only
    MessageId first_database_message_id;  // identifier of the first message in the database, needed
                                          // until there is no gaps in the database
    MessageId last_database_message_id;   // identifier of the last local or server message, if last_database_message_id
                                          // is known and last_message_id is known then last_database_message_id <=
                                          // last_message_id

    std::array<MessageId, search_messages_filter_size()> first_database_message_id_by_index;
    // use struct Count?
    std::array<int32, search_messages_filter_size()> message_count_by_index;

    int32 server_unread_count = 0;
    int32 local_unread_count = 0;
    int32 unread_mention_count = 0;
    MessageId last_read_inbox_message_id;
    MessageId last_read_outbox_message_id;
    MessageId reply_markup_message_id;
    NotificationSettings notification_settings;
    unique_ptr<DraftMessage> draft_message;
    uint64 save_draft_message_logevent_id = 0;
    uint64 save_draft_message_logevent_id_generation = 0;

    MessageId
        last_read_all_mentions_message_id;  // all mentions with a message id not greater than it are implicitly read
    MessageId
        max_unavailable_message_id;  // maximal unavailable message id for dialogs with cleared/unavailable history

    int32 last_clear_history_date = 0;
    MessageId last_clear_history_message_id;
    int64 order = DEFAULT_ORDER;
    int64 pinned_order = DEFAULT_ORDER;
    int32 delete_last_message_date = 0;
    MessageId deleted_last_message_id;

    bool has_contact_registered_message = false;

    bool is_last_message_deleted_locally = false;

    bool know_can_report_spam = false;
    bool can_report_spam = false;

    bool is_opened = false;

    bool need_restore_reply_markup = true;

    bool have_full_history = false;
    bool is_empty = false;

    bool is_last_read_inbox_message_id_inited = false;
    bool is_last_read_outbox_message_id_inited = false;

    int32 pts = 0;                                                     // for channels only
    std::multimap<int32, PendingPtsUpdate> postponed_channel_updates;  // for channels only
    int32 retry_get_difference_timeout = 1;                            // for channels only
    std::unordered_map<int64, MessageId> random_id_to_message_id;      // for secret chats only

    MessageId last_assigned_message_id;  // identifier of the last local or yet unsent message, assigned after
                                         // application start, used to guarantee that all assigned message identifiers
                                         // are different

    std::unordered_map<MessageId, MessageId, MessageIdHash> yet_unsent_message_id_to_persistent_message_id;

    std::unordered_set<MessageId, MessageIdHash> deleted_message_ids;

    std::vector<MessageId> pending_update_new_messages;

    string client_data;

    // Load from newest to oldest message
    MessageId suffix_load_first_message_id_;
    MessageId suffix_load_query_message_id_;
    std::vector<std::pair<Promise<>, std::function<bool(const Message *)>>> suffix_load_queries_;
    bool suffix_load_done_ = false;
    bool suffix_load_has_query_ = false;
    bool suffix_load_was_query_ = false;

    std::unordered_set<MessageId, MessageIdHash> pending_viewed_message_ids;

    unique_ptr<Message> messages = nullptr;

    struct MessageOp {
      enum : int8 { Add, SetPts, Delete, DeleteAll } type;
      bool from_update;
      bool have_previous;
      bool have_next;
      int32 content_type;
      MessageId message_id;
      const char *source;
      double date;

      MessageOp(decltype(type) type, MessageId message_id, int32 content_type, bool from_update, bool have_previous,
                bool have_next, const char *source)
          : type(type)
          , from_update(from_update)
          , have_previous(have_previous)
          , have_next(have_next)
          , content_type(content_type)
          , message_id(message_id)
          , source(source)
          , date(G()->server_time()) {
      }
    };

    vector<MessageOp> debug_message_op;

    Dialog() = default;
    Dialog(const Dialog &) = delete;
    Dialog &operator=(const Dialog &) = delete;
    Dialog(Dialog &&other) = delete;
    Dialog &operator=(Dialog &&other) = delete;
    ~Dialog();

    template <class StorerT>
    void store(StorerT &storer) const;

    template <class ParserT>
    void parse(ParserT &parser);
  };

  class MessagesIteratorBase {
    vector<const Message *> stack_;

   protected:
    MessagesIteratorBase() = default;

    // points iterator to message with greatest id which is less or equal than message_id
    MessagesIteratorBase(const Message *root, MessageId message_id) {
      size_t last_right_pos = 0;
      while (root != nullptr) {
        //        LOG(DEBUG) << "root->message_id = " << root->message_id;
        stack_.push_back(root);
        if (root->message_id.get() <= message_id.get()) {
          //          LOG(DEBUG) << "Go right";
          last_right_pos = stack_.size();
          root = root->right.get();
        } else {
          //          LOG(DEBUG) << "Go left";
          root = root->left.get();
        }
      }
      stack_.resize(last_right_pos);
    }

    const Message *operator*() const {
      return stack_.empty() ? nullptr : stack_.back();
    }

    ~MessagesIteratorBase() = default;

   public:
    MessagesIteratorBase(const MessagesIteratorBase &) = delete;
    MessagesIteratorBase &operator=(const MessagesIteratorBase &) = delete;
    MessagesIteratorBase(MessagesIteratorBase &&other) = default;
    MessagesIteratorBase &operator=(MessagesIteratorBase &&other) = default;

    void operator++() {
      if (stack_.empty()) {
        return;
      }

      const Message *cur = stack_.back();
      if (!cur->have_next) {
        stack_.clear();
        return;
      }
      if (cur->right == nullptr) {
        while (true) {
          stack_.pop_back();
          if (stack_.empty()) {
            return;
          }
          const Message *new_cur = stack_.back();
          if (new_cur->left.get() == cur) {
            return;
          }
          cur = new_cur;
        }
      }

      cur = cur->right.get();
      while (cur != nullptr) {
        stack_.push_back(cur);
        cur = cur->left.get();
      }
    }

    void operator--() {
      if (stack_.empty()) {
        return;
      }

      const Message *cur = stack_.back();
      if (!cur->have_previous) {
        stack_.clear();
        return;
      }
      if (cur->left == nullptr) {
        while (true) {
          stack_.pop_back();
          if (stack_.empty()) {
            return;
          }
          const Message *new_cur = stack_.back();
          if (new_cur->right.get() == cur) {
            return;
          }
          cur = new_cur;
        }
      }

      cur = cur->left.get();
      while (cur != nullptr) {
        stack_.push_back(cur);
        cur = cur->right.get();
      }
    }
  };

  class MessagesIterator : public MessagesIteratorBase {
   public:
    MessagesIterator() = default;

    MessagesIterator(Dialog *d, MessageId message_id) : MessagesIteratorBase(d->messages.get(), message_id) {
    }

    Message *operator*() const {
      return const_cast<Message *>(MessagesIteratorBase::operator*());
    }
  };

  class MessagesConstIterator : public MessagesIteratorBase {
   public:
    MessagesConstIterator() = default;

    MessagesConstIterator(const Dialog *d, MessageId message_id) : MessagesIteratorBase(d->messages.get(), message_id) {
    }

    const Message *operator*() const {
      return MessagesIteratorBase::operator*();
    }
  };

  struct PendingSecretMessage {
    MessageInfo message_info;
    MultiPromiseActor load_data_multipromise;
    Promise<> success_promise;
  };

  struct InputMessageContent {
    unique_ptr<MessageContent> content;
    bool disable_web_page_preview = false;
    bool clear_draft = false;
    int32 ttl = 0;
    UserId via_bot_user_id;

    InputMessageContent(unique_ptr<MessageContent> &&content, bool disable_web_page_preview, bool clear_draft,
                        int32 ttl, UserId via_bot_user_id)
        : content(std::move(content))
        , disable_web_page_preview(disable_web_page_preview)
        , clear_draft(clear_draft)
        , ttl(ttl)
        , via_bot_user_id(via_bot_user_id) {
    }
  };

  class DeleteAllChannelMessagesFromUserOnServerLogEvent;
  class DeleteDialogHistoryFromServerLogEvent;
  class DeleteMessageLogEvent;
  class DeleteMessagesFromServerLogEvent;
  class ForwardMessagesLogEvent;
  class GetChannelDifferenceLogEvent;
  class ReadAllDialogMentionsOnServerLogEvent;
  class ReadHistoryOnServerLogEvent;
  class ReadMessageContentsOnServerLogEvent;
  class ReorderPinnedDialogsOnServerLogEvent;
  class SaveDialogDraftMessageOnServerLogEvent;
  class SendBotStartMessageLogEvent;
  class SendInlineQueryResultMessageLogEvent;
  class SendMessageLogEvent;
  class SendScreenshotTakenNotificationMessageLogEvent;
  class ToggleDialogIsPinnedOnServerLogEvent;

  static constexpr size_t MAX_GROUPED_MESSAGES = 10;               // server side limit
  static constexpr int32 MAX_GET_DIALOGS = 100;                    // server side limit
  static constexpr int32 MAX_GET_HISTORY = 100;                    // server side limit
  static constexpr int32 MAX_SEARCH_MESSAGES = 100;                // server side limit
  static constexpr int32 MIN_SEARCH_PUBLIC_DIALOG_PREFIX_LEN = 5;  // server side limit
  static constexpr int32 MIN_CHANNEL_DIFFERENCE = 10;
  static constexpr int32 MAX_CHANNEL_DIFFERENCE = 100;
  static constexpr int32 MAX_BOT_CHANNEL_DIFFERENCE = 100000;  // server side limit
  static constexpr int32 MAX_RECENT_FOUND_DIALOGS = 20;        // some reasonable value
  static constexpr size_t MAX_CAPTION_LENGTH = 200;            // server side limit
  static constexpr size_t MAX_NAME_LENGTH = 255;               // server side limit for title and description
  static constexpr int32 MIN_PINNED_DIALOG_DATE = 2147000000;  // some big date
  static constexpr int32 MAX_PRIVATE_MESSAGE_TTL = 60;         // server side limit

  static constexpr int32 UPDATE_CHANNEL_TO_LONG_FLAG_HAS_PTS = 1 << 0;

  static constexpr int32 CHANNEL_DIFFERENCE_FLAG_IS_FINAL = 1 << 0;
  static constexpr int32 CHANNEL_DIFFERENCE_FLAG_HAS_TIMEOUT = 1 << 1;

  static constexpr int32 DIALOG_FLAG_HAS_PTS = 1 << 0;
  static constexpr int32 DIALOG_FLAG_HAS_DRAFT = 1 << 1;
  static constexpr int32 DIALOG_FLAG_IS_PINNED = 1 << 2;

  static constexpr int32 MAX_MESSAGE_VIEW_DELAY = 1;  // seconds
  static constexpr int32 MIN_SAVE_DRAFT_DELAY = 1;    // seconds
  static constexpr int32 MAX_SAVE_DIALOG_DELAY = 0;   // seconds
  static constexpr int32 DIALOG_UNLOAD_DELAY = 60;    // seconds

  static constexpr int32 USERNAME_CACHE_EXPIRE_TIME = 3 * 86400;
  static constexpr int32 USERNAME_CACHE_EXPIRE_TIME_SHORT = 900;

  static constexpr int32 MIN_LIVE_LOCATION_PERIOD = 60;     // seconds, server side limit
  static constexpr int32 MAX_LIVE_LOCATION_PERIOD = 86400;  // seconds, server side limit

  static constexpr int32 MAX_PRELOADED_DIALOGS = 1000;

  static constexpr const char *DELETE_MESSAGE_USER_REQUEST_SOURCE = "user request";

  static constexpr bool DROP_UPDATES = false;

  void open_dialog(Dialog *d);

  void close_dialog(Dialog *d);

  void add_secret_message(unique_ptr<PendingSecretMessage> pending_secret_message, Promise<Unit> lock_promise = Auto());

  void finish_add_secret_message(unique_ptr<PendingSecretMessage> pending_secret_message);

  void fix_message_info_dialog_id(MessageInfo &message_info) const;

  MessageInfo parse_telegram_api_message(tl_object_ptr<telegram_api::Message> message_ptr) const;

  std::pair<DialogId, unique_ptr<Message>> create_message(MessageInfo &&message_info, bool is_channel_message);

  FullMessageId on_get_message(MessageInfo &&message_info, bool from_update, bool is_channel_message,
                               bool have_previous, bool have_next, const char *source);

  Result<InputMessageContent> process_input_message_content(
      DialogId dialog_id, tl_object_ptr<td_api::InputMessageContent> &&input_message_content) const;

  Message *get_message_to_send(Dialog *d, MessageId reply_to_message_id, bool disable_notification,
                               bool from_background, unique_ptr<MessageContent> &&content, bool *need_update_dialog_pos,
                               unique_ptr<MessageForwardInfo> forward_info = nullptr);

  int64 begin_send_message(DialogId dialog_id, const Message *m);

  Status can_send_message(DialogId dialog_id) const TD_WARN_UNUSED_RESULT;

  Status can_send_message_content(DialogId dialog_id, const MessageContent *content,
                                  bool is_forward) const TD_WARN_UNUSED_RESULT;

  bool can_edit_message(DialogId dialog_id, const Message *m, bool is_editing, bool only_reply_markup = false) const;

  MessageId get_persistent_message_id(const Dialog *d, MessageId message_id) const;

  static MessageId get_replied_message_id(const Message *m);

  MessageId get_reply_to_message_id(Dialog *d, MessageId message_id);

  bool can_set_game_score(DialogId dialog_id, const Message *m) const;

  bool check_update_dialog_id(const tl_object_ptr<telegram_api::Update> &update, DialogId dialog_id);

  void process_update(tl_object_ptr<telegram_api::Update> &&update);

  void process_channel_update(tl_object_ptr<telegram_api::Update> &&update);

  void delete_messages_from_updates(const vector<MessageId> &message_ids);

  void delete_dialog_messages_from_updates(DialogId dialog_id, const vector<MessageId> &message_ids);

  void do_forward_messages(DialogId to_dialog_id, DialogId from_dialog_id, const vector<Message *> &messages,
                           const vector<MessageId> &message_ids, int64 logevent_id);

  Result<MessageId> forward_message(DialogId to_dialog_id, DialogId from_dialog_id, MessageId message_id,
                                    bool disable_notification, bool from_background,
                                    bool in_game_share) TD_WARN_UNUSED_RESULT;

  SecretInputMedia get_secret_input_media(const MessageContent *content,
                                          tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
                                          BufferSlice thumbnail, int32 layer);

  tl_object_ptr<telegram_api::invoice> get_input_invoice(const Invoice &invoice) const;

  tl_object_ptr<telegram_api::inputWebDocument> get_input_web_document(const Photo &photo) const;

  tl_object_ptr<telegram_api::inputMediaInvoice> get_input_media_invoice(const MessageInvoice *message_invoice) const;

  tl_object_ptr<telegram_api::InputMedia> get_input_media(const MessageContent *content,
                                                          tl_object_ptr<telegram_api::InputFile> input_file,
                                                          tl_object_ptr<telegram_api::InputFile> input_thumbnail,
                                                          int32 ttl);

  void delete_message_content_thumbnail(MessageContent *content);

  void do_send_media(DialogId dialog_id, Message *m, FileId file_id, FileId thumbnail_file_id,
                     tl_object_ptr<telegram_api::InputFile> input_file,
                     tl_object_ptr<telegram_api::InputFile> input_thumbnail);

  void do_send_secret_media(DialogId dialog_id, Message *m, FileId file_id, FileId thumbnail_file_id,
                            tl_object_ptr<telegram_api::InputEncryptedFile> input_encrypted_file,
                            BufferSlice thumbnail);

  void do_send_message(DialogId dialog_id, Message *m, vector<int> bad_parts = {});

  void on_message_media_uploaded(DialogId dialog_id, Message *m, tl_object_ptr<telegram_api::InputMedia> &&input_media,
                                 FileId file_id, FileId thumbnail_file_id);

  void on_secret_message_media_uploaded(DialogId dialog_id, Message *m, SecretInputMedia &&secret_input_media,
                                        FileId file_id, FileId thumbnail_file_id);

  void on_upload_message_media_finished(int64 media_album_id, DialogId dialog_id, MessageId message_id, Status result);

  void do_send_message_group(int64 media_album_id);

  void on_media_message_ready_to_send(DialogId dialog_id, MessageId message_id, Promise<Message *> &&promise);

  void on_yet_unsent_media_queue_updated(DialogId dialog_id);

  void save_send_bot_start_message_logevent(UserId bot_user_id, DialogId dialog_id, const string &parameter,
                                            Message *m);

  void do_send_bot_start_message(UserId bot_user_id, DialogId dialog_id, const string &parameter, Message *m);

  void save_send_inline_query_result_message_logevent(DialogId dialog_id, Message *m, int64 query_id,
                                                      const string &result_id);

  void do_send_inline_query_result_message(DialogId dialog_id, Message *m, int64 query_id, const string &result_id);

  uint64 save_send_screenshot_taken_notification_message_logevent(DialogId dialog_id, const Message *m);

  void do_send_screenshot_taken_notification_message(DialogId dialog_id, const Message *m, uint64 logevent_id);

  Message *continue_send_message(DialogId dialog_id, unique_ptr<Message> &&m, uint64 logevent_id);

  tl_object_ptr<telegram_api::InputChatPhoto> get_input_chat_photo(FileId file_id) const;

  bool is_message_unload_enabled() const;

  static bool is_allowed_media_group_content(int32 content_type);

  static bool can_forward_message(DialogId from_dialog_id, const Message *m);

  static bool is_service_message_content(int32 content_type);

  static bool can_delete_channel_message(DialogParticipantStatus status, const Message *m, bool is_bot);

  bool can_revoke_message(DialogId dialog_id, const Message *m) const;

  bool can_unload_message(const Dialog *d, const Message *m) const;

  void unload_message(Dialog *d, MessageId message_id);

  unique_ptr<Message> delete_message(Dialog *d, MessageId message_id, bool is_permanently_deleted,
                                     bool *need_update_dialog_pos, const char *source);

  unique_ptr<Message> do_delete_message(Dialog *d, MessageId message_id, bool is_permanently_deleted,
                                        bool only_from_memory, bool *need_update_dialog_pos, const char *source);

  void unload_dialog(DialogId dialog_id);

  void delete_all_dialog_messages(Dialog *d, bool remove_from_dialog_list, bool is_permanent);

  void do_delete_all_dialog_messages(Dialog *d, unique_ptr<Message> &m, vector<int64> &deleted_message_ids);

  void delete_messages_from_server(DialogId dialog_id, vector<MessageId> message_ids, bool revoke, uint64 logevent_id,
                                   Promise<Unit> &&promise);

  void delete_dialog_history_from_server(DialogId dialog_id, MessageId max_message_id, bool remove_from_dialog_list,
                                         bool allow_error, uint64 logevent_id, Promise<Unit> &&promise);

  void delete_all_channel_messages_from_user_on_server(ChannelId channel_id, UserId user_id, uint64 logevent_id,
                                                       Promise<Unit> &&promise);

  void read_all_dialog_mentions_on_server(DialogId dialog_id, uint64 logevent_id, Promise<Unit> &&promise);

  static MessageId find_message_by_date(const unique_ptr<Message> &m, int32 date);

  static void find_messages_from_user(const unique_ptr<Message> &m, UserId user_id, vector<MessageId> &message_ids);

  static void find_unread_mentions(const unique_ptr<Message> &m, vector<MessageId> &message_ids);

  static void find_old_messages(const unique_ptr<Message> &m, MessageId max_message_id, vector<MessageId> &message_ids);

  void find_unloadable_messages(const Dialog *d, int32 unload_before_date, const unique_ptr<Message> &m,
                                vector<MessageId> &message_ids, int32 &left_to_unload) const;

  bool message_views_enabled(DialogId dialog_id) const;

  bool update_message_views(DialogId dialog_id, Message *m, int32 views);

  bool update_message_contains_unread_mention(Dialog *d, Message *m, bool contains_unread_mention, const char *source);

  static bool update_opened_message_content(Message *m);

  void read_message_content_from_updates(MessageId message_id);

  void read_channel_message_content_from_updates(Dialog *d, MessageId message_id);

  bool read_message_content(Dialog *d, Message *m, bool is_local_read, const char *source);

  void read_message_contents_on_server(DialogId dialog_id, vector<MessageId> message_ids, uint64 logevent_id);

  void read_history_inbox(DialogId dialog_id, MessageId max_message_id, int32 unread_count, const char *source);

  void read_history_outbox(DialogId dialog_id, MessageId max_message_id, int32 read_date = -1);

  void read_history_on_server(DialogId dialog_id, MessageId max_message_id, bool allow_error, uint64 logevent_id);

  void read_secret_chat_outbox_inner(DialogId dialog_id, int32 up_to_date, int32 read_date);

  void set_dialog_max_unavailable_message_id(DialogId dialog_id, MessageId max_unavailable_message_id, bool from_update,
                                             const char *source);

  void preload_newer_messages(const Dialog *d, MessageId max_message_id);

  void preload_older_messages(const Dialog *d, MessageId min_message_id);

  void on_get_history_from_database(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit,
                                    bool from_the_end, bool only_local, vector<BufferSlice> &&messages,
                                    Promise<Unit> &&promise);

  void get_history_from_the_end(DialogId dialog_id, bool from_database, bool only_local, Promise<Unit> &&promise);

  void get_history(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit, bool from_database,
                   bool only_local, Promise<Unit> &&promise);

  void load_messages(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit, int left_tries,
                     bool only_local, Promise<Unit> &&promise);

  static int32 get_random_y(MessageId message_id);

  bool is_allowed_useless_update(const tl_object_ptr<telegram_api::Update> &update) const;

  bool is_message_auto_read(DialogId dialog_id, bool is_outgoing, bool only_content) const;

  void fail_send_message(FullMessageId full_message_id, int error_code, const string &error_message);

  void on_dialog_updated(DialogId dialog_id, const char *source);

  BufferSlice get_dialog_database_value(const Dialog *d);

  void save_dialog_to_database(DialogId dialog_id);

  void on_save_dialog_to_database(DialogId dialog_id, bool success);

  void load_dialog_list(Promise<Unit> &&promise);

  void load_dialog_list_from_database(int32 limit, Promise<Unit> &&promise);

  static void preload_dialog_list(void *messages_manager_void);

  void update_message_count_by_index(Dialog *d, int diff, const Message *m);

  int32 get_message_index_mask(DialogId dialog_id, const Message *m) const;

  int32 get_message_content_index_mask(const MessageContent *content, bool is_secret, bool is_outgoing) const;

  Message *add_message_to_dialog(DialogId dialog_id, unique_ptr<Message> message, bool from_update, bool *need_update,
                                 bool *need_update_dialog_pos, const char *source);

  Message *add_message_to_dialog(Dialog *d, unique_ptr<Message> message, bool from_update, bool *need_update,
                                 bool *need_update_dialog_pos, const char *source);

  void on_message_changed(const Dialog *d, const Message *m, const char *source);

  void add_message_to_database(const Dialog *d, const Message *m, const char *source);

  void delete_all_dialog_messages_from_database(DialogId dialog_id, MessageId message_id, const char *source);

  void delete_message_from_database(Dialog *d, MessageId message_id, const Message *m,
                                    bool is_permanently_deleted) const;

  void delete_message_files(const Message *m) const;

  void do_delete_message_logevent(const DeleteMessageLogEvent &logevent) const;

  void attach_message_to_previous(Dialog *d, MessageId message_id);

  void attach_message_to_next(Dialog *d, MessageId message_id);

  void update_message(Dialog *d, unique_ptr<Message> &old_message, unique_ptr<Message> new_message,
                      bool need_send_update_message_content, bool *need_update_dialog_pos);

  bool need_message_text_changed_warning(const Message *old_message, const MessageText *old_content,
                                         const MessageText *new_content);

  bool update_message_content(DialogId dialog_id, const Message *old_message, unique_ptr<MessageContent> &old_content,
                              unique_ptr<MessageContent> new_content, bool need_send_update_message_content);

  void send_update_new_message(Dialog *d, const Message *m, bool force = false);

  void flush_pending_update_new_messages(DialogId dialog_id);

  void send_update_message_send_succeeded(Dialog *d, MessageId old_message_id, const Message *m) const;

  void send_update_message_content(DialogId dialog_id, MessageId message_id, const MessageContent *content,
                                   const char *source) const;

  void send_update_message_edited(FullMessageId full_message_id) const;

  void send_update_message_edited(DialogId dialog_id, const Message *m) const;

  void send_update_delete_messages(DialogId dialog_id, vector<int64> &&message_ids, bool is_permanent,
                                   bool from_cache) const;

  void send_update_chat(Dialog *d);

  void send_update_chat_draft_message(const Dialog *d);

  void send_update_chat_last_message(Dialog *d, const char *source);

  void send_update_chat_last_message_impl(const Dialog *d, const char *source) const;

  void send_update_chat_read_inbox(const Dialog *d, bool force, const char *source);

  void send_update_chat_read_outbox(const Dialog *d);

  void send_update_chat_unread_mention_count(const Dialog *d);

  tl_object_ptr<td_api::message> get_message_object(DialogId dialog_id, const Message *message) const;

  static tl_object_ptr<td_api::messages> get_messages_object(int32 total_count,
                                                             vector<tl_object_ptr<td_api::message>> &&messages);

  vector<DialogId> sort_dialogs_by_order(const vector<DialogId> &dialog_ids, int32 limit) const;

  vector<DialogId> get_peers_dialog_ids(vector<tl_object_ptr<telegram_api::Peer>> &&peers);

  void set_dialog_last_read_inbox_message_id(Dialog *d, MessageId message_id, int32 server_unread_count,
                                             int32 local_unread_count, bool force_update, const char *source);

  void set_dialog_last_read_outbox_message_id(Dialog *d, MessageId message_id);

  void set_dialog_last_message_id(Dialog *d, MessageId last_message_id, const char *source);

  void set_dialog_first_database_message_id(Dialog *d, MessageId first_database_message_id, const char *source);

  void set_dialog_last_database_message_id(Dialog *d, MessageId last_database_message_id, const char *source);

  void set_dialog_last_new_message_id(Dialog *d, MessageId last_new_message_id, const char *source);

  void set_dialog_last_clear_history_date(Dialog *d, int32 date, MessageId last_clear_history_message_id,
                                          const char *source);

  void set_dialog_is_empty(Dialog *d, const char *source);

  static int32 get_pinned_dialogs_limit();

  static vector<DialogId> remove_secret_chat_dialog_ids(vector<DialogId> dialog_ids);

  void set_dialog_is_pinned(Dialog *d, bool is_pinned);

  void toggle_dialog_is_pinned_on_server(DialogId dialog_id, bool is_pinned, uint64 logevent_id);

  void reorder_pinned_dialogs_on_server(const vector<DialogId> &dialog_ids, uint64 logevent_id);

  void set_dialog_reply_markup(Dialog *d, MessageId message_id);

  void try_restore_dialog_reply_markup(Dialog *d, const Message *m);

  static string get_notification_settings_scope_database_key(NotificationSettingsScope scope);

  bool update_notification_settings(NotificationSettingsScope scope, NotificationSettings *current_settings,
                                    const NotificationSettings &new_settings);

  Dialog *get_dialog_by_message_id(MessageId message_id);

  MessageId get_message_id_by_random_id(Dialog *d, int64 random_id);

  Dialog *add_dialog(DialogId dialog_id);

  Dialog *add_new_dialog(unique_ptr<Dialog> &&d, bool is_loaded_from_database);

  void add_dialog_last_database_message(Dialog *d, unique_ptr<Message> &&last_database_message);

  tl_object_ptr<td_api::inputMessageText> get_input_message_text_object(
      const InputMessageText &input_message_text) const;

  tl_object_ptr<td_api::draftMessage> get_draft_message_object(const unique_ptr<DraftMessage> &draft_message) const;

  tl_object_ptr<td_api::ChatType> get_chat_type_object(DialogId dialog_id) const;

  tl_object_ptr<td_api::chat> get_chat_object(const Dialog *d);

  bool have_dialog_info(DialogId dialog_id) const;
  bool have_dialog_info_force(DialogId dialog_id) const;

  Dialog *get_dialog(DialogId dialog_id);
  const Dialog *get_dialog(DialogId dialog_id) const;

  Dialog *get_dialog_force(DialogId dialog_id);

  Dialog *on_load_dialog_from_database(const Result<BufferSlice> &r_value);

  void on_get_dialogs_from_database(vector<BufferSlice> &&dialogs, Promise<Unit> &&promise);

  void send_get_dialog_query(DialogId dialog_id, Promise<Unit> &&promise);

  void send_search_public_dialogs_query(const string &query, Promise<Unit> &&promise);

  vector<DialogId> get_pinned_dialogs() const;

  void update_dialogs_hints(const Dialog *d);
  void update_dialogs_hints_rating(const Dialog *d);

  std::pair<int32, vector<DialogParticipant>> search_private_chat_participants(UserId my_user_id, UserId peer_user_id,
                                                                               const string &query, int32 limit) const;

  static unique_ptr<Message> *find_message(unique_ptr<Message> *v, MessageId message_id);
  static const unique_ptr<Message> *find_message(const unique_ptr<Message> *v, MessageId message_id);

  static Message *get_message(Dialog *d, MessageId message_id);
  static const Message *get_message(const Dialog *d, MessageId message_id);

  Message *get_message(FullMessageId full_message_id);
  const Message *get_message(FullMessageId full_message_id) const;

  Message *get_message_force(Dialog *d, MessageId message_id);

  Message *get_message_force(FullMessageId full_message_id);

  void get_message_force_from_server(Dialog *d, MessageId message_id, Promise<Unit> &&promise,
                                     tl_object_ptr<telegram_api::InputMessage> input_message = nullptr);

  Message *on_get_message_from_database(DialogId dialog_id, Dialog *d, const BufferSlice &value);

  void get_dialog_message_by_date_from_server(const Dialog *d, int32 date, int64 random_id, bool after_database_search,
                                              Promise<Unit> &&promise);

  void on_get_dialog_message_by_date_from_database(DialogId dialog_id, int32 date, int64 random_id,
                                                   Result<BufferSlice> result, Promise<Unit> promise);

  static NotificationSettings get_notification_settings(
      tl_object_ptr<telegram_api::PeerNotifySettings> &&notification_settings);

  const NotificationSettings *get_dialog_notification_settings(const Dialog *d, DialogId dialog_id) const;

  NotificationSettings *get_notification_settings(NotificationSettingsScope scope, bool force);

  static unique_ptr<DraftMessage> get_draft_message(ContactsManager *contacts_manager,
                                                    tl_object_ptr<telegram_api::DraftMessage> &&draft_message_ptr);

  static FormattedText get_secret_media_caption(string &&message_text, string &&message_caption);

  Photo get_web_document_photo(tl_object_ptr<telegram_api::webDocument> web_document, DialogId owner_dialog_id) const;

  unique_ptr<MessageContent> get_secret_message_document(
      tl_object_ptr<telegram_api::encryptedFile> file,
      tl_object_ptr<secret_api::decryptedMessageMediaDocument> &&document,
      vector<tl_object_ptr<telegram_api::DocumentAttribute>> &&attributes, DialogId owner_dialog_id,
      FormattedText &&caption, bool is_opened) const;

  unique_ptr<MessageContent> get_message_document(tl_object_ptr<telegram_api::document> &&document,
                                                  DialogId owner_dialog_id, FormattedText &&caption, bool is_opened,
                                                  MultiPromiseActor *load_data_multipromise_ptr) const;

  unique_ptr<MessageContent> get_message_document(std::pair<DocumentsManager::DocumentType, FileId> &&parsed_document,
                                                  FormattedText &&caption, bool is_opened) const;

  unique_ptr<MessagePhoto> get_message_photo(tl_object_ptr<telegram_api::photo> &&photo, DialogId owner_dialog_id,
                                             FormattedText &&caption) const;

  unique_ptr<MessageContent> get_secret_message_content(
      string message_text, tl_object_ptr<telegram_api::encryptedFile> file,
      tl_object_ptr<secret_api::DecryptedMessageMedia> &&media,
      vector<tl_object_ptr<secret_api::MessageEntity>> &&secret_entities, DialogId owner_dialog_id,
      MultiPromiseActor &load_data_multipromise) const;

  unique_ptr<MessageContent> get_message_content(FormattedText message_text,
                                                 tl_object_ptr<telegram_api::MessageMedia> &&media,
                                                 DialogId owner_dialog_id, bool is_content_read, UserId via_bot_user_id,
                                                 int32 *ttl) const;

  unique_ptr<MessageContent> dup_message_content(DialogId dialog_id, const MessageContent *content, bool for_forward);

  unique_ptr<MessageContent> get_message_action_content(tl_object_ptr<telegram_api::MessageAction> &&action,
                                                        DialogId owner_dialog_id, MessageId reply_to_message_id) const;

  tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageContent *content) const;

  static FormattedText get_message_content_caption(const MessageContent *content);

  int32 get_message_content_duration(const MessageContent *content) const;

  static FileId get_message_content_file_id(const MessageContent *content);

  FileId get_message_content_thumbnail_file_id(const MessageContent *content) const;

  vector<FileId> get_message_file_ids(const Message *message) const;

  void cancel_send_message_query(DialogId dialog_id, unique_ptr<Message> &m);

  static int32 get_message_flags(const Message *m);

  unique_ptr<MessageForwardInfo> get_message_forward_info(
      tl_object_ptr<telegram_api::messageFwdHeader> &&forward_header);

  tl_object_ptr<td_api::MessageForwardInfo> get_message_forward_info_object(
      const unique_ptr<MessageForwardInfo> &forward_info) const;

  void ttl_read_history_inbox(DialogId dialog_id, MessageId from_message_id, MessageId till_message_id,
                              double timestamp);
  void ttl_read_history_outbox(DialogId dialog_id, MessageId from_message_id, MessageId till_message_id,
                               double timestamp);
  void ttl_on_view(const Dialog *d, Message *message, double view_date, double now);
  bool ttl_on_open(Dialog *d, Message *message, double now, bool is_local_read);
  void ttl_register_message(DialogId dialog_id, const Message *message, double now);
  void ttl_unregister_message(DialogId dialog_id, const Message *message, double now);
  void ttl_loop(double now);
  void ttl_update_timeout(double now);

  void on_message_ttl_expired(Dialog *d, Message *message);
  void on_message_ttl_expired_impl(Dialog *d, Message *message);

  void start_up() override;
  void loop() override;
  void tear_down() override;

  void ttl_db_loop_start(double server_now);
  void ttl_db_loop(double server_now);
  void ttl_db_on_result(Result<std::pair<std::vector<std::pair<DialogId, BufferSlice>>, int32>> r_result, bool dummy);

  static MessageId get_first_database_message_id_by_index(const Dialog *d, SearchMessagesFilter filter);

  void on_search_dialog_messages_db_result(int64 random_id, DialogId dialog_id, MessageId from_message_id,
                                           MessageId first_db_message_id, SearchMessagesFilter filter_type,
                                           int32 offset, int32 limit, Result<MessagesDbMessagesResult> result,
                                           Promise<> promise);

  void on_messages_db_fts_result(Result<MessagesDbFtsResult> result, int64 random_id, Promise<> &&promise);

  void on_messages_db_calls_result(Result<MessagesDbCallsResult> result, int64 random_id, MessageId first_db_message_id,
                                   SearchMessagesFilter filter, Promise<> &&promise);

  void on_load_active_live_location_full_message_ids_from_database(string value);

  void on_load_active_live_location_messages_finished();

  void try_add_active_live_location(DialogId dialog_id, const Message *m);

  void add_active_live_location(FullMessageId full_message_id);

  bool delete_active_live_location(DialogId dialog_id, const Message *m);

  void save_active_live_locations();

  Result<unique_ptr<ReplyMarkup>> get_dialog_reply_markup(
      DialogId dialog_id, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup_ptr) const TD_WARN_UNUSED_RESULT;

  const DialogPhoto *get_dialog_photo(DialogId dialog_id) const;

  string get_dialog_title(DialogId dialog_id) const;

  string get_dialog_username(DialogId dialog_id) const;

  static int64 get_dialog_order(MessageId message_id, int32 message_date);

  bool update_dialog_draft_message(Dialog *d, unique_ptr<DraftMessage> &&draft_message, bool from_update,
                                   bool need_update_dialog_pos);

  void save_dialog_draft_message_on_server(DialogId dialog_id);

  void on_saved_dialog_draft_message(DialogId dialog_id, uint64 generation);

  int64 get_next_pinned_dialog_order();

  void update_dialog_pos(Dialog *d, bool remove_from_dialog_list, const char *source,
                         bool need_send_update_chat_order = true);

  bool set_dialog_order(Dialog *d, int64 new_order, bool need_send_update_chat_order);

  void update_last_dialog_date();

  void load_notification_settings();

  void set_get_difference_timeout(double timeout);

  void skip_old_pending_update(tl_object_ptr<telegram_api::Update> &&update, int32 new_pts, int32 old_pts,
                               int32 pts_count, const char *source);

  void process_pending_updates();

  void drop_pending_updates();

  static string get_channel_pts_key(DialogId dialog_id);

  int32 load_channel_pts(DialogId dialog_id) const;

  void set_channel_pts(Dialog *d, int32 new_pts, const char *source) const;

  bool running_get_channel_difference(DialogId dialog_id) const;

  void get_channel_difference(DialogId dialog_id, int32 pts, bool force, const char *source);

  void do_get_channel_difference(DialogId dialog_id, int32 pts, bool force,
                                 tl_object_ptr<telegram_api::InputChannel> &&input_channel, const char *source);

  void process_get_channel_difference_updates(DialogId dialog_id,
                                              vector<tl_object_ptr<telegram_api::Message>> &&new_messages,
                                              vector<tl_object_ptr<telegram_api::Update>> &&other_updates);

  void on_get_channel_dialog(DialogId dialog_id, MessageId last_message_id, MessageId read_inbox_max_message_id,
                             int32 server_unread_count, int32 unread_mention_count,
                             MessageId read_outbox_max_message_id,
                             vector<tl_object_ptr<telegram_api::Message>> &&messages);

  void after_get_channel_difference(DialogId dialog_id, bool success);

  static void on_channel_get_difference_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int);

  static void on_pending_message_views_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int);

  static void on_pending_draft_message_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int);

  static void on_pending_updated_dialog_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int);

  static void on_pending_unload_dialog_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int);

  void load_secret_thumbnail(FileId thumbnail_file_id);

  static tl_object_ptr<telegram_api::channelAdminLogEventsFilter> get_channel_admin_log_events_filter(
      const tl_object_ptr<td_api::chatEventLogFilters> &filters);

  tl_object_ptr<td_api::ChatEventAction> get_chat_event_action_object(
      tl_object_ptr<telegram_api::ChannelAdminLogEventAction> &&action_ptr);

  void on_upload_media(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file,
                       tl_object_ptr<telegram_api::InputEncryptedFile> input_encrypted_file);
  void on_upload_media_error(FileId file_id, Status status);

  void on_load_secret_thumbnail(FileId thumbnail_file_id, BufferSlice thumbnail);
  void on_upload_thumbnail(FileId thumbnail_file_id, tl_object_ptr<telegram_api::InputFile> thumbnail_input_file);

  void on_upload_dialog_photo(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file);
  void on_upload_dialog_photo_error(FileId file_id, Status status);

  static uint64 get_sequence_dispatcher_id(DialogId dialog_id, int32 message_content_type);

  Dialog *get_service_notifications_dialog();

  static MessageId get_next_message_id(Dialog *d, int32 type);

  static MessageId get_next_local_message_id(Dialog *d);

  static MessageId get_next_yet_unsent_message_id(Dialog *d);

  bool add_recently_found_dialog_internal(DialogId dialog_id);

  bool remove_recently_found_dialog_internal(DialogId dialog_id);

  void save_recently_found_dialogs();
  bool load_recently_found_dialogs(Promise<Unit> &promise);

  void update_used_hashtags(DialogId dialog_id, const Message *m);

  string get_search_text(const Message *m);

  unique_ptr<Dialog> parse_dialog(DialogId dialog_id, const BufferSlice &value);

  void load_calls_db_state();
  void save_calls_db_state();

  static constexpr bool is_debug_message_op_enabled();

  static void dump_debug_message_op(const Dialog *d, int priority = 0);

  void add_message_dependencies(Dependencies &dependencies, DialogId dialog_id, const Message *m);

  void add_dialog_dependencies(Dependencies &dependencies, DialogId dialog_id);

  void resolve_dependencies_force(const Dependencies &dependencies);

  void save_send_message_logevent(DialogId dialog_id, Message *m);

  void suffix_load_loop(Dialog *d);
  void suffix_load_update_first_message_id(Dialog *d);
  void suffix_load_query_ready(DialogId dialog_id);
  void suffix_load_add_query(Dialog *d, std::pair<Promise<>, std::function<bool(const Message *)>> query);
  void suffix_load_till_date(Dialog *d, int32 date, Promise<> promise);
  void suffix_load_till_message_id(Dialog *d, MessageId message_id, Promise<> promise);

  Result<ServerMessageId> get_invoice_message_id(FullMessageId full_message_id);

  bool is_broadcast_channel(DialogId dialog_id) const;

  int32 recently_found_dialogs_loaded_ = 0;  // 0 - not loaded, 1 - load request was sent, 2 - loaded
  MultiPromiseActor resolve_recent_found_dialogs_multipromise_;

  vector<DialogId> recently_found_dialog_ids_;

  class UploadMediaCallback;
  class UploadThumbnailCallback;
  class UploadDialogPhotoCallback;

  std::shared_ptr<UploadMediaCallback> upload_media_callback_;
  std::shared_ptr<UploadThumbnailCallback> upload_thumbnail_callback_;
  std::shared_ptr<UploadDialogPhotoCallback> upload_dialog_photo_callback_;

  int32 accumulated_pts_count_ = 0;
  int32 accumulated_pts_ = -1;
  Timeout pts_gap_timeout_;

  std::unordered_map<FileId, std::pair<FullMessageId, FileId>, FileIdHash>
      being_uploaded_files_;  // file_id -> message, thumbnail_file_id
  struct UploadedThumbnailInfo {
    FullMessageId full_message_id;
    FileId file_id;                                     // original file file_id
    tl_object_ptr<telegram_api::InputFile> input_file;  // original file InputFile
  };
  std::unordered_map<FileId, UploadedThumbnailInfo, FileIdHash> being_uploaded_thumbnails_;  // thumbnail_file_id -> ...
  struct UploadedSecretThumbnailInfo {
    FullMessageId full_message_id;
    FileId file_id;                                              // original file file_id
    tl_object_ptr<telegram_api::InputEncryptedFile> input_file;  // original file InputEncryptedFile
  };
  std::unordered_map<FileId, UploadedSecretThumbnailInfo, FileIdHash>
      being_loaded_secret_thumbnails_;  // thumbnail_file_id -> ...

  // TTL
  class TtlNode : private HeapNode {
   public:
    TtlNode(DialogId dialog_id, MessageId message_id) : full_message_id(dialog_id, message_id) {
    }

    FullMessageId full_message_id;

    HeapNode *as_heap_node() const {
      return const_cast<HeapNode *>(static_cast<const HeapNode *>(this));
    }
    static TtlNode *from_heap_node(HeapNode *node) {
      return static_cast<TtlNode *>(node);
    }

    bool operator==(const TtlNode &other) const {
      return full_message_id == other.full_message_id;
    }
  };
  struct TtlNodeHash {
    std::size_t operator()(const TtlNode &ttl_node) const {
      return FullMessageIdHash()(ttl_node.full_message_id);
    }
  };
  std::unordered_set<TtlNode, TtlNodeHash> ttl_nodes_;
  KHeap<double> ttl_heap_;
  Slot ttl_slot_;

  enum YieldType { None, Ttl, TtlDb };  // None should be first
  int32 ttl_db_expire_from_;
  int32 ttl_db_expire_till_;
  bool ttl_db_has_query_;
  Slot ttl_db_slot_;

  std::unordered_set<int64> message_random_ids_;
  std::unordered_map<int64, FullMessageId> being_sent_messages_;  // message_random_id -> message

  std::unordered_map<FullMessageId, MessageId, FullMessageIdHash>
      update_message_ids_;                                         // full_message_id -> temporary_id
  std::unordered_map<int64, DialogId> debug_being_sent_messages_;  // message_random_id -> dialog_id

  const char *debug_add_message_to_dialog_fail_reason = "";

  struct UploadedDialogPhotoInfo {
    Promise<Unit> promise;
    DialogId dialog_id;
  };
  std::unordered_map<FileId, UploadedDialogPhotoInfo, FileIdHash> uploaded_dialog_photos_;  // file_id -> ...

  struct PendingMessageGroupSend {
    DialogId dialog_id;
    size_t finished_count = 0;
    vector<MessageId> message_ids;
    vector<bool> is_finished;
    vector<Status> results;
  };
  std::unordered_map<int64, PendingMessageGroupSend> pending_message_group_sends_;  // media_album_id -> ...

  std::unordered_map<MessageId, DialogId, MessageIdHash> message_id_to_dialog_id_;
  std::unordered_map<MessageId, DialogId, MessageIdHash> last_clear_history_message_id_to_dialog_id_;

  std::unordered_map<int64, DialogId> created_dialogs_;                                // random_id -> dialog_id
  std::unordered_map<DialogId, Promise<Unit>, DialogIdHash> pending_created_dialogs_;  // dialog_id -> promise

  bool running_get_difference_ = false;  // true after before_get_difference and false after after_get_difference

  std::unordered_map<DialogId, unique_ptr<Dialog>, DialogIdHash> dialogs_;
  std::multimap<int32, PendingPtsUpdate> pending_updates_;
  std::multimap<int32, PendingPtsUpdate> postponed_pts_updates_;

  std::unordered_set<DialogId, DialogIdHash>
      loaded_dialogs_;  // dialogs loaded from database, but not added to dialogs_

  std::unordered_set<DialogId, DialogIdHash> postponed_chat_read_inbox_updates_;
  std::unordered_map<DialogId, vector<std::pair<MessageId, Promise<Unit>>>, DialogIdHash>
      postponed_get_message_requests_;

  std::unordered_map<string, vector<Promise<Unit>>> search_public_dialogs_queries_;
  std::unordered_map<string, vector<DialogId>> found_public_dialogs_;     // TODO time bound cache
  std::unordered_map<string, vector<DialogId>> found_on_server_dialogs_;  // TODO time bound cache

  std::unordered_map<UserId, vector<DialogId>, UserIdHash> found_common_dialogs_;  // TODO time bound cache

  std::unordered_map<int64, FullMessageId> get_dialog_message_by_date_results_;

  std::unordered_map<int64, std::pair<int32, vector<MessageId>>>
      found_dialog_messages_;  // random_id -> [total_count, [message_id]...]
  std::unordered_map<int64, std::pair<int32, vector<FullMessageId>>>
      found_messages_;  // random_id -> [total_count, [full_message_id]...]
  std::unordered_map<int64, std::pair<int32, vector<FullMessageId>>>
      found_call_messages_;  // random_id -> [total_count, [full_message_id]...]
  std::unordered_map<int64, std::pair<int32, vector<MessageId>>>
      found_dialog_recent_location_messages_;  // random_id -> [total_count, [message_id]...]

  std::unordered_map<int64, std::pair<int64, vector<FullMessageId>>>
      found_fts_messages_;  // random_id -> [from_search_id, [full_message_id]...]

  std::unordered_map<FullMessageId, std::pair<string, string>, FullMessageIdHash> public_message_links_[2];

  std::unordered_map<int64, tl_object_ptr<td_api::chatEvents>> chat_events_;  // random_id -> chat events

  std::unordered_map<int64, tl_object_ptr<td_api::gameHighScores>> game_high_scores_;  // random_id -> high scores

  std::unordered_map<DialogId, vector<Promise<Unit>>, DialogIdHash> get_dialog_queries_;

  std::unordered_map<FullMessageId, int32, FullMessageIdHash> replied_by_yet_unsent_messages_;

  std::unordered_set<FullMessageId, FullMessageIdHash> waiting_for_web_page_messages_;

  NotificationSettings users_notification_settings_;
  NotificationSettings chats_notification_settings_;
  NotificationSettings dialogs_notification_settings_;

  // uint32 preloaded_dialogs_ = 0;  // TODO remove variable

  int64 current_pinned_dialog_order_ = DEFAULT_ORDER;

  std::set<DialogDate> ordered_dialogs_;
  std::set<DialogDate> ordered_server_dialogs_;

  // date of last dialog in the dialog list
  // last_dialog_date_ == min(last_server_dialog_date_, last_secret_chat_dialog_date_)
  DialogDate last_dialog_date_ = MIN_DIALOG_DATE;  // in memory

  // date of last known user/group/channel dialog in the right order
  DialogDate last_server_dialog_date_ = MIN_DIALOG_DATE;
  DialogDate last_loaded_database_dialog_date_ = MIN_DIALOG_DATE;
  DialogDate last_database_server_dialog_date_ = MIN_DIALOG_DATE;

  MultiPromiseActor load_dialog_list_multipromise_;  // should be defined before pending_on_get_dialogs_
  Timeout preload_dialog_list_timeout_;

  std::unordered_map<DialogId, string, DialogIdHash> active_get_channel_differencies_;
  std::unordered_map<DialogId, uint64, DialogIdHash> get_channel_difference_to_logevent_id_;

  MultiTimeout channel_get_difference_timeout_;
  MultiTimeout channel_get_difference_retry_timeout_;
  MultiTimeout pending_message_views_timeout_;
  MultiTimeout pending_draft_message_timeout_;
  MultiTimeout pending_updated_dialog_timeout_;
  MultiTimeout pending_unload_dialog_timeout_;

  Hints dialogs_hints_;  // search dialogs by title and username

  std::unordered_set<FullMessageId, FullMessageIdHash> active_live_location_full_message_ids_;
  bool are_active_live_location_messages_loaded_ = false;
  vector<Promise<Unit>> load_active_live_location_messages_queries_;

  struct ResolvedUsername {
    DialogId dialog_id;
    double expires_at;
  };

  std::unordered_map<string, ResolvedUsername> resolved_usernames_;
  std::unordered_map<string, DialogId> unaccessible_resolved_usernames_;

  struct PendingOnGetDialogs {
    vector<tl_object_ptr<telegram_api::dialog>> dialogs;
    int32 total_count;
    vector<tl_object_ptr<telegram_api::Message>> messages;
    Promise<Unit> promise;
  };

  vector<PendingOnGetDialogs> pending_on_get_dialogs_;
  std::unordered_map<DialogId, PendingOnGetDialogs, DialogIdHash> pending_channel_on_get_dialogs_;

  ChangesProcessor<unique_ptr<PendingSecretMessage>> pending_secret_messages_;

  std::unordered_map<DialogId, vector<DialogId>, DialogIdHash>
      pending_add_dialog_last_database_message_dependent_dialogs_;
  std::unordered_map<DialogId, std::pair<int32, unique_ptr<Message>>, DialogIdHash>
      pending_add_dialog_last_database_message_;  // dialog -> dependency counter + message

  CallsDbState calls_db_state_;

  std::unordered_map<uint64, std::map<int64, Promise<Message *>>> yet_unsent_media_queues_;

  Td *td_;
  ActorShared<> parent_;
};

}  // namespace td