From 1b73b34810c264bc5b146c9e4b27159c4d000375 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Wed, 14 Dec 2022 16:15:46 +0400
Subject: [PATCH] Make HistoryItem a final class.

---
 Telegram/CMakeLists.txt                       |    6 +-
 Telegram/SourceFiles/api/api_polls.cpp        |    3 +-
 Telegram/SourceFiles/api/api_sending.cpp      |    3 +-
 Telegram/SourceFiles/apiwrap.cpp              |    3 +-
 .../boxes/background_preview_box.cpp          |    2 +-
 .../boxes/peers/edit_peer_invite_link.cpp     |    2 +-
 .../boxes/reactions_settings_box.cpp          |    2 +-
 Telegram/SourceFiles/boxes/share_box.cpp      |    3 +-
 .../calls/calls_box_controller.cpp            |    1 +
 .../calls/group/calls_group_settings.cpp      |    2 +-
 .../chat_helpers/message_field.cpp            |    2 +-
 .../data/data_download_manager.cpp            |    2 +-
 .../SourceFiles/data/data_media_types.cpp     |   11 +-
 Telegram/SourceFiles/data/data_media_types.h  |    7 +-
 .../SourceFiles/data/data_replies_list.cpp    |   10 +-
 Telegram/SourceFiles/data/data_replies_list.h |    3 +-
 .../data/data_scheduled_messages.cpp          |    2 +-
 Telegram/SourceFiles/data/data_session.h      |    2 -
 .../data/data_sponsored_messages.cpp          |    2 +-
 .../SourceFiles/dialogs/ui/dialogs_layout.cpp |    3 +-
 .../admin_log/history_admin_log_inner.cpp     |   17 +-
 .../admin_log/history_admin_log_inner.h       |    6 -
 .../admin_log/history_admin_log_item.cpp      |   26 +-
 Telegram/SourceFiles/history/history.cpp      |   10 +-
 Telegram/SourceFiles/history/history.h        |   20 +-
 .../history/history_inner_widget.cpp          |   19 +-
 Telegram/SourceFiles/history/history_item.cpp | 3829 +++++++++++++++--
 Telegram/SourceFiles/history/history_item.h   |  434 +-
 .../history/history_item_components.cpp       |   23 +-
 .../history/history_item_components.h         |  108 +-
 .../history/history_item_helpers.cpp          |  656 +++
 .../history/history_item_helpers.h            |  133 +
 .../SourceFiles/history/history_message.cpp   | 1883 --------
 .../SourceFiles/history/history_message.h     |  276 --
 .../SourceFiles/history/history_service.cpp   | 1821 --------
 .../SourceFiles/history/history_service.h     |  235 -
 .../SourceFiles/history/history_widget.cpp    |    2 +-
 .../controls/history_view_forward_panel.cpp   |    3 +-
 .../history/view/history_view_bottom_info.cpp |    4 +-
 .../view/history_view_context_menu.cpp        |    1 -
 .../history/view/history_view_element.cpp     |   18 +-
 .../history/view/history_view_element.h       |   14 -
 .../history/view/history_view_list_widget.cpp |   15 +-
 .../history/view/history_view_list_widget.h   |    6 -
 .../history/view/history_view_message.cpp     |   81 +-
 .../history/view/history_view_message.h       |    6 +-
 .../view/history_view_service_message.cpp     |   15 +-
 .../view/history_view_service_message.h       |    8 +-
 .../view/media/history_view_media_grouped.cpp |    2 +-
 .../view/reactions/history_view_reactions.cpp |    4 +-
 .../downloads/info_downloads_provider.cpp     |    1 +
 .../info/media/info_media_list_widget.cpp     |    3 +-
 .../info/media/info_media_provider.cpp        |    1 +
 .../inline_bots/inline_bot_send_data.cpp      |    2 +-
 Telegram/SourceFiles/mainwidget.cpp           |    3 +-
 .../media/player/media_player_widget.cpp      |    1 +
 .../media/view/media_view_overlay_widget.cpp  |    3 +-
 .../SourceFiles/overview/overview_layout.cpp  |    7 +-
 .../SourceFiles/payments/payments_form.cpp    |    2 +-
 .../settings/settings_privacy_controllers.cpp |    4 +-
 .../support/support_autocomplete.cpp          |    2 +-
 .../SourceFiles/window/window_peer_menu.cpp   |    3 +-
 62 files changed, 4662 insertions(+), 5116 deletions(-)
 create mode 100644 Telegram/SourceFiles/history/history_item_helpers.cpp
 create mode 100644 Telegram/SourceFiles/history/history_item_helpers.h
 delete mode 100644 Telegram/SourceFiles/history/history_message.cpp
 delete mode 100644 Telegram/SourceFiles/history/history_message.h
 delete mode 100644 Telegram/SourceFiles/history/history_service.cpp
 delete mode 100644 Telegram/SourceFiles/history/history_service.h

diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index 95a70f8fb..8360eefeb 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -764,6 +764,8 @@ PRIVATE
     history/history_item_components.h
     history/history_item_edition.cpp
     history/history_item_edition.h
+    history/history_item_helpers.cpp
+    history/history_item_helpers.h
     history/history_item_reply_markup.cpp
     history/history_item_reply_markup.h
     history/history_item_text.cpp
@@ -772,10 +774,6 @@ PRIVATE
     history/history_inner_widget.h
     history/history_location_manager.cpp
     history/history_location_manager.h
-    history/history_message.cpp
-    history/history_message.h
-    history/history_service.cpp
-    history/history_service.h
     history/history_unread_things.cpp
     history/history_unread_things.h
     history/history_view_highlight_manager.cpp
diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp
index 85f34e2c2..d18c947c1 100644
--- a/Telegram/SourceFiles/api/api_polls.cpp
+++ b/Telegram/SourceFiles/api/api_polls.cpp
@@ -16,7 +16,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_poll.h"
 #include "data/data_session.h"
 #include "history/history.h"
-#include "history/history_message.h" // ShouldSendSilent
+#include "history/history_item.h"
+#include "history/history_item_helpers.h" // ShouldSendSilent
 #include "main/main_session.h"
 
 namespace Api {
diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp
index 917c6d002..b2004f70b 100644
--- a/Telegram/SourceFiles/api/api_sending.cpp
+++ b/Telegram/SourceFiles/api/api_sending.cpp
@@ -20,7 +20,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_changes.h"
 #include "data/stickers/data_stickers.h"
 #include "history/history.h"
-#include "history/history_message.h" // NewMessageFlags.
+#include "history/history_item.h"
+#include "history/history_item_helpers.h" // NewMessageFlags.
 #include "chat_helpers/message_field.h" // ConvertTextTagsToEntities.
 #include "chat_helpers/stickers_dice_pack.h" // DicePacks::kDiceString.
 #include "ui/text/text_entity.h" // TextWithEntities.
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 4231e5c9b..8ac060deb 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -67,8 +67,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "boxes/add_contact_box.h"
 #include "mtproto/mtproto_config.h"
 #include "history/history.h"
-#include "history/history_message.h"
+#include "history/history_item.h"
 #include "history/history_item_components.h"
+#include "history/history_item_helpers.h"
 #include "main/main_session.h"
 #include "main/main_session_settings.h"
 #include "main/main_account.h"
diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp
index 7e0be1f36..1a2d3fe24 100644
--- a/Telegram/SourceFiles/boxes/background_preview_box.cpp
+++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp
@@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/painter.h"
 #include "ui/ui_utility.h"
 #include "history/history.h"
-#include "history/history_message.h"
+#include "history/history_item.h"
 #include "history/view/history_view_message.h"
 #include "main/main_session.h"
 #include "apiwrap.h"
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp
index f9a24c72b..04c63816f 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp
@@ -32,7 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/painter.h"
 #include "boxes/share_box.h"
 #include "history/view/history_view_group_call_bar.h" // GenerateUserpics...
-#include "history/history_message.h" // GetErrorTextForSending.
+#include "history/history_item_helpers.h" // GetErrorTextForSending.
 #include "history/history.h"
 #include "ui/boxes/confirm_box.h"
 #include "boxes/peer_list_box.h"
diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp
index ce617370b..19b42bb21 100644
--- a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp
+++ b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp
@@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_session.h"
 #include "history/admin_log/history_admin_log_item.h"
 #include "history/history.h"
-#include "history/history_message.h"
+#include "history/history_item.h"
 #include "history/view/history_view_element.h"
 #include "history/view/reactions/history_view_reactions_strip.h"
 #include "lang/lang_keys.h"
diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp
index e8c4b4987..3e8f33765 100644
--- a/Telegram/SourceFiles/boxes/share_box.cpp
+++ b/Telegram/SourceFiles/boxes/share_box.cpp
@@ -30,7 +30,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "menu/menu_check_item.h"
 #include "menu/menu_send.h"
 #include "history/history.h"
-#include "history/history_message.h"
+#include "history/history_item.h"
+#include "history/history_item_helpers.h"
 #include "history/view/history_view_element.h" // HistoryView::Context.
 #include "history/view/history_view_context_menu.h" // CopyPostLink.
 #include "history/view/history_view_schedule_box.h"
diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp
index e5af11c75..5e26204c4 100644
--- a/Telegram/SourceFiles/calls/calls_box_controller.cpp
+++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp
@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "calls/calls_instance.h"
 #include "history/history.h"
 #include "history/history_item.h"
+#include "history/history_item_helpers.h"
 #include "mainwidget.h"
 #include "window/window_session_controller.h"
 #include "main/main_session.h"
diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp
index 5fa269328..210427142 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp
@@ -24,7 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "lang/lang_keys.h"
 #include "boxes/share_box.h"
 #include "history/view/history_view_schedule_box.h"
-#include "history/history_message.h" // GetErrorTextForSending.
+#include "history/history_item_helpers.h" // GetErrorTextForSending.
 #include "history/history.h"
 #include "data/data_histories.h"
 #include "data/data_session.h"
diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp
index 3891ab82b..5d5a3b7f3 100644
--- a/Telegram/SourceFiles/chat_helpers/message_field.cpp
+++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp
@@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history_widget.h"
 #include "history/history.h" // History::session
 #include "history/history_item.h" // HistoryItem::originalText
-#include "history/history_message.h" // DropCustomEmoji
+#include "history/history_item_helpers.h" // DropCustomEmoji
 #include "base/qthelp_regex.h"
 #include "base/qthelp_url.h"
 #include "base/event_filter.h"
diff --git a/Telegram/SourceFiles/data/data_download_manager.cpp b/Telegram/SourceFiles/data/data_download_manager.cpp
index e5861c470..c121fa968 100644
--- a/Telegram/SourceFiles/data/data_download_manager.cpp
+++ b/Telegram/SourceFiles/data/data_download_manager.cpp
@@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "storage/storage_account.h"
 #include "history/history.h"
 #include "history/history_item.h"
-#include "history/history_message.h"
+#include "history/history_item_helpers.h"
 #include "core/application.h"
 #include "core/mime_type.h"
 #include "ui/controls/download_bar.h"
diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp
index 988e048f7..578f89ea7 100644
--- a/Telegram/SourceFiles/data/data_media_types.cpp
+++ b/Telegram/SourceFiles/data/data_media_types.cpp
@@ -8,8 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_media_types.h"
 
 #include "history/history.h"
-#include "history/history_item.h"
-#include "history/history_message.h" // CreateMedia.
+#include "history/history_item.h" // CreateMedia.
 #include "history/history_location_manager.h"
 #include "history/view/history_view_element.h"
 #include "history/view/history_view_item_preview.h"
@@ -235,7 +234,7 @@ template <typename MediaType>
 
 bool UpdateExtendedMedia(
 		Invoice &invoice,
-		not_null<HistoryMessage*> item,
+		not_null<HistoryItem*> item,
 		const MTPMessageExtendedMedia &media) {
 	return media.match([&](const MTPDmessageExtendedMediaPreview &data) {
 		if (invoice.extendedMedia) {
@@ -269,7 +268,7 @@ bool UpdateExtendedMedia(
 		}
 		return changed;
 	}, [&](const MTPDmessageExtendedMedia &data) {
-		invoice.extendedMedia = HistoryMessage::CreateMedia(
+		invoice.extendedMedia = HistoryItem::CreateMedia(
 			item,
 			data.vmedia());
 		return true;
@@ -291,7 +290,7 @@ TextForMimeData WithCaptionClipboardText(
 }
 
 Invoice ComputeInvoiceData(
-		not_null<HistoryMessage*> item,
+		not_null<HistoryItem*> item,
 		const MTPDmessageMediaInvoice &data) {
 	auto description = qs(data.vdescription());
 	auto result = Invoice{
@@ -1639,7 +1638,7 @@ bool MediaInvoice::updateSentMedia(const MTPMessageMedia &media) {
 }
 
 bool MediaInvoice::updateExtendedMedia(
-		not_null<HistoryMessage*> item,
+		not_null<HistoryItem*> item,
 		const MTPMessageExtendedMedia &media) {
 	Expects(item == parent());
 
diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h
index b9c240c3d..afcc725c3 100644
--- a/Telegram/SourceFiles/data/data_media_types.h
+++ b/Telegram/SourceFiles/data/data_media_types.h
@@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 class Image;
 class History;
 class HistoryItem;
-class HistoryMessage;
 
 namespace base {
 template <typename Enum>
@@ -140,7 +139,7 @@ public:
 	virtual bool updateInlineResultMedia(const MTPMessageMedia &media) = 0;
 	virtual bool updateSentMedia(const MTPMessageMedia &media) = 0;
 	virtual bool updateExtendedMedia(
-			not_null<HistoryMessage*> item,
+			not_null<HistoryItem*> item,
 			const MTPMessageExtendedMedia &media) {
 		return false;
 	}
@@ -428,7 +427,7 @@ public:
 	bool updateInlineResultMedia(const MTPMessageMedia &media) override;
 	bool updateSentMedia(const MTPMessageMedia &media) override;
 	bool updateExtendedMedia(
-		not_null<HistoryMessage*> item,
+		not_null<HistoryItem*> item,
 		const MTPMessageExtendedMedia &media) override;
 	std::unique_ptr<HistoryView::Media> createView(
 		not_null<HistoryView::Element*> message,
@@ -541,7 +540,7 @@ private:
 	TextForMimeData &&caption);
 
 [[nodiscard]] Invoice ComputeInvoiceData(
-	not_null<HistoryMessage*> item,
+	not_null<HistoryItem*> item,
 	const MTPDmessageMediaInvoice &data);
 
 [[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call);
diff --git a/Telegram/SourceFiles/data/data_replies_list.cpp b/Telegram/SourceFiles/data/data_replies_list.cpp
index dedcb57f2..733a0ae05 100644
--- a/Telegram/SourceFiles/data/data_replies_list.cpp
+++ b/Telegram/SourceFiles/data/data_replies_list.cpp
@@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "history/history.h"
 #include "history/history_item.h"
-#include "history/history_service.h"
+#include "history/history_item_helpers.h"
 #include "main/main_session.h"
 #include "data/data_histories.h"
 #include "data/data_session.h"
@@ -30,15 +30,15 @@ constexpr auto kMessagesPerPage = 50;
 constexpr auto kReadRequestTimeout = 3 * crl::time(1000);
 constexpr auto kMaxMessagesToDeleteMyTopic = 10;
 
-[[nodiscard]] HistoryService *GenerateDivider(
+[[nodiscard]] HistoryItem *GenerateDivider(
 		not_null<History*> history,
 		TimeId date,
 		const QString &text) {
-	return history->makeServiceMessage(
+	return history->makeMessage(
 		history->nextNonHistoryEntryId(),
 		MessageFlag::FakeHistoryItem,
 		date,
-		HistoryService::PreparedText{ { .text = text } });
+		PreparedServiceText{ { .text = text } });
 }
 
 [[nodiscard]] bool IsCreating(not_null<History*> history, MsgId rootId) {
@@ -345,7 +345,7 @@ void RepliesList::injectRootDivider(
 			text());
 	} else if (_dividerWithComments != withComments) {
 		_dividerWithComments = withComments;
-		_divider->setServiceText(HistoryService::PreparedText{ { text() } });
+		_divider->updateServiceText(PreparedServiceText{ { text() } });
 	}
 	slice->ids.push_back(_divider->fullId());
 }
diff --git a/Telegram/SourceFiles/data/data_replies_list.h b/Telegram/SourceFiles/data/data_replies_list.h
index e38d9c80a..a33dee56b 100644
--- a/Telegram/SourceFiles/data/data_replies_list.h
+++ b/Telegram/SourceFiles/data/data_replies_list.h
@@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "base/timer.h"
 
 class History;
-class HistoryService;
 
 namespace Data {
 
@@ -123,7 +122,7 @@ private:
 	rpl::variable<std::optional<int>> _unreadCount;
 	MsgId _inboxReadTillId = 0;
 	MsgId _outboxReadTillId = 0;
-	HistoryService *_divider = nullptr;
+	HistoryItem *_divider = nullptr;
 	bool _dividerWithComments = false;
 	int _beforeId = 0;
 	int _afterId = 0;
diff --git a/Telegram/SourceFiles/data/data_scheduled_messages.cpp b/Telegram/SourceFiles/data/data_scheduled_messages.cpp
index c6231a695..e33cad8e0 100644
--- a/Telegram/SourceFiles/data/data_scheduled_messages.cpp
+++ b/Telegram/SourceFiles/data/data_scheduled_messages.cpp
@@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "main/main_session.h"
 #include "history/history.h"
 #include "history/history_item_components.h"
-#include "history/history_message.h"
+#include "history/history_item_helpers.h"
 #include "apiwrap.h"
 
 namespace Data {
diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h
index 6e7f5f644..e82b5218b 100644
--- a/Telegram/SourceFiles/data/data_session.h
+++ b/Telegram/SourceFiles/data/data_session.h
@@ -19,8 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 class Image;
 class HistoryItem;
-class HistoryMessage;
-class HistoryService;
 struct WebPageCollage;
 enum class WebPageType;
 enum class NewMessageType;
diff --git a/Telegram/SourceFiles/data/data_sponsored_messages.cpp b/Telegram/SourceFiles/data/data_sponsored_messages.cpp
index 011ad072d..f6f0a7b4b 100644
--- a/Telegram/SourceFiles/data/data_sponsored_messages.cpp
+++ b/Telegram/SourceFiles/data/data_sponsored_messages.cpp
@@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_session.h"
 #include "data/data_user.h"
 #include "history/history.h"
-#include "history/history_message.h"
+#include "history/history_item.h"
 #include "history/view/history_view_element.h"
 #include "main/main_session.h"
 #include "ui/image/image_location_factory.h"
diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp
index d318efb0b..8bb625bf7 100644
--- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp
+++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp
@@ -29,8 +29,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/history_view_send_action.h"
 #include "history/view/history_view_item_preview.h"
 #include "history/history_unread_things.h"
-#include "history/history_item_components.h"
 #include "history/history_item.h"
+#include "history/history_item_components.h"
+#include "history/history_item_helpers.h"
 #include "history/history.h"
 #include "base/unixtime.h"
 #include "data/data_channel.h"
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
index ea4f2a0bb..2a171099e 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
@@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history.h"
 #include "history/view/media/history_view_media.h"
 #include "history/view/media/history_view_web_page.h"
-#include "history/history_message.h"
+#include "history/history_item.h"
 #include "history/history_item_components.h"
 #include "history/history_item_text.h"
 #include "history/admin_log/history_admin_log_section.h"
@@ -582,18 +582,6 @@ HistoryView::Context InnerWidget::elementContext() {
 	return HistoryView::Context::AdminLog;
 }
 
-std::unique_ptr<HistoryView::Element> InnerWidget::elementCreate(
-		not_null<HistoryMessage*> message,
-		Element *replacing) {
-	return std::make_unique<HistoryView::Message>(this, message, replacing);
-}
-
-std::unique_ptr<HistoryView::Element> InnerWidget::elementCreate(
-		not_null<HistoryService*> message,
-		Element *replacing) {
-	return std::make_unique<HistoryView::Service>(this, message, replacing);
-}
-
 bool InnerWidget::elementUnderCursor(
 		not_null<const HistoryView::Element*> view) {
 	return (Element::Hovered() == view);
@@ -1294,7 +1282,6 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
 
 		_antiSpamValidator.addAction(_menu, itemId);
 
-		auto msg = dynamic_cast<HistoryMessage*>(item);
 		if (isUponSelected > 0) {
 			_menu->addAction(
 				tr::lng_context_copy_selected(tr::now),
@@ -1311,7 +1298,7 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
 						}), &st::menuIconDownload);
 					}
 				}
-				if (msg
+				if (!item->isService()
 					&& !link
 					&& (view->hasVisibleText()
 						|| mediaHasTextForCopy
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
index cf92d2ceb..684fba84f 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
@@ -91,12 +91,6 @@ public:
 
 	// HistoryView::ElementDelegate interface.
 	HistoryView::Context elementContext() override;
-	std::unique_ptr<HistoryView::Element> elementCreate(
-		not_null<HistoryMessage*> message,
-		HistoryView::Element *replacing = nullptr) override;
-	std::unique_ptr<HistoryView::Element> elementCreate(
-		not_null<HistoryService*> message,
-		HistoryView::Element *replacing = nullptr) override;
 	bool elementUnderCursor(
 		not_null<const HistoryView::Element*> view) override;
 	[[nodiscard]] float64 elementHighlightOpacity(
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
index fc7c56071..cdefc990d 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
@@ -9,10 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "history/admin_log/history_admin_log_inner.h"
 #include "history/view/history_view_element.h"
-#include "history/history_location_manager.h"
-#include "history/history_service.h"
-#include "history/history_message.h"
 #include "history/history.h"
+#include "history/history_item.h"
+#include "history/history_item_helpers.h"
+#include "history/history_location_manager.h"
 #include "api/api_chat_participants.h"
 #include "api/api_text_entities.h"
 #include "data/data_channel.h"
@@ -773,10 +773,10 @@ void GenerateItems(
 			const TextWithEntities &text,
 			MsgId realId = MsgId(),
 			PhotoData *photo = nullptr) {
-		auto message = HistoryService::PreparedText{ text };
+		auto message = PreparedServiceText{ text };
 		message.links.push_back(fromLink);
 		addPart(
-			history->makeServiceMessage(
+			history->makeMessage(
 				history->nextNonHistoryEntryId(),
 				MessageFlag::AdminLogEntry,
 				date,
@@ -1117,10 +1117,10 @@ void GenerateItems(
 						Ui::LayerOption::CloseOther);
 				}
 			});
-			auto message = HistoryService::PreparedText { text };
+			auto message = PreparedServiceText{ text };
 			message.links.push_back(fromLink);
 			message.links.push_back(setLink);
-			addPart(history->makeServiceMessage(
+			addPart(history->makeMessage(
 				history->nextNonHistoryEntryId(),
 				MessageFlag::AdminLogEntry,
 				date,
@@ -1198,10 +1198,10 @@ void GenerateItems(
 					window->showPeerHistory(now);
 				}
 			});
-			auto message = HistoryService::PreparedText{ text };
+			auto message = PreparedServiceText{ text };
 			message.links.push_back(fromLink);
 			message.links.push_back(chatLink);
-			addPart(history->makeServiceMessage(
+			addPart(history->makeMessage(
 				history->nextNonHistoryEntryId(),
 				MessageFlag::AdminLogEntry,
 				date,
@@ -1294,10 +1294,10 @@ void GenerateItems(
 	const auto addServiceMessageWithLink = [&](
 			const TextWithEntities &text,
 			const ClickHandlerPtr &link) {
-		auto message = HistoryService::PreparedText{ text };
+		auto message = PreparedServiceText{ text };
 		message.links.push_back(fromLink);
 		message.links.push_back(link);
-		addPart(history->makeServiceMessage(
+		addPart(history->makeMessage(
 			history->nextNonHistoryEntryId(),
 			MessageFlag::AdminLogEntry,
 			date,
@@ -1363,7 +1363,7 @@ void GenerateItems(
 			const TextWithEntities &text,
 			const MTPExportedChatInvite &data,
 			ClickHandlerPtr additional = nullptr) {
-		auto message = HistoryService::PreparedText{ text };
+		auto message = PreparedServiceText{ text };
 		message.links.push_back(fromLink);
 		if (!ExtractInviteLink(data).endsWith(Ui::kQEllipsis)) {
 			message.links.push_back(std::make_shared<UrlClickHandler>(
@@ -1372,7 +1372,7 @@ void GenerateItems(
 		if (additional) {
 			message.links.push_back(std::move(additional));
 		}
-		addPart(history->makeServiceMessage(
+		addPart(history->makeMessage(
 			history->nextNonHistoryEntryId(),
 			MessageFlag::AdminLogEntry,
 			date,
diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp
index 6e9d5e621..df1bcbf87 100644
--- a/Telegram/SourceFiles/history/history.cpp
+++ b/Telegram/SourceFiles/history/history.cpp
@@ -9,9 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "history/view/history_view_element.h"
 #include "history/view/history_view_item_preview.h"
-#include "history/history_message.h"
-#include "history/history_service.h"
+#include "history/history_item.h"
 #include "history/history_item_components.h"
+#include "history/history_item_helpers.h"
 #include "history/history_inner_widget.h"
 #include "history/history_unread_things.h"
 #include "dialogs/dialogs_indexed_list.h"
@@ -419,7 +419,9 @@ not_null<HistoryItem*> History::createItem(
 		}
 		return result;
 	}
-	return HistoryItem::Create(this, id, message, localFlags);
+	return message.match([&](const auto &data) {
+		return makeMessage(id, data, localFlags);
+	});
 }
 
 std::vector<not_null<HistoryItem*>> History::createItems(
@@ -3080,7 +3082,7 @@ MsgRange History::rangeForDifferenceRequest() const {
 	return MsgRange();
 }
 
-HistoryService *History::insertJoinedMessage() {
+HistoryItem *History::insertJoinedMessage() {
 	const auto channel = peer->asChannel();
 	if (!channel
 		|| _joinedMessage
diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h
index e3c408820..3d36ad806 100644
--- a/Telegram/SourceFiles/history/history.h
+++ b/Telegram/SourceFiles/history/history.h
@@ -19,8 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 class History;
 class HistoryBlock;
 class HistoryItem;
-class HistoryMessage;
-class HistoryService;
 struct HistoryMessageMarkupData;
 class HistoryMainElementDelegateMixin;
 
@@ -126,22 +124,14 @@ public:
 	void applyGroupAdminChanges(const base::flat_set<UserId> &changes);
 
 	template <typename ...Args>
-	not_null<HistoryMessage*> makeMessage(Args &&...args) {
-		return static_cast<HistoryMessage*>(
+	not_null<HistoryItem*> makeMessage(Args &&...args) {
+		return static_cast<HistoryItem*>(
 			insertItem(
-				std::make_unique<HistoryMessage>(
+				std::make_unique<HistoryItem>(
 					this,
 					std::forward<Args>(args)...)).get());
 	}
 
-	template <typename ...Args>
-	not_null<HistoryService*> makeServiceMessage(Args &&...args) {
-		return static_cast<HistoryService*>(
-			insertItem(
-				std::make_unique<HistoryService>(
-					this,
-					std::forward<Args>(args)...)).get());
-	}
 	void destroyMessage(not_null<HistoryItem*> item);
 	void destroyMessagesByDates(TimeId minDate, TimeId maxDate);
 	void destroyMessagesByTopic(MsgId topicRootId);
@@ -577,7 +567,7 @@ private:
 
 	void createLocalDraftFromCloud(MsgId topicRootId);
 
-	HistoryService *insertJoinedMessage();
+	HistoryItem *insertJoinedMessage();
 	void insertMessageToBlocks(not_null<HistoryItem*> item);
 
 	[[nodiscard]] Dialogs::BadgesState computeBadgesState() const;
@@ -598,7 +588,7 @@ private:
 	int _height = 0;
 	Element *_unreadBarView = nullptr;
 	Element *_firstUnreadView = nullptr;
-	HistoryService *_joinedMessage = nullptr;
+	HistoryItem *_joinedMessage = nullptr;
 	bool _loadedAtTop = false;
 	bool _loadedAtBottom = true;
 
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index cc36aee31..e8ebf4084 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -11,7 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "core/crash_reports.h"
 #include "core/click_handler_types.h"
 #include "history/history.h"
-#include "history/history_message.h"
+#include "history/history_item.h"
+#include "history/history_item_helpers.h"
 #include "history/view/media/history_view_media.h"
 #include "history/view/media/history_view_sticker.h"
 #include "history/view/media/history_view_web_page.h"
@@ -145,22 +146,6 @@ public:
 	HistoryView::Context elementContext() override {
 		return HistoryView::Context::History;
 	}
-	std::unique_ptr<Element> elementCreate(
-			not_null<HistoryMessage*> message,
-			Element *replacing = nullptr) override {
-		return std::make_unique<HistoryView::Message>(
-			this,
-			message,
-			replacing);
-	}
-	std::unique_ptr<HistoryView::Element> elementCreate(
-			not_null<HistoryService*> message,
-			Element *replacing = nullptr) override {
-		return std::make_unique<HistoryView::Service>(
-			this,
-			message,
-			replacing);
-	}
 	bool elementUnderCursor(
 			not_null<const Element*> view) override {
 		return (Element::Moused() == view);
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index cb694ccca..a4b917b87 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -9,18 +9,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "lang/lang_keys.h"
 #include "mainwidget.h"
+#include "calls/calls_instance.h" // Core::App().calls().joinGroupCall.
 #include "history/view/history_view_element.h"
 #include "history/view/history_view_item_preview.h"
+#include "history/view/history_view_message.h"
 #include "history/view/history_view_service_message.h"
 #include "history/view/media/history_view_media_grouped.h"
+#include "history/history_item.h"
 #include "history/history_item_components.h"
-#include "history/history_service.h"
-#include "history/history_message.h"
+#include "history/history_item_helpers.h"
 #include "history/history_unread_things.h"
 #include "history/history.h"
 #include "mtproto/mtproto_config.h"
 #include "media/clip/media_clip_reader.h"
 #include "ui/effects/ripple_animation.h"
+#include "ui/text/format_values.h"
 #include "ui/text/text_isolated_emoji.h"
 #include "ui/text/text_options.h"
 #include "ui/text/text_utilities.h"
@@ -30,6 +33,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "main/main_account.h"
 #include "main/main_domain.h"
 #include "main/main_session.h"
+#include "main/main_session_settings.h"
+#include "menu/menu_ttl_validator.h"
 #include "apiwrap.h"
 #include "media/audio/media_audio.h"
 #include "core/application.h"
@@ -37,9 +42,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "window/window_controller.h"
 #include "window/window_session_controller.h"
 #include "core/crash_reports.h"
+#include "core/click_handler_types.h"
 #include "base/unixtime.h"
+#include "base/timer_rpl.h"
 #include "api/api_text_entities.h"
+#include "api/api_updates.h"
 #include "dialogs/ui/dialogs_message_view.h"
+#include "data/notify/data_notify_settings.h"
 #include "data/data_scheduled_messages.h" // kScheduledUntilOnlineTimestamp
 #include "data/data_changes.h"
 #include "data/data_session.h"
@@ -51,194 +60,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_forum_topic.h"
 #include "data/data_channel.h"
 #include "data/data_chat.h"
+#include "data/data_game.h"
 #include "data/data_user.h"
+#include "data/data_group_call.h" // Data::GroupCall::id().
 #include "data/data_sponsored_messages.h"
+#include "data/data_web_page.h"
+#include "chat_helpers/stickers_gift_box_pack.h"
+#include "payments/payments_checkout_process.h" // CheckoutProcess::Start.
 #include "styles/style_dialogs.h"
 #include "styles/style_chat.h"
 
 namespace {
 
 constexpr auto kNotificationTextLimit = 255;
-constexpr auto kMaxUnreadReactions = 5; // Now 3, but just in case.
+constexpr auto kPinnedMessageTextLimit = 16;
 
 using ItemPreview = HistoryView::ItemPreview;
 
-enum class MediaCheckResult {
-	Good,
-	Unsupported,
-	Empty,
-	HasTimeToLive,
-};
-
-not_null<HistoryItem*> CreateUnsupportedMessage(
-		not_null<History*> history,
-		MsgId msgId,
-		MessageFlags flags,
-		MsgId replyTo,
-		UserId viaBotId,
-		TimeId date,
-		PeerId from) {
-	const auto siteLink = u"https://desktop.telegram.org"_q;
-	auto text = TextWithEntities{
-		tr::lng_message_unsupported(tr::now, lt_link, siteLink)
-	};
-	TextUtilities::ParseEntities(text, Ui::ItemTextNoMonoOptions().flags);
-	text.entities.push_front(
-		EntityInText(EntityType::Italic, 0, text.text.size()));
-	flags &= ~MessageFlag::HasPostAuthor;
-	flags |= MessageFlag::Legacy;
-	const auto groupedId = uint64();
-	return history->makeMessage(
-		msgId,
-		flags,
-		replyTo,
-		viaBotId,
-		date,
-		from,
-		QString(),
-		text,
-		MTP_messageMediaEmpty(),
-		HistoryMessageMarkupData(),
-		groupedId);
-}
-
-MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
-	using Result = MediaCheckResult;
-	return media.match([](const MTPDmessageMediaEmpty &) {
-		return Result::Good;
-	}, [](const MTPDmessageMediaContact &) {
-		return Result::Good;
-	}, [](const MTPDmessageMediaGeo &data) {
-		return data.vgeo().match([](const MTPDgeoPoint &) {
-			return Result::Good;
-		}, [](const MTPDgeoPointEmpty &) {
-			return Result::Empty;
-		});
-	}, [](const MTPDmessageMediaVenue &data) {
-		return data.vgeo().match([](const MTPDgeoPoint &) {
-			return Result::Good;
-		}, [](const MTPDgeoPointEmpty &) {
-			return Result::Empty;
-		});
-	}, [](const MTPDmessageMediaGeoLive &data) {
-		return data.vgeo().match([](const MTPDgeoPoint &) {
-			return Result::Good;
-		}, [](const MTPDgeoPointEmpty &) {
-			return Result::Empty;
-		});
-	}, [](const MTPDmessageMediaPhoto &data) {
-		const auto photo = data.vphoto();
-		if (data.vttl_seconds()) {
-			return Result::HasTimeToLive;
-		} else if (!photo) {
-			return Result::Empty;
-		}
-		return photo->match([](const MTPDphoto &) {
-			return Result::Good;
-		}, [](const MTPDphotoEmpty &) {
-			return Result::Empty;
-		});
-	}, [](const MTPDmessageMediaDocument &data) {
-		const auto document = data.vdocument();
-		if (data.vttl_seconds()) {
-			return Result::HasTimeToLive;
-		} else if (!document) {
-			return Result::Empty;
-		}
-		return document->match([](const MTPDdocument &) {
-			return Result::Good;
-		}, [](const MTPDdocumentEmpty &) {
-			return Result::Empty;
-		});
-	}, [](const MTPDmessageMediaWebPage &data) {
-		return data.vwebpage().match([](const MTPDwebPage &) {
-			return Result::Good;
-		}, [](const MTPDwebPageEmpty &) {
-			return Result::Good;
-		}, [](const MTPDwebPagePending &) {
-			return Result::Good;
-		}, [](const MTPDwebPageNotModified &) {
-			return Result::Unsupported;
-		});
-	}, [](const MTPDmessageMediaGame &data) {
-		return data.vgame().match([](const MTPDgame &) {
-			return Result::Good;
-		});
-	}, [](const MTPDmessageMediaInvoice &) {
-		return Result::Good;
-	}, [](const MTPDmessageMediaPoll &) {
-		return Result::Good;
-	}, [](const MTPDmessageMediaDice &) {
-		return Result::Good;
-	}, [](const MTPDmessageMediaUnsupported &) {
-		return Result::Unsupported;
-	});
-}
-
-[[nodiscard]] MessageFlags FinalizeMessageFlags(MessageFlags flags) {
-	if (!(flags & MessageFlag::FakeHistoryItem)
-		&& !(flags & MessageFlag::IsOrWasScheduled)
-		&& !(flags & MessageFlag::AdminLogEntry)) {
-		flags |= MessageFlag::HistoryEntry;
-	}
-	return flags;
-}
-
-using OnStackUsers = std::array<UserData*, kMaxUnreadReactions>;
-[[nodiscard]] OnStackUsers LookupRecentUnreadReactedUsers(
-		not_null<HistoryItem*> item) {
-	auto result = OnStackUsers();
-	auto index = 0;
-	for (const auto &[emoji, reactions] : item->recentReactions()) {
-		for (const auto &reaction : reactions) {
-			if (!reaction.unread) {
-				continue;
-			}
-			if (const auto user = reaction.peer->asUser()) {
-				result[index++] = user;
-				if (index == result.size()) {
-					return result;
-				}
-			}
-		}
-	}
-	return result;
-}
-
-void CheckReactionNotificationSchedule(
-		not_null<HistoryItem*> item,
-		const OnStackUsers &wasUsers) {
-	// Call to addToUnreadThings may have read the reaction already.
-	if (!item->hasUnreadReaction()) {
-		return;
-	}
-	for (const auto &[emoji, reactions] : item->recentReactions()) {
-		for (const auto &reaction : reactions) {
-			if (!reaction.unread) {
-				continue;
-			}
-			const auto user = reaction.peer->asUser();
-			if (!user
-				|| !user->isContact()
-				|| ranges::contains(wasUsers, user)) {
-				continue;
-			}
-			using Status = PeerData::BlockStatus;
-			if (user->blockStatus() == Status::Unknown) {
-				user->updateFull();
-			}
-			const auto notification = Data::ItemNotification{
-				.item = item,
-				.reactionSender = user,
-				.type = Data::ItemNotificationType::Reaction,
-			};
-			item->notificationThread()->pushNotification(notification);
-			Core::App().notifications().schedule(notification);
-			return;
-		}
-	}
-}
-
 } // namespace
 
 void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) {
@@ -247,6 +85,523 @@ void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) {
 	}
 }
 
+struct HistoryItem::CreateConfig {
+	PeerId replyToPeer = 0;
+	MsgId replyTo = 0;
+	MsgId replyToTop = 0;
+	bool replyIsTopicPost = false;
+	UserId viaBotId = 0;
+	int viewsCount = -1;
+	QString author;
+	PeerId senderOriginal = 0;
+	QString senderNameOriginal;
+	QString forwardPsaType;
+	MsgId originalId = 0;
+	PeerId savedFromPeer = 0;
+	MsgId savedFromMsgId = 0;
+	QString authorOriginal;
+	TimeId originalDate = 0;
+	TimeId editDate = 0;
+	HistoryMessageMarkupData markup;
+	HistoryMessageRepliesData replies;
+	bool imported = false;
+
+	// For messages created from existing messages (forwarded).
+	const HistoryMessageReplyMarkup *inlineMarkup = nullptr;
+};
+
+void HistoryItem::FillForwardedInfo(
+		CreateConfig &config,
+		const MTPDmessageFwdHeader &data) {
+	if (const auto fromId = data.vfrom_id()) {
+		config.senderOriginal = peerFromMTP(*fromId);
+	}
+	config.originalDate = data.vdate().v;
+	config.senderNameOriginal = qs(data.vfrom_name().value_or_empty());
+	config.forwardPsaType = qs(data.vpsa_type().value_or_empty());
+	config.originalId = data.vchannel_post().value_or_empty();
+	config.authorOriginal = qs(data.vpost_author().value_or_empty());
+	const auto savedFromPeer = data.vsaved_from_peer();
+	const auto savedFromMsgId = data.vsaved_from_msg_id();
+	if (savedFromPeer && savedFromMsgId) {
+		config.savedFromPeer = peerFromMTP(*savedFromPeer);
+		config.savedFromMsgId = savedFromMsgId->v;
+	}
+	config.imported = data.is_imported();
+}
+
+std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
+		not_null<HistoryItem*> item,
+		const MTPMessageMedia &media) {
+	using Result = std::unique_ptr<Data::Media>;
+	return media.match([&](const MTPDmessageMediaContact &media) -> Result {
+		return std::make_unique<Data::MediaContact>(
+			item,
+			media.vuser_id().v,
+			qs(media.vfirst_name()),
+			qs(media.vlast_name()),
+			qs(media.vphone_number()));
+	}, [&](const MTPDmessageMediaGeo &media) -> Result {
+		return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result {
+			return std::make_unique<Data::MediaLocation>(
+				item,
+				Data::LocationPoint(point));
+		}, [](const MTPDgeoPointEmpty &) -> Result {
+			return nullptr;
+		});
+	}, [&](const MTPDmessageMediaGeoLive &media) -> Result {
+		return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result {
+			return std::make_unique<Data::MediaLocation>(
+				item,
+				Data::LocationPoint(point));
+		}, [](const MTPDgeoPointEmpty &) -> Result {
+			return nullptr;
+		});
+	}, [&](const MTPDmessageMediaVenue &media) -> Result {
+		return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result {
+			return std::make_unique<Data::MediaLocation>(
+				item,
+				Data::LocationPoint(point),
+				qs(media.vtitle()),
+				qs(media.vaddress()));
+		}, [](const MTPDgeoPointEmpty &data) -> Result {
+			return nullptr;
+		});
+	}, [&](const MTPDmessageMediaPhoto &media) -> Result {
+		const auto photo = media.vphoto();
+		if (media.vttl_seconds()) {
+			LOG(("App Error: "
+				"Unexpected MTPMessageMediaPhoto "
+				"with ttl_seconds in CreateMedia."));
+			return nullptr;
+		} else if (!photo) {
+			LOG(("API Error: "
+				"Got MTPMessageMediaPhoto "
+				"without photo and without ttl_seconds."));
+			return nullptr;
+		}
+		return photo->match([&](const MTPDphoto &photo) -> Result {
+			return std::make_unique<Data::MediaPhoto>(
+				item,
+				item->history()->owner().processPhoto(photo));
+		}, [](const MTPDphotoEmpty &) -> Result {
+			return nullptr;
+		});
+	}, [&](const MTPDmessageMediaDocument &media) -> Result {
+		const auto document = media.vdocument();
+		if (media.vttl_seconds()) {
+			LOG(("App Error: "
+				"Unexpected MTPMessageMediaDocument "
+				"with ttl_seconds in CreateMedia."));
+			return nullptr;
+		} else if (!document) {
+			LOG(("API Error: "
+				"Got MTPMessageMediaDocument "
+				"without document and without ttl_seconds."));
+			return nullptr;
+		}
+		return document->match([&](const MTPDdocument &document) -> Result {
+			return std::make_unique<Data::MediaFile>(
+				item,
+				item->history()->owner().processDocument(document),
+				media.is_nopremium());
+		}, [](const MTPDdocumentEmpty &) -> Result {
+			return nullptr;
+		});
+	}, [&](const MTPDmessageMediaWebPage &media) {
+		return media.vwebpage().match([](const MTPDwebPageEmpty &) -> Result {
+			return nullptr;
+		}, [&](const MTPDwebPagePending &webpage) -> Result {
+			return std::make_unique<Data::MediaWebPage>(
+				item,
+				item->history()->owner().processWebpage(webpage));
+		}, [&](const MTPDwebPage &webpage) -> Result {
+			return std::make_unique<Data::MediaWebPage>(
+				item,
+				item->history()->owner().processWebpage(webpage));
+		}, [](const MTPDwebPageNotModified &) -> Result {
+			LOG(("API Error: "
+				"webPageNotModified is unexpected in message media."));
+			return nullptr;
+		});
+	}, [&](const MTPDmessageMediaGame &media) -> Result {
+		return media.vgame().match([&](const MTPDgame &game) {
+			return std::make_unique<Data::MediaGame>(
+				item,
+				item->history()->owner().processGame(game));
+		});
+	}, [&](const MTPDmessageMediaInvoice &media) -> Result {
+		return std::make_unique<Data::MediaInvoice>(
+			item,
+			Data::ComputeInvoiceData(item, media));
+	}, [&](const MTPDmessageMediaPoll &media) -> Result {
+		return std::make_unique<Data::MediaPoll>(
+			item,
+			item->history()->owner().processPoll(media));
+	}, [&](const MTPDmessageMediaDice &media) -> Result {
+		return std::make_unique<Data::MediaDice>(
+			item,
+			qs(media.vemoticon()),
+			media.vvalue().v);
+	}, [](const MTPDmessageMediaEmpty &) -> Result {
+		return nullptr;
+	}, [](const MTPDmessageMediaUnsupported &) -> Result {
+		return nullptr;
+	});
+}
+
+HistoryItem::HistoryItem(
+	not_null<History*> history,
+	MsgId id,
+	const MTPDmessage &data,
+	MessageFlags localFlags)
+: HistoryItem(
+		history,
+		id,
+		FlagsFromMTP(id, data.vflags().v, localFlags),
+		data.vdate().v,
+		data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) {
+	const auto media = data.vmedia();
+	const auto checked = media
+		? CheckMessageMedia(*media)
+		: MediaCheckResult::Good;
+	if (checked == MediaCheckResult::Unsupported) {
+		_flags &= ~MessageFlag::HasPostAuthor;
+		_flags |= MessageFlag::Legacy;
+		createComponents(data);
+		setText(UnsupportedMessageText());
+	} else if (checked == MediaCheckResult::Empty) {
+		AddComponents(HistoryServiceData::Bit());
+		setServiceText({
+			tr::lng_message_empty(tr::now, Ui::Text::WithEntities)
+		});
+	} else if (checked == MediaCheckResult::HasTimeToLive) {
+		createServiceFromMtp(data);
+		applyTTL(data);
+	} else {
+		createComponents(data);
+		if (const auto media = data.vmedia()) {
+			setMedia(*media);
+		}
+		auto textWithEntities = TextWithEntities{
+			qs(data.vmessage()),
+			Api::EntitiesFromMTP(
+				&history->session(),
+				data.ventities().value_or_empty())
+		};
+		setText(_media ? textWithEntities : EnsureNonEmpty(textWithEntities));
+		if (const auto groupedId = data.vgrouped_id()) {
+			setGroupId(
+				MessageGroupId::FromRaw(history->peer->id, groupedId->v));
+		}
+		setReactions(data.vreactions());
+		applyTTL(data);
+	}
+}
+
+HistoryItem::HistoryItem(
+	not_null<History*> history,
+	MsgId id,
+	const MTPDmessageService &data,
+	MessageFlags localFlags)
+: HistoryItem(
+		history,
+		id,
+		FlagsFromMTP(id, data.vflags().v, localFlags),
+		data.vdate().v,
+		data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) {
+	if (data.vaction().type() != mtpc_messageActionPhoneCall) {
+		createServiceFromMtp(data);
+	} else {
+		createComponents(CreateConfig());
+		_media = std::make_unique<Data::MediaCall>(
+			this,
+			Data::ComputeCallData(data.vaction().c_messageActionPhoneCall()));
+		setTextValue({});
+	}
+	applyTTL(data);
+}
+
+HistoryItem::HistoryItem(
+	not_null<History*> history,
+	MsgId id,
+	const MTPDmessageEmpty &data,
+	MessageFlags localFlags)
+: HistoryItem(
+	history,
+	id,
+	localFlags,
+	TimeId(0),
+	PreparedServiceText{ tr::lng_message_empty(
+		tr::now,
+		Ui::Text::WithEntities) }) {
+}
+
+HistoryItem::HistoryItem(
+	not_null<History*> history,
+	MsgId id,
+	MessageFlags flags,
+	TimeId date,
+	PreparedServiceText &&message,
+	PeerId from,
+	PhotoData *photo)
+: HistoryItem(history, id, flags, date, from) {
+	setServiceText(std::move(message));
+	if (photo) {
+		_media = std::make_unique<Data::MediaPhoto>(
+			this,
+			history->peer,
+			photo);
+	}
+}
+
+HistoryItem::HistoryItem(
+	not_null<History*> history,
+	MsgId id,
+	MessageFlags flags,
+	TimeId date,
+	PeerId from,
+	const QString &postAuthor,
+	not_null<HistoryItem*> original,
+	MsgId topicRootId)
+: HistoryItem(
+		history,
+		id,
+		(NewForwardedFlags(history->peer, from, original) | flags),
+		date,
+		from) {
+	const auto peer = history->peer;
+
+	auto config = CreateConfig();
+
+	const auto originalMedia = original->media();
+	const auto dropForwardInfo = original->computeDropForwardedInfo();
+	config.replyTo = config.replyToTop = topicRootId;
+	config.replyIsTopicPost = (topicRootId != 0);
+	if (!dropForwardInfo) {
+		config.originalDate = original->dateOriginal();
+		if (const auto info = original->hiddenSenderInfo()) {
+			config.senderNameOriginal = info->name;
+		} else if (const auto senderOriginal = original->senderOriginal()) {
+			config.senderOriginal = senderOriginal->id;
+			if (senderOriginal->isChannel()) {
+				config.originalId = original->idOriginal();
+			}
+		} else {
+			Unexpected("Corrupt forwarded information in message.");
+		}
+		config.authorOriginal = original->authorOriginal();
+	}
+	if (peer->isSelf()) {
+		//
+		// iOS app sends you to the original post if we forward a forward from channel.
+		// But server returns not the original post but the forward in saved_from_...
+		//
+		//if (config.originalId) {
+		//	config.savedFromPeer = config.senderOriginal;
+		//	config.savedFromMsgId = config.originalId;
+		//} else {
+			config.savedFromPeer = original->history()->peer->id;
+			config.savedFromMsgId = original->id;
+		//}
+	}
+	if (flags & MessageFlag::HasPostAuthor) {
+		config.author = postAuthor;
+	}
+	if (const auto fwdViaBot = original->viaBot()) {
+		config.viaBotId = peerToUser(fwdViaBot->id);
+	} else if (originalMedia && originalMedia->game()) {
+		if (const auto sender = original->senderOriginal()) {
+			if (const auto user = sender->asUser()) {
+				if (user->isBot()) {
+					config.viaBotId = peerToUser(user->id);
+				}
+			}
+		}
+	}
+	const auto fwdViewsCount = original->viewsCount();
+	if (fwdViewsCount > 0) {
+		config.viewsCount = fwdViewsCount;
+	} else if ((isPost() && !isScheduled())
+		|| (original->senderOriginal()
+			&& original->senderOriginal()->isChannel())) {
+		config.viewsCount = 1;
+	}
+
+	const auto mediaOriginal = original->media();
+	if (CopyMarkupToForward(original)) {
+		config.inlineMarkup = original->inlineReplyMarkup();
+	}
+	createComponents(std::move(config));
+
+	const auto ignoreMedia = [&] {
+		if (mediaOriginal && mediaOriginal->webpage()) {
+			if (peer->amRestricted(ChatRestriction::EmbedLinks)) {
+				return true;
+			}
+		}
+		return false;
+	};
+	if (mediaOriginal && !ignoreMedia()) {
+		_media = mediaOriginal->clone(this);
+	}
+
+	const auto dropCustomEmoji = dropForwardInfo
+		&& !history->session().premium()
+		&& !history->peer->isSelf();
+	setText(dropCustomEmoji
+		? DropCustomEmoji(original->originalText())
+		: original->originalText());
+}
+
+HistoryItem::HistoryItem(
+	not_null<History*> history,
+	MsgId id,
+	MessageFlags flags,
+	MsgId replyTo,
+	UserId viaBotId,
+	TimeId date,
+	PeerId from,
+	const QString &postAuthor,
+	const TextWithEntities &textWithEntities,
+	const MTPMessageMedia &media,
+	HistoryMessageMarkupData &&markup,
+	uint64 groupedId)
+: HistoryItem(
+		history,
+		id,
+		flags,
+		date,
+		(flags & MessageFlag::HasFromId) ? from : 0) {
+	createComponentsHelper(
+		flags,
+		replyTo,
+		viaBotId,
+		postAuthor,
+		std::move(markup));
+	setMedia(media);
+	setText(textWithEntities);
+	if (groupedId) {
+		setGroupId(MessageGroupId::FromRaw(history->peer->id, groupedId));
+	}
+}
+
+HistoryItem::HistoryItem(
+	not_null<History*> history,
+	MsgId id,
+	MessageFlags flags,
+	MsgId replyTo,
+	UserId viaBotId,
+	TimeId date,
+	PeerId from,
+	const QString &postAuthor,
+	not_null<DocumentData*> document,
+	const TextWithEntities &caption,
+	HistoryMessageMarkupData &&markup)
+: HistoryItem(
+		history,
+		id,
+		flags,
+		date,
+		(flags & MessageFlag::HasFromId) ? from : 0) {
+	createComponentsHelper(
+		flags,
+		replyTo,
+		viaBotId,
+		postAuthor,
+		std::move(markup));
+
+	const auto skipPremiumEffect = !history->session().premium();
+	_media = std::make_unique<Data::MediaFile>(
+		this,
+		document,
+		skipPremiumEffect);
+	setText(caption);
+}
+
+HistoryItem::HistoryItem(
+	not_null<History*> history,
+	MsgId id,
+	MessageFlags flags,
+	MsgId replyTo,
+	UserId viaBotId,
+	TimeId date,
+	PeerId from,
+	const QString &postAuthor,
+	not_null<PhotoData*> photo,
+	const TextWithEntities &caption,
+	HistoryMessageMarkupData &&markup)
+: HistoryItem(
+		history,
+		id,
+		flags,
+		date,
+		(flags & MessageFlag::HasFromId) ? from : 0) {
+	createComponentsHelper(
+		flags,
+		replyTo,
+		viaBotId,
+		postAuthor,
+		std::move(markup));
+
+	_media = std::make_unique<Data::MediaPhoto>(this, photo);
+	setText(caption);
+}
+
+HistoryItem::HistoryItem(
+	not_null<History*> history,
+	MsgId id,
+	MessageFlags flags,
+	MsgId replyTo,
+	UserId viaBotId,
+	TimeId date,
+	PeerId from,
+	const QString &postAuthor,
+	not_null<GameData*> game,
+	HistoryMessageMarkupData &&markup)
+: HistoryItem(
+		history,
+		id,
+		flags,
+		date,
+		(flags & MessageFlag::HasFromId) ? from : 0) {
+	createComponentsHelper(
+		flags,
+		replyTo,
+		viaBotId,
+		postAuthor,
+		std::move(markup));
+
+	_media = std::make_unique<Data::MediaGame>(this, game);
+	setTextValue({});
+}
+
+HistoryItem::HistoryItem(
+	not_null<History*> history,
+	MsgId id,
+	Data::SponsoredFrom from,
+	const TextWithEntities &textWithEntities,
+	HistoryItem *injectedAfter)
+: HistoryItem(
+		history,
+		id,
+		((history->peer->isChannel() ? MessageFlag::Post : MessageFlag(0))
+			//| (from.peer ? MessageFlag::HasFromId : MessageFlag(0))
+			| MessageFlag::Local),
+		HistoryItem::NewMessageDate(injectedAfter
+			? injectedAfter->date()
+			: 0),
+		/*from.peer ? from.peer->id : */PeerId(0)) {
+	createComponentsHelper(
+		_flags,
+		MsgId(0), // replyTo
+		UserId(0), // viaBotId
+		QString(), // postAuthor
+		HistoryMessageMarkupData());
+	setText(textWithEntities);
+	setSponsoredFrom(from);
+}
+
 HistoryItem::HistoryItem(
 	not_null<History*> history,
 	MsgId id,
@@ -263,6 +618,16 @@ HistoryItem::HistoryItem(
 	}
 }
 
+HistoryItem::~HistoryItem() {
+	_media = nullptr;
+	clearSavedMedia();
+	if (auto reply = Get<HistoryMessageReply>()) {
+		reply->clearData(this);
+	}
+	clearDependencyMessage();
+	applyTTL(0);
+}
+
 TimeId HistoryItem::date() const {
 	return _date;
 }
@@ -271,12 +636,136 @@ TimeId HistoryItem::NewMessageDate(TimeId scheduled) {
 	return scheduled ? scheduled : base::unixtime::now();
 }
 
-void HistoryItem::applyServiceDateEdition(const MTPDmessageService &data) {
-	const auto date = data.vdate().v;
-	if (_date == date) {
-		return;
+HistoryServiceDependentData *HistoryItem::GetServiceDependentData() {
+	if (const auto pinned = Get<HistoryServicePinned>()) {
+		return pinned;
+	} else if (const auto gamescore = Get<HistoryServiceGameScore>()) {
+		return gamescore;
+	} else if (const auto payment = Get<HistoryServicePayment>()) {
+		return payment;
+	} else if (const auto info = Get<HistoryServiceTopicInfo>()) {
+		return info;
 	}
-	_date = date;
+	return nullptr;
+}
+
+auto HistoryItem::GetServiceDependentData() const
+-> const HistoryServiceDependentData * {
+	return const_cast<HistoryItem*>(this)->GetServiceDependentData();
+}
+
+void HistoryItem::dependencyItemRemoved(not_null<HistoryItem*> dependency) {
+	if (const auto reply = Get<HistoryMessageReply>()) {
+		const auto documentId = reply->replyToDocumentId;
+		reply->itemRemoved(this, dependency);
+		if (documentId != reply->replyToDocumentId
+			&& generateLocalEntitiesByReply()) {
+			_history->owner().requestItemTextRefresh(this);
+		}
+	} else {
+		clearDependencyMessage();
+		updateDependentServiceText();
+	}
+}
+
+void HistoryItem::updateDependencyItem() {
+	if (const auto reply = Get<HistoryMessageReply>()) {
+		const auto documentId = reply->replyToDocumentId;
+		const auto webpageId = reply->replyToWebPageId;
+		reply->updateData(this, true);
+		const auto mediaIdChanged = (documentId != reply->replyToDocumentId)
+			|| (webpageId != reply->replyToWebPageId);
+		if (mediaIdChanged && generateLocalEntitiesByReply()) {
+			_history->owner().requestItemTextRefresh(this);
+		}
+	} else if (GetServiceDependentData()) {
+		updateServiceDependent(true);
+	}
+}
+
+void HistoryItem::updateDependentServiceText() {
+	if (Has<HistoryServicePinned>()) {
+		updateServiceText(preparePinnedText());
+	} else if (Has<HistoryServiceGameScore>()) {
+		updateServiceText(prepareGameScoreText());
+	} else if (Has<HistoryServicePayment>()) {
+		updateServiceText(preparePaymentSentText());
+	}
+}
+
+bool HistoryItem::updateServiceDependent(bool force) {
+	auto dependent = GetServiceDependentData();
+	Assert(dependent != nullptr);
+
+	if (!force) {
+		if (!dependent->msgId || dependent->msg) {
+			return true;
+		}
+	}
+
+	if (!dependent->lnk) {
+		dependent->lnk = JumpToMessageClickHandler(
+			(dependent->peerId
+				? _history->owner().peer(dependent->peerId)
+				: _history->peer),
+			dependent->msgId,
+			fullId());
+	}
+	auto gotDependencyItem = false;
+	if (!dependent->msg) {
+		dependent->msg = _history->owner().message(
+			(dependent->peerId
+				? dependent->peerId
+				: _history->peer->id),
+			dependent->msgId);
+		if (dependent->msg) {
+			if (dependent->msg->isEmpty()) {
+				// Really it is deleted.
+				dependent->msg = nullptr;
+				force = true;
+			} else {
+				_history->owner().registerDependentMessage(
+					this,
+					dependent->msg);
+				gotDependencyItem = true;
+			}
+		}
+	}
+	if (dependent->msg) {
+		updateDependentServiceText();
+	} else if (force) {
+		if (dependent->msgId > 0) {
+			dependent->msgId = 0;
+			gotDependencyItem = true;
+		}
+		updateDependentServiceText();
+	}
+	if (force && gotDependencyItem) {
+		Core::App().notifications().checkDelayed();
+	}
+	return (dependent->msg || !dependent->msgId);
+}
+
+MsgId HistoryItem::dependencyMsgId() const {
+	if (auto dependent = GetServiceDependentData()) {
+		return dependent->msgId;
+	}
+	return replyToId();
+}
+
+void HistoryItem::checkBuyButton() {
+	if (const auto invoice = _media ? _media->invoice() : nullptr) {
+		if (invoice->receiptMsgId) {
+			replaceBuyWithReceiptInMarkup();
+		}
+	}
+}
+
+bool HistoryItem::notificationReady() const {
+	if (const auto dependent = GetServiceDependentData()) {
+		return (dependent->msg || !dependent->msgId);
+	}
+	return true;
 }
 
 void HistoryItem::finishEdition(int oldKeyboardTop) {
@@ -307,6 +796,176 @@ void HistoryItem::setGroupId(MessageGroupId groupId) {
 	_history->owner().groups().registerMessage(this);
 }
 
+bool HistoryItem::checkCommentsLinkedChat(ChannelId id) const {
+	if (!id) {
+		return true;
+	} else if (const auto channel = _history->peer->asChannel()) {
+		if (channel->linkedChatKnown()
+			|| !(channel->flags() & ChannelDataFlag::HasLink)) {
+			const auto linked = channel->linkedChat();
+			if (!linked || peerToChannel(linked->id) != id) {
+				return false;
+			}
+		}
+		return true;
+	}
+	return false;
+}
+
+void HistoryItem::setReplyMarkup(HistoryMessageMarkupData &&markup) {
+	const auto requestUpdate = [&] {
+		history()->owner().requestItemResize(this);
+		history()->session().changes().messageUpdated(
+			this,
+			Data::MessageUpdate::Flag::ReplyMarkup);
+	};
+	if (markup.isNull()) {
+		if (_flags & MessageFlag::HasReplyMarkup) {
+			_flags &= ~MessageFlag::HasReplyMarkup;
+			if (Has<HistoryMessageReplyMarkup>()) {
+				RemoveComponents(HistoryMessageReplyMarkup::Bit());
+			}
+			requestUpdate();
+		}
+		return;
+	}
+
+	// optimization: don't create markup component for the case
+	// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
+	if (markup.isTrivial()) {
+		bool changed = false;
+		if (Has<HistoryMessageReplyMarkup>()) {
+			RemoveComponents(HistoryMessageReplyMarkup::Bit());
+			changed = true;
+		}
+		if (!(_flags & MessageFlag::HasReplyMarkup)) {
+			_flags |= MessageFlag::HasReplyMarkup;
+			changed = true;
+		}
+		if (changed) {
+			requestUpdate();
+		}
+	} else {
+		if (!(_flags & MessageFlag::HasReplyMarkup)) {
+			_flags |= MessageFlag::HasReplyMarkup;
+		}
+		if (!Has<HistoryMessageReplyMarkup>()) {
+			AddComponents(HistoryMessageReplyMarkup::Bit());
+		}
+		Get<HistoryMessageReplyMarkup>()->updateData(std::move(markup));
+		requestUpdate();
+	}
+}
+
+void HistoryItem::setCommentsInboxReadTill(MsgId readTillId) {
+	const auto views = Get<HistoryMessageViews>();
+	if (!views) {
+		return;
+	}
+	const auto newReadTillId = std::max(readTillId.bare, int64(1));
+	const auto ignore = (newReadTillId < views->commentsInboxReadTillId);
+	if (ignore) {
+		return;
+	}
+	const auto changed = (newReadTillId > views->commentsInboxReadTillId);
+	if (!changed) {
+		return;
+	}
+	const auto wasUnread = areCommentsUnread();
+	views->commentsInboxReadTillId = newReadTillId;
+	if (wasUnread && !areCommentsUnread()) {
+		_history->owner().requestItemRepaint(this);
+	}
+}
+
+void HistoryItem::setCommentsMaxId(MsgId maxId) {
+	if (const auto views = Get<HistoryMessageViews>()) {
+		if (views->commentsMaxId != maxId) {
+			const auto wasUnread = areCommentsUnread();
+			views->commentsMaxId = maxId;
+			if (wasUnread != areCommentsUnread()) {
+				_history->owner().requestItemRepaint(this);
+			}
+		}
+	}
+}
+
+void HistoryItem::setCommentsPossibleMaxId(MsgId possibleMaxId) {
+	if (const auto views = Get<HistoryMessageViews>()) {
+		if (views->commentsMaxId < possibleMaxId) {
+			const auto wasUnread = areCommentsUnread();
+			views->commentsMaxId = possibleMaxId;
+			if (!wasUnread && areCommentsUnread()) {
+				_history->owner().requestItemRepaint(this);
+			}
+		}
+	}
+}
+
+bool HistoryItem::areCommentsUnread() const {
+	const auto views = Get<HistoryMessageViews>();
+	if (!views
+		|| !views->commentsMegagroupId
+		|| !checkCommentsLinkedChat(views->commentsMegagroupId)) {
+		return false;
+	}
+	const auto till = views->commentsInboxReadTillId;
+	if (views->commentsInboxReadTillId < 2 || views->commentsMaxId <= till) {
+		return false;
+	}
+	const auto group = views->commentsMegagroupId
+		? _history->owner().historyLoaded(
+			peerFromChannel(views->commentsMegagroupId))
+		: _history.get();
+	return !group || (views->commentsMaxId > group->inboxReadTillId());
+}
+
+FullMsgId HistoryItem::commentsItemId() const {
+	if (const auto views = Get<HistoryMessageViews>()) {
+		return FullMsgId(
+			PeerId(views->commentsMegagroupId),
+			views->commentsRootId);
+	}
+	return FullMsgId();
+}
+
+void HistoryItem::setCommentsItemId(FullMsgId id) {
+	if (id.peer == _history->peer->id) {
+		if (id.msg != this->id) {
+			if (const auto reply = Get<HistoryMessageReply>()) {
+				reply->replyToMsgTop = id.msg;
+			}
+		}
+	} else if (const auto views = Get<HistoryMessageViews>()) {
+		if (const auto channelId = peerToChannel(id.peer)) {
+			if (views->commentsMegagroupId != channelId) {
+				views->commentsMegagroupId = channelId;
+				_history->owner().requestItemResize(this);
+			}
+			views->commentsRootId = id.msg;
+		}
+	}
+}
+
+void HistoryItem::setServiceText(PreparedServiceText &&prepared) {
+	AddComponents(HistoryServiceData::Bit());
+	_flags &= ~MessageFlag::HasTextLinks;
+	const auto data = Get<HistoryServiceData>();
+	const auto had = !_text.empty();
+	_text = std::move(prepared.text);
+	data->textLinks = std::move(prepared.links);
+	if (had) {
+		_history->owner().requestItemTextRefresh(this);
+	}
+}
+
+void HistoryItem::updateServiceText(PreparedServiceText &&text) {
+	setServiceText(std::move(text));
+	_history->owner().requestItemResize(this);
+	invalidateChatListEntry();
+	_history->owner().updateDependentMessages(this);
+}
+
 HistoryMessageReplyMarkup *HistoryItem::inlineReplyMarkup() {
 	if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
 		if (markup->data.flags & ReplyMarkupFlag::Inline) {
@@ -324,7 +983,7 @@ ReplyKeyboard *HistoryItem::inlineReplyKeyboard() {
 }
 
 ChannelData *HistoryItem::discussionPostOriginalSender() const {
-	if (!history()->peer->isMegagroup()) {
+	if (!_history->peer->isMegagroup()) {
 		return nullptr;
 	}
 	if (const auto forwarded = Get<HistoryMessageForwarded>()) {
@@ -341,7 +1000,7 @@ bool HistoryItem::isDiscussionPost() const {
 }
 
 HistoryItem *HistoryItem::lookupDiscussionPostOriginal() const {
-	if (!history()->peer->isMegagroup()) {
+	if (!_history->peer->isMegagroup()) {
 		return nullptr;
 	}
 	const auto forwarded = Get<HistoryMessageForwarded>();
@@ -363,18 +1022,30 @@ PeerData *HistoryItem::displayFrom() const {
 			return nullptr;
 		}
 	} else if (const auto forwarded = Get<HistoryMessageForwarded>()) {
-		if (history()->peer->isSelf() || history()->peer->isRepliesChat() || forwarded->imported) {
+		if (_history->peer->isSelf() || _history->peer->isRepliesChat() || forwarded->imported) {
 			return forwarded->originalSender;
 		}
 	}
 	return author().get();
 }
 
+std::unique_ptr<HistoryView::Element> HistoryItem::createView(
+		not_null<HistoryView::ElementDelegate*> delegate,
+		HistoryView::Element *replacing) {
+	if (isService()) {
+		return std::make_unique<HistoryView::Service>(
+			delegate,
+			this,
+			replacing);
+	}
+	return std::make_unique<HistoryView::Message>(delegate, this, replacing);
+}
+
 void HistoryItem::invalidateChatListEntry() {
-	history()->session().changes().messageUpdated(
+	_history->session().changes().messageUpdated(
 		this,
 		Data::MessageUpdate::Flag::DialogRowRefresh);
-	history()->lastItemDialogsView().itemInvalidated(this);
+	_history->lastItemDialogsView().itemInvalidated(this);
 	if (const auto topic = this->topic()) {
 		topic->lastItemDialogsView().itemInvalidated(this);
 	}
@@ -383,7 +1054,7 @@ void HistoryItem::invalidateChatListEntry() {
 void HistoryItem::customEmojiRepaint() {
 	if (!(_flags & MessageFlag::CustomEmojiRepainting)) {
 		_flags |= MessageFlag::CustomEmojiRepainting;
-		history()->owner().requestItemRepaint(this);
+		_history->owner().requestItemRepaint(this);
 	}
 }
 
@@ -447,6 +1118,13 @@ void HistoryItem::markMediaAndMentionRead() {
 			topic->unreadMentions().erase(id);
 		}
 	}
+
+	if (const auto selfdestruct = Get<HistoryServiceSelfDestruct>()) {
+		if (!selfdestruct->destructAt) {
+			selfdestruct->destructAt = crl::now() + selfdestruct->timeToLive;
+			_history->owner().selfDestructIn(this, selfdestruct->timeToLive);
+		}
+	}
 }
 
 void HistoryItem::markReactionsRead() {
@@ -465,7 +1143,7 @@ void HistoryItem::markReactionsRead() {
 bool HistoryItem::markContentsRead(bool fromThisClient) {
 	if (hasUnreadReaction()) {
 		if (fromThisClient) {
-			history()->owner().requestUnreadReactionsAnimation(this);
+			_history->owner().requestUnreadReactionsAnimation(this);
 		}
 		markReactionsRead();
 		return true;
@@ -480,17 +1158,17 @@ void HistoryItem::setIsPinned(bool pinned) {
 	const auto changed = (isPinned() != pinned);
 	if (pinned) {
 		_flags |= MessageFlag::Pinned;
-		auto &storage = history()->session().storage();
+		auto &storage = _history->session().storage();
 		storage.add(Storage::SharedMediaAddExisting(
-			history()->peer->id,
+			_history->peer->id,
 			MsgId(0), // topicRootId
 			Storage::SharedMediaType::Pinned,
 			id,
 			{ id, id }));
-		history()->setHasPinnedMessages(true);
+		_history->setHasPinnedMessages(true);
 		if (const auto topic = this->topic()) {
 			storage.add(Storage::SharedMediaAddExisting(
-				history()->peer->id,
+				_history->peer->id,
 				topic->rootId(),
 				Storage::SharedMediaType::Pinned,
 				id,
@@ -499,17 +1177,30 @@ void HistoryItem::setIsPinned(bool pinned) {
 		}
 	} else {
 		_flags &= ~MessageFlag::Pinned;
-		history()->session().storage().remove(Storage::SharedMediaRemoveOne(
-			history()->peer->id,
+		_history->session().storage().remove(Storage::SharedMediaRemoveOne(
+			_history->peer->id,
 			Storage::SharedMediaType::Pinned,
 			id));
 	}
 	if (changed) {
-		history()->owner().notifyItemDataChange(this);
+		_history->owner().notifyItemDataChange(this);
 	}
 }
 
 void HistoryItem::returnSavedMedia() {
+	if (!isEditingMedia()) {
+		return;
+	}
+	const auto wasGrouped = history()->owner().groups().isGrouped(this);
+	_media = std::move(_savedLocalEditMediaData->media);
+	setText(_savedLocalEditMediaData->text);
+	clearSavedMedia();
+	if (wasGrouped) {
+		history()->owner().groups().refreshMessage(this, true);
+	} else {
+		history()->owner().requestItemViewRefresh(this);
+		history()->owner().updateDependentMessages(this);
+	}
 }
 
 void HistoryItem::savePreviousMedia() {
@@ -587,7 +1278,7 @@ UserData *HistoryItem::getMessageBot() const {
 	}
 	auto bot = from()->asUser();
 	if (!bot) {
-		bot = history()->peer->asUser();
+		bot = _history->peer->asUser();
 	}
 	return (bot && bot->isBot()) ? bot : nullptr;
 }
@@ -666,28 +1357,98 @@ void HistoryItem::clearMainView() {
 	_mainView = nullptr;
 }
 
-void HistoryItem::addToUnreadThings(HistoryUnreadThings::AddType type) {
+void HistoryItem::applyEdition(HistoryMessageEdition &&edition) {
+	int keyboardTop = -1;
+	//if (!pendingResize()) {// #TODO edit bot message
+	//	if (auto keyboard = inlineReplyKeyboard()) {
+	//		int h = st::msgBotKbButton.margin + keyboard->naturalHeight();
+	//		keyboardTop = _height - h + st::msgBotKbButton.margin - marginBottom();
+	//	}
+	//}
+
+	if (edition.isEditHide) {
+		_flags |= MessageFlag::HideEdited;
+	} else {
+		_flags &= ~MessageFlag::HideEdited;
+	}
+
+	if (edition.editDate != -1) {
+		//_flags |= MTPDmessage::Flag::f_edit_date;
+		if (!Has<HistoryMessageEdited>()) {
+			AddComponents(HistoryMessageEdited::Bit());
+		}
+		auto edited = Get<HistoryMessageEdited>();
+		edited->date = edition.editDate;
+	}
+
+	if (!edition.useSameMarkup) {
+		setReplyMarkup(base::take(edition.replyMarkup));
+	}
+	if (!isLocalUpdateMedia()) {
+		refreshMedia(edition.mtpMedia);
+	}
+	if (!edition.useSameReactions) {
+		updateReactions(edition.mtpReactions);
+	}
+	changeViewsCount(edition.views);
+	setForwardsCount(edition.forwards);
+	setText(_media
+		? edition.textWithEntities
+		: EnsureNonEmpty(edition.textWithEntities));
+	if (!edition.useSameReplies) {
+		if (!edition.replies.isNull) {
+			if (checkRepliesPts(edition.replies)) {
+				setReplies(base::take(edition.replies));
+			}
+		} else {
+			clearReplies();
+		}
+	}
+
+	applyTTL(edition.ttl);
+
+	finishEdition(keyboardTop);
 }
 
-void HistoryItem::applyEditionToHistoryCleared() {
-	applyEdition(
-		MTP_messageService(
-			MTP_flags(0),
-			MTP_int(id),
-			peerToMTP(PeerId(0)), // from_id
-			peerToMTP(history()->peer->id),
-			MTPMessageReplyHeader(),
-			MTP_int(date()),
-			MTP_messageActionHistoryClear(),
-			MTPint() // ttl_period
-		).c_messageService());
+void HistoryItem::applyEdition(const MTPDmessageService &message) {
+	if (message.vaction().type() == mtpc_messageActionHistoryClear) {
+		const auto wasGrouped = history()->owner().groups().isGrouped(this);
+		setReplyMarkup({});
+		refreshMedia(nullptr);
+		setTextValue({});
+		changeViewsCount(-1);
+		setForwardsCount(-1);
+		if (wasGrouped) {
+			history()->owner().groups().unregisterMessage(this);
+		}
+		clearDependencyMessage();
+		UpdateComponents(0);
+		createServiceFromMtp(message);
+		applyServiceDateEdition(message);
+		finishEditionToEmpty();
+	} else if (isService()) {
+		clearDependencyMessage();
+		UpdateComponents(0);
+		createServiceFromMtp(message);
+		applyServiceDateEdition(message);
+		finishEdition(-1);
+	}
+}
+
+void HistoryItem::applyEdition(const MTPMessageExtendedMedia &media) {
+	if (const auto existing = this->media()) {
+		if (existing->updateExtendedMedia(this, media)) {
+			checkBuyButton();
+			finishEdition(-1);
+		}
+	}
 }
 
 void HistoryItem::applySentMessage(const MTPDmessage &data) {
 	updateSentContent({
 		qs(data.vmessage()),
 		Api::EntitiesFromMTP(
-			&history()->session(),
+			&_history->session(),
 			data.ventities().value_or_empty())
 	}, data.vmedia());
 	updateReplyMarkup(HistoryMessageMarkupData(data.vreply_markup()));
@@ -713,9 +1474,14 @@ void HistoryItem::applySentMessage(const MTPDmessage &data) {
 	contributeToSlowmode(data.vdate().v);
 	indexAsNewItem();
 	invalidateChatListEntry();
-	history()->owner().notifyItemDataChange(this);
-	history()->owner().requestItemTextRefresh(this);
-	history()->owner().updateDependentMessages(this);
+	if (const auto period = data.vttl_period(); period && period->v > 0) {
+		applyTTL(data.vdate().v + period->v);
+	} else {
+		applyTTL(0);
+	}
+	_history->owner().notifyItemDataChange(this);
+	_history->owner().requestItemTextRefresh(this);
+	_history->owner().updateDependentMessages(this);
 }
 
 void HistoryItem::applySentMessage(
@@ -725,7 +1491,7 @@ void HistoryItem::applySentMessage(
 	updateSentContent({
 		text,
 		Api::EntitiesFromMTP(
-			&history()->session(),
+			&_history->session(),
 			data.ventities().value_or_empty())
 		}, data.vmedia());
 	contributeToSlowmode(data.vdate().v);
@@ -733,6 +1499,154 @@ void HistoryItem::applySentMessage(
 		indexAsNewItem();
 	}
 	invalidateChatListEntry();
+	if (const auto period = data.vttl_period(); period && period->v > 0) {
+		applyTTL(data.vdate().v + period->v);
+	} else {
+		applyTTL(0);
+	}
+}
+
+void HistoryItem::updateSentContent(
+		const TextWithEntities &textWithEntities,
+		const MTPMessageMedia *media) {
+	setText(textWithEntities);
+	if (_flags & MessageFlag::FromInlineBot) {
+		if (!media || !_media || !_media->updateInlineResultMedia(*media)) {
+			refreshSentMedia(media);
+		}
+		_flags &= ~MessageFlag::FromInlineBot;
+	} else if (media || _media) {
+		if (!media || !_media || !_media->updateSentMedia(*media)) {
+			refreshSentMedia(media);
+		}
+	}
+	history()->owner().requestItemResize(this);
+}
+
+void HistoryItem::updateForwardedInfo(const MTPMessageFwdHeader *fwd) {
+	const auto forwarded = Get<HistoryMessageForwarded>();
+	if (!fwd) {
+		if (forwarded) {
+			LOG(("API Error: Server removed forwarded information."));
+		}
+		return;
+	} else if (!forwarded) {
+		LOG(("API Error: Server added forwarded information."));
+		return;
+	}
+	fwd->match([&](const MTPDmessageFwdHeader &data) {
+		auto config = CreateConfig();
+		FillForwardedInfo(config, data);
+		setupForwardedComponent(config);
+		history()->owner().requestItemResize(this);
+	});
+}
+
+void HistoryItem::applyEditionToHistoryCleared() {
+	applyEdition(
+		MTP_messageService(
+			MTP_flags(0),
+			MTP_int(id),
+			peerToMTP(PeerId(0)), // from_id
+			peerToMTP(_history->peer->id),
+			MTPMessageReplyHeader(),
+			MTP_int(date()),
+			MTP_messageActionHistoryClear(),
+			MTPint() // ttl_period
+		).c_messageService());
+}
+
+void HistoryItem::updateReplyMarkup(HistoryMessageMarkupData &&markup) {
+	setReplyMarkup(std::move(markup));
+}
+
+void HistoryItem::contributeToSlowmode(TimeId realDate) {
+	if (const auto channel = history()->peer->asChannel()) {
+		if (out() && isRegular() && !isService()) {
+			channel->growSlowmodeLastMessage(realDate ? realDate : date());
+		}
+	}
+}
+
+void HistoryItem::addToUnreadThings(HistoryUnreadThings::AddType type) {
+	if (!isRegular()) {
+		return;
+	}
+	const auto mention = isUnreadMention();
+	const auto reaction = hasUnreadReaction();
+	if (!mention && !reaction) {
+		return;
+	}
+	const auto topic = this->topic();
+	const auto history = this->history();
+	const auto changes = &history->session().changes();
+	if (mention) {
+		if (history->unreadMentions().add(id, type)) {
+			changes->historyUpdated(
+				history,
+				Data::HistoryUpdate::Flag::UnreadMentions);
+		}
+		if (topic && topic->unreadMentions().add(id, type)) {
+			changes->topicUpdated(
+				topic,
+				Data::TopicUpdate::Flag::UnreadMentions);
+		}
+	}
+	if (reaction) {
+		const auto toHistory = history->unreadReactions().add(id, type);
+		const auto toTopic = topic && topic->unreadReactions().add(id, type);
+		if (toHistory || toTopic) {
+			if (type == HistoryUnreadThings::AddType::New) {
+				changes->messageUpdated(
+					this,
+					Data::MessageUpdate::Flag::NewUnreadReaction);
+			}
+			if (hasUnreadReaction()) {
+				if (toHistory) {
+					changes->historyUpdated(
+						history,
+						Data::HistoryUpdate::Flag::UnreadReactions);
+				}
+				if (toTopic) {
+					changes->topicUpdated(
+						topic,
+						Data::TopicUpdate::Flag::UnreadReactions);
+				}
+			}
+		}
+	}
+}
+
+void HistoryItem::destroyHistoryEntry() {
+	if (isUnreadMention()) {
+		history()->unreadMentions().erase(id);
+		if (const auto topic = this->topic()) {
+			topic->unreadMentions().erase(id);
+		}
+	}
+	if (hasUnreadReaction()) {
+		history()->unreadReactions().erase(id);
+		if (const auto topic = this->topic()) {
+			topic->unreadReactions().erase(id);
+		}
+	}
+	if (const auto reply = Get<HistoryMessageReply>()) {
+		changeReplyToTopCounter(reply, -1);
+	}
+}
+
+Storage::SharedMediaTypesMask HistoryItem::sharedMediaTypes() const {
+	auto result = Storage::SharedMediaTypesMask {};
+	if (const auto media = this->media()) {
+		result.set(media->sharedMediaTypes());
+	}
+	if (hasTextLinks()) {
+		result.set(Storage::SharedMediaType::Link);
+	}
+	if (isPinned()) {
+		result.set(Storage::SharedMediaType::Pinned);
+	}
+	return result;
 }
 
 void HistoryItem::indexAsNewItem() {
@@ -754,6 +1668,49 @@ void HistoryItem::indexAsNewItem() {
 	}
 }
 
+void HistoryItem::incrementReplyToTopCounter() {
+	if (const auto reply = Get<HistoryMessageReply>()) {
+		changeReplyToTopCounter(reply, 1);
+	}
+}
+
+void HistoryItem::changeReplyToTopCounter(
+		not_null<HistoryMessageReply*> reply,
+		int delta) {
+	if (!isRegular() || !_history->peer->isMegagroup()) {
+		return;
+	} else if (delta > 0) {
+		_history->session().changes().messageUpdated(
+			this,
+			Data::MessageUpdate::Flag::ReplyToTopAdded);
+	}
+	const auto topId = reply->replyToTop();
+	if (!topId) {
+		return;
+	}
+	const auto top = _history->owner().message(_history->peer->id, topId);
+	if (!top) {
+		return;
+	}
+	const auto from = displayFrom();
+	const auto replier = from ? from->id : PeerId();
+	top->changeRepliesCount(delta, replier);
+	if (const auto original = top->lookupDiscussionPostOriginal()) {
+		original->changeRepliesCount(delta, replier);
+	}
+}
+
+QString HistoryItem::notificationHeader() const {
+	if (isService()) {
+		return QString();
+	} else if (out() && isFromScheduled() && !_history->peer->isSelf()) {
+		return tr::lng_from_you(tr::now);
+	} else if (!_history->peer->isUser() && !isPost()) {
+		return from()->name();
+	}
+	return QString();
+}
+
 void HistoryItem::setRealId(MsgId newId) {
 	Expects(_flags & MessageFlag::BeingSent);
 	Expects(IsClientMsgId(id));
@@ -775,7 +1732,15 @@ void HistoryItem::setRealId(MsgId newId) {
 	}
 
 	_history->owner().notifyItemDataChange(this);
+	_history->owner().groups().refreshMessage(this);
 	_history->owner().requestItemResize(this);
+
+	if (const auto reply = Get<HistoryMessageReply>()) {
+		if (reply->replyToLink()) {
+			reply->setReplyToLinkFrom(this);
+		}
+		changeReplyToTopCounter(reply, 1);
+	}
 }
 
 bool HistoryItem::canPin() const {
@@ -788,15 +1753,34 @@ bool HistoryItem::canPin() const {
 }
 
 bool HistoryItem::allowsSendNow() const {
-	return false;
+	return !isService()
+		&& isScheduled()
+		&& !isSending()
+		&& !hasFailed()
+		&& !isEditingMedia();
 }
 
 bool HistoryItem::allowsForward() const {
-	return false;
+	return !isService()
+		&& isRegular()
+		&& !forbidsForward()
+		&& history()->peer->allowsForwarding()
+		&& (!_media || _media->allowsForward());
+}
+
+bool HistoryItem::isTooOldForEdit(TimeId now) const {
+	return !_history->peer->canEditMessagesIndefinitely()
+		&& !isScheduled()
+		&& (now - date() >= _history->session().serverConfig().editTimeLimit);
 }
 
 bool HistoryItem::allowsEdit(TimeId now) const {
-	return false;
+	return !isService()
+		&& canBeEdited()
+		&& !isTooOldForEdit(now)
+		&& (!_media || _media->allowsEdit())
+		&& !isLegacyMessage()
+		&& !isEditingMedia();
 }
 
 bool HistoryItem::canBeEdited() const {
@@ -872,8 +1856,8 @@ bool HistoryItem::canDelete() const {
 }
 
 bool HistoryItem::canDeleteForEveryone(TimeId now) const {
-	const auto peer = history()->peer;
-	const auto &config = history()->session().serverConfig();
+	const auto peer = _history->peer;
+	const auto &config = _history->session().serverConfig();
 	const auto messageToMyself = peer->isSelf();
 	const auto messageTooOld = messageToMyself
 		? false
@@ -914,16 +1898,16 @@ bool HistoryItem::canDeleteForEveryone(TimeId now) const {
 bool HistoryItem::suggestReport() const {
 	if (out() || isService() || !isRegular()) {
 		return false;
-	} else if (const auto channel = history()->peer->asChannel()) {
+	} else if (const auto channel = _history->peer->asChannel()) {
 		return true;
-	} else if (const auto user = history()->peer->asUser()) {
+	} else if (const auto user = _history->peer->asUser()) {
 		return user->isBot();
 	}
 	return false;
 }
 
 bool HistoryItem::suggestBanReport() const {
-	const auto channel = history()->peer->asChannel();
+	const auto channel = _history->peer->asChannel();
 	if (!channel || !channel->canRestrictParticipant(from())) {
 		return false;
 	}
@@ -931,7 +1915,7 @@ bool HistoryItem::suggestBanReport() const {
 }
 
 bool HistoryItem::suggestDeleteAllReport() const {
-	auto channel = history()->peer->asChannel();
+	auto channel = _history->peer->asChannel();
 	if (!channel || !channel->canDeleteMessages()) {
 		return false;
 	}
@@ -955,7 +1939,7 @@ void HistoryItem::toggleReaction(
 	if (!_reactions) {
 		_reactions = std::make_unique<Data::MessageReactions>(this);
 		const auto canViewReactions = !isDiscussionPost()
-			&& (history()->peer->isChat() || history()->peer->isMegagroup());
+			&& (_history->peer->isChat() || _history->peer->isMegagroup());
 		if (canViewReactions) {
 			_flags |= MessageFlag::CanViewReactions;
 		}
@@ -965,72 +1949,12 @@ void HistoryItem::toggleReaction(
 		if (_reactions->empty()) {
 			_reactions = nullptr;
 			_flags &= ~MessageFlag::CanViewReactions;
-			history()->owner().notifyItemDataChange(this);
+			_history->owner().notifyItemDataChange(this);
 		}
 	} else {
 		_reactions->add(reaction, (source == ReactionSource::Selector));
 	}
-	history()->owner().notifyItemDataChange(this);
-}
-
-void HistoryItem::setReactions(const MTPMessageReactions *reactions) {
-	Expects(!_reactions);
-
-	if (changeReactions(reactions) && _reactions->hasUnread()) {
-		_flags |= MessageFlag::HasUnreadReaction;
-	}
-}
-
-void HistoryItem::updateReactions(const MTPMessageReactions *reactions) {
-	const auto wasRecentUsers = LookupRecentUnreadReactedUsers(this);
-	const auto hadUnread = hasUnreadReaction();
-	const auto changed = changeReactions(reactions);
-	if (!changed) {
-		return;
-	}
-	const auto hasUnread = _reactions && _reactions->hasUnread();
-	if (hasUnread && !hadUnread) {
-		_flags |= MessageFlag::HasUnreadReaction;
-
-		addToUnreadThings(HistoryUnreadThings::AddType::New);
-	} else if (!hasUnread && hadUnread) {
-		markReactionsRead();
-	}
-	CheckReactionNotificationSchedule(this, wasRecentUsers);
-	history()->owner().notifyItemDataChange(this);
-}
-
-bool HistoryItem::changeReactions(const MTPMessageReactions *reactions) {
-	if (reactions || _reactionsLastRefreshed) {
-		_reactionsLastRefreshed = crl::now();
-	}
-	if (!reactions) {
-		_flags &= ~MessageFlag::CanViewReactions;
-		return (base::take(_reactions) != nullptr);
-	}
-	return reactions->match([&](const MTPDmessageReactions &data) {
-		if (data.is_can_see_list()) {
-			_flags |= MessageFlag::CanViewReactions;
-		} else {
-			_flags &= ~MessageFlag::CanViewReactions;
-		}
-		if (data.vresults().v.isEmpty()) {
-			return (base::take(_reactions) != nullptr);
-		} else if (!_reactions) {
-			_reactions = std::make_unique<Data::MessageReactions>(this);
-		}
-		const auto min = data.is_min();
-		const auto &list = data.vresults().v;
-		const auto &recent = data.vrecent_reactions().value_or_empty();
-		if (min && hasUnreadReaction()) {
-			// We can't update reactions from min if we have unread.
-			if (_reactions->checkIfChanged(list, recent)) {
-				updateReactionsUnknown();
-			}
-			return false;
-		}
-		return _reactions->change(list, recent, min);
-	});
+	_history->owner().notifyItemDataChange(this);
 }
 
 void HistoryItem::updateReactionsUnknown() {
@@ -1105,7 +2029,7 @@ Data::MessagePosition HistoryItem::position() const {
 bool HistoryItem::computeDropForwardedInfo() const {
 	const auto media = this->media();
 	return (media && media->dropForwardedInfo())
-		|| (history()->peer->isSelf()
+		|| (_history->peer->isSelf()
 			&& !Has<HistoryMessageForwarded>()
 			&& (!media || !media->forceForwardedInfo()));
 }
@@ -1116,7 +2040,7 @@ bool HistoryItem::inThread(MsgId rootId) const {
 }
 
 not_null<PeerData*> HistoryItem::author() const {
-	return (isPost() && !isSponsored()) ? history()->peer : from();
+	return (isPost() && !isSponsored()) ? _history->peer : from();
 }
 
 TimeId HistoryItem::dateOriginal() const {
@@ -1130,7 +2054,7 @@ PeerData *HistoryItem::senderOriginal() const {
 	if (const auto forwarded = Get<HistoryMessageForwarded>()) {
 		return forwarded->originalSender;
 	}
-	const auto peer = history()->peer;
+	const auto peer = _history->peer;
 	return (peer->isChannel() && !peer->isMegagroup()) ? peer : from();
 }
 
@@ -1172,6 +2096,220 @@ MsgId HistoryItem::idOriginal() const {
 	return id;
 }
 
+TextWithEntities HistoryItem::originalText() const {
+	return isService() ? TextWithEntities() : _text;
+}
+
+TextWithEntities HistoryItem::originalTextWithLocalEntities() const {
+	return isService()
+		? TextWithEntities()
+		: withLocalEntities(originalText());
+}
+
+TextForMimeData HistoryItem::clipboardText() const {
+	return isService()
+		? TextForMimeData()
+		: TextForMimeData::WithExpandedLinks(_text);
+}
+
+bool HistoryItem::changeViewsCount(int count) {
+	const auto views = Get<HistoryMessageViews>();
+	if (!views
+		|| views->views.count == count
+		|| (count >= 0 && views->views.count > count)) {
+		return false;
+	}
+
+	views->views.count = count;
+	return true;
+}
+
+void HistoryItem::setForwardsCount(int count) {
+	const auto views = Get<HistoryMessageViews>();
+	if (!views
+		|| views->forwardsCount == count
+		|| (count >= 0 && views->forwardsCount > count)) {
+		return;
+	}
+
+	views->forwardsCount = count;
+	history()->owner().notifyItemDataChange(this);
+}
+
+void HistoryItem::setPostAuthor(const QString &author) {
+	auto msgsigned = Get<HistoryMessageSigned>();
+	if (author.isEmpty()) {
+		if (!msgsigned) {
+			return;
+		}
+		RemoveComponents(HistoryMessageSigned::Bit());
+		history()->owner().requestItemResize(this);
+		return;
+	}
+	if (!msgsigned) {
+		AddComponents(HistoryMessageSigned::Bit());
+		msgsigned = Get<HistoryMessageSigned>();
+	} else if (msgsigned->author == author) {
+		return;
+	}
+	msgsigned->author = author;
+	msgsigned->isAnonymousRank = !isDiscussionPost()
+		&& this->author()->isMegagroup();
+	history()->owner().requestItemResize(this);
+}
+
+void HistoryItem::setReplies(HistoryMessageRepliesData &&data) {
+	if (data.isNull) {
+		return;
+	}
+	auto views = Get<HistoryMessageViews>();
+	if (!views) {
+		AddComponents(HistoryMessageViews::Bit());
+		views = Get<HistoryMessageViews>();
+	}
+	const auto &repliers = data.recentRepliers;
+	const auto count = data.repliesCount;
+	const auto channelId = data.channelId;
+	const auto readTillId = data.readMaxId
+		? std::max({
+			views->commentsInboxReadTillId.bare,
+			data.readMaxId.bare,
+			int64(1),
+		})
+		: views->commentsInboxReadTillId;
+	const auto maxId = data.maxId ? data.maxId : views->commentsMaxId;
+	const auto countsChanged = (views->replies.count != count)
+		|| (views->commentsInboxReadTillId != readTillId)
+		|| (views->commentsMaxId != maxId);
+	const auto megagroupChanged = (views->commentsMegagroupId != channelId);
+	const auto recentChanged = (views->recentRepliers != repliers);
+	if (!countsChanged && !megagroupChanged && !recentChanged) {
+		return;
+	}
+	views->replies.count = count;
+	if (recentChanged) {
+		views->recentRepliers = repliers;
+	}
+	const auto wasUnread = areCommentsUnread();
+	views->commentsMegagroupId = channelId;
+	views->commentsInboxReadTillId = readTillId;
+	views->commentsMaxId = maxId;
+	if (wasUnread != areCommentsUnread()) {
+		history()->owner().requestItemRepaint(this);
+	}
+	refreshRepliesText(views, megagroupChanged);
+}
+
+void HistoryItem::clearReplies() {
+	auto views = Get<HistoryMessageViews>();
+	if (!views) {
+		return;
+	}
+	const auto viewsPart = views->views;
+	if (viewsPart.count < 0) {
+		RemoveComponents(HistoryMessageViews::Bit());
+	} else {
+		*views = HistoryMessageViews();
+		views->views = viewsPart;
+	}
+	history()->owner().requestItemResize(this);
+}
+
+void HistoryItem::refreshRepliesText(
+		not_null<HistoryMessageViews*> views,
+		bool forceResize) {
+	if (views->commentsMegagroupId) {
+		views->replies.text = (views->replies.count > 0)
+			? tr::lng_comments_open_count(
+				tr::now,
+				lt_count_short,
+				views->replies.count)
+			: tr::lng_comments_open_none(tr::now);
+		views->replies.textWidth = st::semiboldFont->width(
+			views->replies.text);
+		views->repliesSmall.text = (views->replies.count > 0)
+			? Lang::FormatCountToShort(views->replies.count).string
+			: QString();
+		const auto hadText = (views->repliesSmall.textWidth > 0);
+		views->repliesSmall.textWidth = (views->replies.count > 0)
+			? st::semiboldFont->width(views->repliesSmall.text)
+			: 0;
+		const auto hasText = (views->repliesSmall.textWidth > 0);
+		if (hasText != hadText) {
+			forceResize = true;
+		}
+	}
+	if (forceResize) {
+		history()->owner().requestItemResize(this);
+	} else {
+		history()->owner().requestItemRepaint(this);
+	}
+}
+
+void HistoryItem::changeRepliesCount(int delta, PeerId replier) {
+	const auto views = Get<HistoryMessageViews>();
+	const auto limit = HistoryMessageViews::kMaxRecentRepliers;
+	if (!views) {
+		return;
+	}
+
+	// Update full count.
+	if (views->replies.count < 0) {
+		return;
+	}
+	views->replies.count = std::max(views->replies.count + delta, 0);
+	if (replier && views->commentsMegagroupId) {
+		if (delta < 0) {
+			views->recentRepliers.erase(
+				ranges::remove(views->recentRepliers, replier),
+				end(views->recentRepliers));
+		} else if (!ranges::contains(views->recentRepliers, replier)) {
+			views->recentRepliers.insert(views->recentRepliers.begin(), replier);
+			while (views->recentRepliers.size() > limit) {
+				views->recentRepliers.pop_back();
+			}
+		}
+	}
+	refreshRepliesText(views);
+	history()->owner().notifyItemDataChange(this);
+}
+
+void HistoryItem::setReplyFields(
+		MsgId replyTo,
+		MsgId replyToTop,
+		bool isForumPost) {
+	if (isScheduled()) {
+		return;
+	} else if (const auto data = GetServiceDependentData()) {
+		if ((data->topId != replyToTop) && !IsServerMsgId(data->topId)) {
+			data->topId = replyToTop;
+			if (isForumPost) {
+				data->topicPost = true;
+			}
+		}
+	} else if (const auto reply = Get<HistoryMessageReply>()) {
+		reply->topicPost = isForumPost;
+		if ((reply->replyToMsgId != replyTo)
+			&& !IsServerMsgId(reply->replyToMsgId)) {
+			reply->replyToMsgId = replyTo;
+			if (!reply->updateData(this)) {
+				RequestDependentMessageData(
+					this,
+					reply->replyToPeerId,
+					reply->replyToMsgId);
+			}
+		}
+		if ((reply->replyToMsgTop != replyToTop)
+			&& !IsServerMsgId(reply->replyToMsgTop)) {
+			reply->replyToMsgTop = replyToTop;
+			changeReplyToTopCounter(reply, 1);
+		}
+	}
+	if (const auto topic = this->topic()) {
+		topic->maybeSetLastMessage(this);
+	}
+}
+
 void HistoryItem::updateDate(TimeId newDate) {
 	if (canUpdateDate() && _date != newDate) {
 		_date = newDate;
@@ -1183,38 +2321,41 @@ bool HistoryItem::canUpdateDate() const {
 	return isScheduled();
 }
 
-void HistoryItem::applyTTL(const MTPDmessage &data) {
-	if (const auto period = data.vttl_period()) {
-		if (period->v > 0) {
-			applyTTL(data.vdate().v + period->v);
-		}
-	}
-}
-
-void HistoryItem::applyTTL(const MTPDmessageService &data) {
-	if (const auto period = data.vttl_period()) {
-		if (period->v > 0) {
-			applyTTL(data.vdate().v + period->v);
-		}
-	}
-}
-
 void HistoryItem::applyTTL(TimeId destroyAt) {
 	const auto previousDestroyAt = std::exchange(_ttlDestroyAt, destroyAt);
 	if (previousDestroyAt) {
-		history()->owner().unregisterMessageTTL(previousDestroyAt, this);
+		_history->owner().unregisterMessageTTL(previousDestroyAt, this);
 	}
 	if (!_ttlDestroyAt) {
 		return;
 	} else if (base::unixtime::now() >= _ttlDestroyAt) {
-		const auto session = &history()->session();
+		const auto session = &_history->session();
 		crl::on_main(session, [session, id = fullId()]{
 			if (const auto item = session->data().message(id)) {
 				item->destroy();
 			}
 		});
 	} else {
-		history()->owner().registerMessageTTL(_ttlDestroyAt, this);
+		_history->owner().registerMessageTTL(_ttlDestroyAt, this);
+	}
+}
+
+void HistoryItem::replaceBuyWithReceiptInMarkup() {
+	if (const auto markup = inlineReplyMarkup()) {
+		for (auto &row : markup->data.rows) {
+			for (auto &button : row) {
+				if (button.type == HistoryMessageMarkupButton::Type::Buy) {
+					const auto receipt = tr::lng_payments_receipt_button(tr::now);
+					if (button.text != receipt) {
+						button.text = receipt;
+						if (markup->inlineKeyboard) {
+							markup->inlineKeyboard = nullptr;
+							_history->owner().requestItemResize(this);
+						}
+					}
+				}
+			}
+		}
 	}
 }
 
@@ -1226,6 +2367,40 @@ bool HistoryItem::isRegular() const {
 	return isHistoryEntry() && !isLocal();
 }
 
+int HistoryItem::viewsCount() const {
+	if (const auto views = Get<HistoryMessageViews>()) {
+		return std::max(views->views.count, 0);
+	}
+	return hasViews() ? 1 : -1;
+}
+
+int HistoryItem::repliesCount() const {
+	if (const auto views = Get<HistoryMessageViews>()) {
+		if (!checkCommentsLinkedChat(views->commentsMegagroupId)) {
+			return 0;
+		}
+		return std::max(views->replies.count, 0);
+	}
+	return 0;
+}
+
+bool HistoryItem::repliesAreComments() const {
+	if (const auto views = Get<HistoryMessageViews>()) {
+		return (views->commentsMegagroupId != 0)
+			&& checkCommentsLinkedChat(views->commentsMegagroupId);
+	}
+	return false;
+}
+
+bool HistoryItem::externalReply() const {
+	if (!_history->peer->isRepliesChat()) {
+		return false;
+	} else if (const auto forwarded = Get<HistoryMessageForwarded>()) {
+		return forwarded->savedFromPeer && forwarded->savedFromMsgId;
+	}
+	return false;
+}
+
 bool HistoryItem::hasExtendedMediaPreview() const {
 	if (const auto media = _media.get()) {
 		if (const auto invoice = media->invoice()) {
@@ -1241,23 +2416,28 @@ void HistoryItem::sendFailed() {
 
 	_flags = (_flags | MessageFlag::SendingFailed) & ~MessageFlag::BeingSent;
 	_history->owner().notifyItemDataChange(this);
-	history()->session().changes().historyUpdated(
-		history(),
+	_history->session().changes().historyUpdated(
+		_history,
 		Data::HistoryUpdate::Flag::ClientSideMessages);
 }
 
 bool HistoryItem::needCheck() const {
-	return (out() && !isEmpty()) || (!isRegular() && history()->peer->isSelf());
+	return (out() && !isEmpty())
+		|| (!isRegular() && _history->peer->isSelf());
+}
+
+bool HistoryItem::isService() const {
+	return Has<HistoryServiceData>();
 }
 
 bool HistoryItem::unread(not_null<Data::Thread*> thread) const {
 	// Messages from myself are always read, unless scheduled.
-	if (history()->peer->isSelf() && !isFromScheduled()) {
+	if (_history->peer->isSelf() && !isFromScheduled()) {
 		return false;
 	}
 
 	// All messages in converted chats are always read.
-	if (history()->peer->migrateTo()) {
+	if (_history->peer->migrateTo()) {
 		return false;
 	}
 
@@ -1266,11 +2446,11 @@ bool HistoryItem::unread(not_null<Data::Thread*> thread) const {
 			return false;
 		}
 		if (out()) {
-			if (const auto user = history()->peer->asUser()) {
+			if (const auto user = _history->peer->asUser()) {
 				if (user->isBot() && !user->isSupport()) {
 					return false;
 				}
-			} else if (const auto channel = history()->peer->asChannel()) {
+			} else if (const auto channel = _history->peer->asChannel()) {
 				if (!channel->isMegagroup()) {
 					return false;
 				}
@@ -1282,6 +2462,60 @@ bool HistoryItem::unread(not_null<Data::Thread*> thread) const {
 	return out() || (_flags & MessageFlag::ClientSideUnread);
 }
 
+MsgId HistoryItem::replyToId() const {
+	if (const auto reply = Get<HistoryMessageReply>()) {
+		return reply->replyToId();
+	}
+	return 0;
+}
+
+MsgId HistoryItem::replyToTop() const {
+	if (const auto reply = Get<HistoryMessageReply>()) {
+		return reply->replyToTop();
+	} else if (const auto data = GetServiceDependentData()) {
+		return data->topId;
+	}
+	return 0;
+}
+
+MsgId HistoryItem::topicRootId() const {
+	if (const auto reply = Get<HistoryMessageReply>()
+		; reply && reply->topicPost) {
+		return reply->replyToTop();
+	} else if (const auto data = GetServiceDependentData()
+		; data && data->topicPost && data->topId) {
+		return data->topId;
+	} else if (const auto info = Get<HistoryServiceTopicInfo>()) {
+		if (info->created()) {
+			return id;
+		}
+	}
+	return Data::ForumTopic::kGeneralId;
+}
+
+void HistoryItem::setText(const TextWithEntities &textWithEntities) {
+	for (const auto &entity : textWithEntities.entities) {
+		auto type = entity.type();
+		if (type == EntityType::Url
+			|| type == EntityType::CustomUrl
+			|| type == EntityType::Email) {
+			_flags |= MessageFlag::HasTextLinks;
+			break;
+		}
+	}
+	setTextValue((_media && _media->consumeMessageText(textWithEntities))
+		? TextWithEntities()
+		: std::move(textWithEntities));
+}
+
+void HistoryItem::setTextValue(TextWithEntities text) {
+	const auto had = !_text.empty();
+	_text = std::move(text);
+	if (had) {
+		history()->owner().requestItemTextRefresh(this);
+	}
+}
+
 bool HistoryItem::showNotification() const {
 	const auto channel = _history->peer->asChannel();
 	if (channel && !channel->amIn()) {
@@ -1322,12 +2556,20 @@ TextWithEntities HistoryItem::notificationText() const {
 		Ui::kQEllipsis);
 }
 
-const std::vector<ClickHandlerPtr> &HistoryItem::customTextLinks() const {
-	static const auto result = std::vector<ClickHandlerPtr>();
-	return result;
-}
-
 ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
+	if (isService()) {
+		// Don't show small media for service messages (chat photo changed).
+		// Because larger version is shown exactly to the left of the small.
+		//auto media = _media ? _media->toPreview(options) : ItemPreview();
+		return {
+			.text = Ui::Text::Wrapped(
+				notificationText(),
+				EntityType::PlainLink),
+			//.images = std::move(media.images),
+			//.loadingContext = std::move(media.loadingContext),
+		};
+	}
+
 	auto result = [&]() -> ItemPreview {
 		if (_media) {
 			return _media->toPreview(options);
@@ -1375,178 +2617,1819 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
 }
 
 TextWithEntities HistoryItem::inReplyText() const {
-	return toPreview({
-		.hideSender = true,
-		.generateImages = false,
-	}).text;
-}
-
-HistoryItem::~HistoryItem() {
-	applyTTL(0);
-}
-
-Main::Session *SessionByUniqueId(uint64 sessionUniqueId) {
-	if (!sessionUniqueId) {
-		return nullptr;
+	if (!isService()) {
+		return toPreview({
+			.hideSender = true,
+			.generateImages = false,
+		}).text;
 	}
-	for (const auto &[index, account] : Core::App().domain().accounts()) {
-		if (const auto session = account->maybeSession()) {
-			if (session->uniqueId() == sessionUniqueId) {
-				return session;
+	auto result = notificationText();
+	const auto &name = author()->name();
+	TextUtilities::Trim(result);
+	if (result.text.startsWith(name)) {
+		result = Ui::Text::Mid(result, name.size());
+		TextUtilities::Trim(result);
+	}
+	return Ui::Text::Wrapped(result, EntityType::PlainLink);
+}
+
+const std::vector<ClickHandlerPtr> &HistoryItem::customTextLinks() const {
+	static const auto kEmpty = std::vector<ClickHandlerPtr>();
+	const auto service = Get<HistoryServiceData>();
+	return service ? service->textLinks : kEmpty;
+}
+
+void HistoryItem::createComponents(CreateConfig &&config) {
+	uint64 mask = 0;
+	if (config.replyTo) {
+		mask |= HistoryMessageReply::Bit();
+	}
+	if (config.viaBotId) {
+		mask |= HistoryMessageVia::Bit();
+	}
+	if (config.viewsCount >= 0 || !config.replies.isNull) {
+		mask |= HistoryMessageViews::Bit();
+	}
+	if (!config.author.isEmpty()) {
+		mask |= HistoryMessageSigned::Bit();
+	} else if (_history->peer->isMegagroup() // Discussion posts signatures.
+		&& config.savedFromPeer
+		&& !config.authorOriginal.isEmpty()) {
+		const auto savedFrom = _history->owner().peerLoaded(
+			config.savedFromPeer);
+		if (savedFrom && savedFrom->isChannel()) {
+			mask |= HistoryMessageSigned::Bit();
+		}
+	} else if ((_history->peer->isSelf() || _history->peer->isRepliesChat())
+		&& !config.authorOriginal.isEmpty()) {
+		mask |= HistoryMessageSigned::Bit();
+	}
+	if (config.editDate != TimeId(0)) {
+		mask |= HistoryMessageEdited::Bit();
+	}
+	if (config.originalDate != 0) {
+		mask |= HistoryMessageForwarded::Bit();
+	}
+	if (!config.markup.isTrivial()) {
+		mask |= HistoryMessageReplyMarkup::Bit();
+	} else if (config.inlineMarkup) {
+		mask |= HistoryMessageReplyMarkup::Bit();
+	}
+
+	UpdateComponents(mask);
+
+	if (const auto reply = Get<HistoryMessageReply>()) {
+		reply->replyToPeerId = config.replyToPeer;
+		reply->replyToMsgId = config.replyTo;
+		reply->replyToMsgTop = isScheduled() ? 0 : config.replyToTop;
+		reply->topicPost = config.replyIsTopicPost;
+		if (!reply->updateData(this)) {
+			RequestDependentMessageData(
+				this,
+				reply->replyToPeerId,
+				reply->replyToMsgId);
+		}
+	}
+	if (const auto via = Get<HistoryMessageVia>()) {
+		via->create(&_history->owner(), config.viaBotId);
+	}
+	if (const auto views = Get<HistoryMessageViews>()) {
+		changeViewsCount(config.viewsCount);
+		if (config.replies.isNull
+			&& isSending()
+			&& config.markup.isNull()) {
+			if (const auto broadcast = _history->peer->asBroadcast()) {
+				if (const auto linked = broadcast->linkedChat()) {
+					config.replies.isNull = false;
+					config.replies.channelId = peerToChannel(linked->id);
+				}
+			}
+		}
+		setReplies(std::move(config.replies));
+	}
+	if (const auto edited = Get<HistoryMessageEdited>()) {
+		edited->date = config.editDate;
+	}
+	if (const auto msgsigned = Get<HistoryMessageSigned>()) {
+		msgsigned->author = config.author.isEmpty()
+			? config.authorOriginal
+			: config.author;
+		msgsigned->isAnonymousRank = !isDiscussionPost()
+			&& author()->isMegagroup();
+	}
+	setupForwardedComponent(config);
+	if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
+		if (!config.markup.isTrivial()) {
+			markup->updateData(std::move(config.markup));
+		} else if (config.inlineMarkup) {
+			markup->createForwarded(*config.inlineMarkup);
+		}
+		if (markup->data.flags & ReplyMarkupFlag::HasSwitchInlineButton) {
+			_flags |= MessageFlag::HasSwitchInlineButton;
+		}
+	} else if (!config.markup.isNull()) {
+		_flags |= MessageFlag::HasReplyMarkup;
+	} else {
+		_flags &= ~MessageFlag::HasReplyMarkup;
+	}
+}
+
+bool HistoryItem::checkRepliesPts(
+		const HistoryMessageRepliesData &data) const {
+	const auto channel = _history->peer->asChannel();
+	const auto pts = channel
+		? channel->pts()
+		: _history->session().updates().pts();
+	return (data.pts >= pts);
+}
+
+void HistoryItem::setupForwardedComponent(const CreateConfig &config) {
+	const auto forwarded = Get<HistoryMessageForwarded>();
+	if (!forwarded) {
+		return;
+	}
+	forwarded->originalDate = config.originalDate;
+	const auto originalSender = config.senderOriginal
+		? config.senderOriginal
+		: !config.senderNameOriginal.isEmpty()
+		? PeerId()
+		: from()->id;
+	forwarded->originalSender = originalSender
+		? _history->owner().peer(originalSender).get()
+		: nullptr;
+	if (!forwarded->originalSender) {
+		forwarded->hiddenSenderInfo = std::make_unique<HiddenSenderInfo>(
+			config.senderNameOriginal,
+			config.imported);
+	}
+	forwarded->originalId = config.originalId;
+	forwarded->originalAuthor = config.authorOriginal;
+	forwarded->psaType = config.forwardPsaType;
+	forwarded->savedFromPeer = _history->owner().peerLoaded(
+		config.savedFromPeer);
+	forwarded->savedFromMsgId = config.savedFromMsgId;
+	forwarded->imported = config.imported;
+}
+
+bool HistoryItem::generateLocalEntitiesByReply() const {
+	using namespace HistoryView;
+	if (!_media) {
+		return true;
+	} else if (const auto document = _media->document()) {
+		return !DurationForTimestampLinks(document);
+	} else if (const auto webpage = _media->webpage()) {
+		return (webpage->type != WebPageType::Video)
+			&& !DurationForTimestampLinks(webpage);
+	}
+	return true;
+}
+
+TextWithEntities HistoryItem::withLocalEntities(
+		const TextWithEntities &textWithEntities) const {
+	using namespace HistoryView;
+	if (!generateLocalEntitiesByReply()) {
+		if (!_media) {
+		} else if (const auto document = _media->document()) {
+			if (const auto duration = DurationForTimestampLinks(document)) {
+				return AddTimestampLinks(
+					textWithEntities,
+					duration,
+					TimestampLinkBase(document, fullId()));
+			}
+		} else if (const auto webpage = _media->webpage()) {
+			if (const auto duration = DurationForTimestampLinks(webpage)) {
+				return AddTimestampLinks(
+					textWithEntities,
+					duration,
+					TimestampLinkBase(webpage, fullId()));
+			}
+		}
+		return textWithEntities;
+	}
+	if (const auto reply = Get<HistoryMessageReply>()) {
+		const auto document = reply->replyToDocumentId
+			? _history->owner().document(reply->replyToDocumentId).get()
+			: nullptr;
+		const auto webpage = reply->replyToWebPageId
+			? _history->owner().webpage(reply->replyToWebPageId).get()
+			: nullptr;
+		if (document) {
+			if (const auto duration = DurationForTimestampLinks(document)) {
+				const auto context = reply->replyToMsg->fullId();
+				return AddTimestampLinks(
+					textWithEntities,
+					duration,
+					TimestampLinkBase(document, context));
+			}
+		} else if (webpage) {
+			if (const auto duration = DurationForTimestampLinks(webpage)) {
+				const auto context = reply->replyToMsg->fullId();
+				return AddTimestampLinks(
+					textWithEntities,
+					duration,
+					TimestampLinkBase(webpage, context));
 			}
 		}
 	}
-	return nullptr;
+	return textWithEntities;
 }
 
-HistoryItem *MessageByGlobalId(GlobalMsgId globalId) {
-	const auto sessionId = globalId.itemId ? globalId.sessionUniqueId : 0;
-	if (const auto session = SessionByUniqueId(sessionId)) {
-		return session->data().message(globalId.itemId);
+void HistoryItem::setSponsoredFrom(const Data::SponsoredFrom &from) {
+	AddComponents(HistoryMessageSponsored::Bit());
+	const auto sponsored = Get<HistoryMessageSponsored>();
+	sponsored->sender = std::make_unique<HiddenSenderInfo>(
+		from.title,
+		false);
+	sponsored->recommended = from.isRecommended;
+	sponsored->isForceUserpicDisplay = from.isForceUserpicDisplay;
+	if (from.userpic.location.valid()) {
+		sponsored->sender->customUserpic.set(
+			&_history->session(),
+			from.userpic);
 	}
-	return nullptr;
+
+	using Type = HistoryMessageSponsored::Type;
+	sponsored->type = from.isExactPost
+		? Type::Post
+		: from.isBot
+		? Type::Bot
+		: from.isBroadcast
+		? Type::Broadcast
+		: (from.peer && from.peer->isUser())
+		? Type::User
+		: Type::Group;
 }
 
-QDateTime ItemDateTime(not_null<const HistoryItem*> item) {
-	return base::unixtime::parse(item->date());
+void HistoryItem::createComponentsHelper(
+		MessageFlags flags,
+		MsgId replyTo,
+		UserId viaBotId,
+		const QString &postAuthor,
+		HistoryMessageMarkupData &&markup) {
+	auto config = CreateConfig();
+	config.viaBotId = viaBotId;
+	if (flags & MessageFlag::HasReplyInfo) {
+		config.replyTo = replyTo;
+		const auto to = LookupReplyTo(_history, replyTo);
+		const auto replyToTop = LookupReplyToTop(to);
+		config.replyToTop = replyToTop ? replyToTop : replyTo;
+		const auto forum = _history->asForum();
+		config.replyIsTopicPost = LookupReplyIsTopicPost(to)
+			|| (to && to->Has<HistoryServiceTopicInfo>())
+			|| (forum && forum->creating(config.replyToTop));
+	}
+	config.markup = std::move(markup);
+	if (flags & MessageFlag::HasPostAuthor) config.author = postAuthor;
+	if (flags & MessageFlag::HasViews) config.viewsCount = 1;
+
+	createComponents(std::move(config));
 }
 
-QString ItemDateText(not_null<const HistoryItem*> item, bool isUntilOnline) {
-	const auto dateText = langDayOfMonthFull(ItemDateTime(item).date());
-	return !item->isScheduled()
-		? dateText
-		: isUntilOnline
-			? tr::lng_scheduled_date_until_online(tr::now)
-			: tr::lng_scheduled_date(tr::now, lt_date, dateText);
+void HistoryItem::setReactions(const MTPMessageReactions *reactions) {
+	Expects(!_reactions);
+
+	if (changeReactions(reactions) && _reactions->hasUnread()) {
+		_flags |= MessageFlag::HasUnreadReaction;
+	}
 }
 
-bool IsItemScheduledUntilOnline(not_null<const HistoryItem*> item) {
-	return item->isScheduled()
-		&& (item->date() ==
-			Data::ScheduledMessages::kScheduledUntilOnlineTimestamp);
+void HistoryItem::updateReactions(const MTPMessageReactions *reactions) {
+	const auto wasRecentUsers = LookupRecentUnreadReactedUsers(this);
+	const auto hadUnread = hasUnreadReaction();
+	const auto changed = changeReactions(reactions);
+	if (!changed) {
+		return;
+	}
+	const auto hasUnread = _reactions && _reactions->hasUnread();
+	if (hasUnread && !hadUnread) {
+		_flags |= MessageFlag::HasUnreadReaction;
+
+		addToUnreadThings(HistoryUnreadThings::AddType::New);
+	} else if (!hasUnread && hadUnread) {
+		markReactionsRead();
+	}
+	CheckReactionNotificationSchedule(this, wasRecentUsers);
+	_history->owner().notifyItemDataChange(this);
 }
 
-ClickHandlerPtr goToMessageClickHandler(
-		not_null<HistoryItem*> item,
-		FullMsgId returnToId) {
-	return goToMessageClickHandler(
-		item->history()->peer,
-		item->id,
-		returnToId);
+bool HistoryItem::changeReactions(const MTPMessageReactions *reactions) {
+	if (reactions || _reactionsLastRefreshed) {
+		_reactionsLastRefreshed = crl::now();
+	}
+	if (!reactions) {
+		_flags &= ~MessageFlag::CanViewReactions;
+		return (base::take(_reactions) != nullptr);
+	}
+	return reactions->match([&](const MTPDmessageReactions &data) {
+		if (data.is_can_see_list()) {
+			_flags |= MessageFlag::CanViewReactions;
+		} else {
+			_flags &= ~MessageFlag::CanViewReactions;
+		}
+		if (data.vresults().v.isEmpty()) {
+			return (base::take(_reactions) != nullptr);
+		} else if (!_reactions) {
+			_reactions = std::make_unique<Data::MessageReactions>(this);
+		}
+		const auto min = data.is_min();
+		const auto &list = data.vresults().v;
+		const auto &recent = data.vrecent_reactions().value_or_empty();
+		if (min && hasUnreadReaction()) {
+			// We can't update reactions from min if we have unread.
+			if (_reactions->checkIfChanged(list, recent)) {
+				updateReactionsUnknown();
+			}
+			return false;
+		}
+		return _reactions->change(list, recent, min);
+	});
 }
 
-ClickHandlerPtr goToMessageClickHandler(
-		not_null<PeerData*> peer,
-		MsgId msgId,
-		FullMsgId returnToId) {
-	return std::make_shared<LambdaClickHandler>([=] {
-		const auto separate = Core::App().separateWindowForPeer(peer);
-		const auto controller = separate
-			? separate->sessionController()
-			: peer->session().tryResolveWindow();
-		if (controller) {
-			auto params = Window::SectionShow{
-				Window::SectionShow::Way::Forward
-			};
-			params.origin = Window::SectionShow::OriginMessage{
-				returnToId
-			};
-			if (const auto item = peer->owner().message(peer, msgId)) {
-				controller->showMessage(item, params);
+void HistoryItem::applyTTL(const MTPDmessage &data) {
+	if (const auto period = data.vttl_period()) {
+		if (period->v > 0) {
+			applyTTL(data.vdate().v + period->v);
+		}
+	}
+}
+
+void HistoryItem::applyTTL(const MTPDmessageService &data) {
+	if (const auto period = data.vttl_period()) {
+		if (period->v > 0) {
+			applyTTL(data.vdate().v + period->v);
+		}
+	}
+}
+
+void HistoryItem::createComponents(const MTPDmessage &data) {
+	auto config = CreateConfig();
+	if (const auto forwarded = data.vfwd_from()) {
+		forwarded->match([&](const MTPDmessageFwdHeader &data) {
+			FillForwardedInfo(config, data);
+		});
+	}
+	if (const auto reply = data.vreply_to()) {
+		reply->match([&](const MTPDmessageReplyHeader &data) {
+			if (const auto peer = data.vreply_to_peer_id()) {
+				config.replyToPeer = peerFromMTP(*peer);
+				if (config.replyToPeer == _history->peer->id) {
+					config.replyToPeer = 0;
+				}
+			}
+		const auto id = data.vreply_to_msg_id().v;
+		config.replyTo = data.is_reply_to_scheduled()
+			? _history->owner().scheduledMessages().localMessageId(id)
+			: id;
+		config.replyToTop = data.vreply_to_top_id().value_or(id);
+		config.replyIsTopicPost = data.is_forum_topic();
+		});
+	}
+	config.viaBotId = data.vvia_bot_id().value_or_empty();
+	config.viewsCount = data.vviews().value_or(-1);
+	config.replies = isScheduled()
+		? HistoryMessageRepliesData()
+		: HistoryMessageRepliesData(data.vreplies());
+	config.markup = HistoryMessageMarkupData(data.vreply_markup());
+	config.editDate = data.vedit_date().value_or_empty();
+	config.author = qs(data.vpost_author().value_or_empty());
+	createComponents(std::move(config));
+}
+
+void HistoryItem::refreshMedia(const MTPMessageMedia *media) {
+	const auto was = (_media != nullptr);
+	if (const auto invoice = was ? _media->invoice() : nullptr) {
+		if (invoice->extendedMedia) {
+			return;
+		}
+	}
+	_media = nullptr;
+	if (media) {
+		setMedia(*media);
+	}
+	if (was || _media) {
+		if (const auto views = Get<HistoryMessageViews>()) {
+			refreshRepliesText(views);
+		}
+	}
+}
+
+void HistoryItem::refreshSentMedia(const MTPMessageMedia *media) {
+	const auto wasGrouped = history()->owner().groups().isGrouped(this);
+	refreshMedia(media);
+	if (wasGrouped) {
+		history()->owner().groups().refreshMessage(this);
+	} else {
+		history()->owner().requestItemViewRefresh(this);
+	}
+}
+
+void HistoryItem::createServiceFromMtp(const MTPDmessage &message) {
+	AddComponents(HistoryServiceData::Bit());
+
+	const auto media = message.vmedia();
+	Assert(media != nullptr);
+
+	const auto mediaType = media->type();
+	switch (mediaType) {
+	case mtpc_messageMediaPhoto: {
+		if (message.is_media_unread()) {
+			const auto &photo = media->c_messageMediaPhoto();
+			const auto ttl = photo.vttl_seconds();
+			Assert(ttl != nullptr);
+
+			setSelfDestruct(HistoryServiceSelfDestruct::Type::Photo, ttl->v);
+			if (out()) {
+				setServiceText({
+					tr::lng_ttl_photo_sent(tr::now, Ui::Text::WithEntities)
+				});
 			} else {
-				controller->showPeerHistory(peer, params, msgId);
+				auto result = PreparedServiceText();
+				result.links.push_back(fromLink());
+				result.text = tr::lng_ttl_photo_received(
+					tr::now,
+					lt_from,
+					fromLinkText(), // Link 1.
+					Ui::Text::WithEntities);
+				setServiceText(std::move(result));
+			}
+		} else {
+			setServiceText({
+				tr::lng_ttl_photo_expired(tr::now, Ui::Text::WithEntities)
+			});
+		}
+	} break;
+	case mtpc_messageMediaDocument: {
+		if (message.is_media_unread()) {
+			const auto &document = media->c_messageMediaDocument();
+			const auto ttl = document.vttl_seconds();
+			Assert(ttl != nullptr);
+
+			setSelfDestruct(HistoryServiceSelfDestruct::Type::Video, ttl->v);
+			if (out()) {
+				setServiceText({
+					tr::lng_ttl_video_sent(tr::now, Ui::Text::WithEntities)
+				});
+			} else {
+				auto result = PreparedServiceText();
+				result.links.push_back(fromLink());
+				result.text = tr::lng_ttl_video_received(
+					tr::now,
+					lt_from,
+					fromLinkText(), // Link 1.
+					Ui::Text::WithEntities);
+				setServiceText(std::move(result));
+			}
+		} else {
+			setServiceText({
+				tr::lng_ttl_video_expired(tr::now, Ui::Text::WithEntities)
+			});
+		}
+	} break;
+
+	default: Unexpected("Media type in HistoryItem::createServiceFromMtp()");
+	}
+
+	if (const auto reactions = message.vreactions()) {
+		updateReactions(reactions);
+	}
+}
+
+void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) {
+	AddComponents(HistoryServiceData::Bit());
+
+	const auto &action = message.vaction();
+	const auto type = action.type();
+	if (type == mtpc_messageActionPinMessage) {
+		UpdateComponents(HistoryServicePinned::Bit());
+	} else if (type == mtpc_messageActionTopicCreate
+		|| type == mtpc_messageActionTopicEdit) {
+		UpdateComponents(HistoryServiceTopicInfo::Bit());
+		const auto info = Get<HistoryServiceTopicInfo>();
+		info->topicPost = true;
+		if (type == mtpc_messageActionTopicEdit) {
+			const auto &data = action.c_messageActionTopicEdit();
+			if (const auto title = data.vtitle()) {
+				info->title = qs(*title);
+				info->renamed = true;
+			}
+			if (const auto icon = data.vicon_emoji_id()) {
+				info->iconId = icon->v;
+				info->reiconed = true;
+			}
+			if (const auto closed = data.vclosed()) {
+				info->closed = mtpIsTrue(*closed);
+				info->reopened = !info->closed;
+			}
+			if (const auto hidden = data.vhidden()) {
+				info->hidden = mtpIsTrue(*hidden);
+				info->unhidden = !info->hidden;
+			}
+		} else {
+			const auto &data = action.c_messageActionTopicCreate();
+			info->title = qs(data.vtitle());
+			info->iconId = data.vicon_emoji_id().value_or_empty();
+		}
+	} else if (type == mtpc_messageActionSetChatTheme) {
+		setupChatThemeChange();
+	} else if (type == mtpc_messageActionSetMessagesTTL) {
+		setupTTLChange();
+	} else if (type == mtpc_messageActionGameScore) {
+		const auto &data = action.c_messageActionGameScore();
+		UpdateComponents(HistoryServiceGameScore::Bit());
+		Get<HistoryServiceGameScore>()->score = data.vscore().v;
+	} else if (type == mtpc_messageActionPaymentSent) {
+		const auto &data = action.c_messageActionPaymentSent();
+		UpdateComponents(HistoryServicePayment::Bit());
+		const auto amount = data.vtotal_amount().v;
+		const auto currency = qs(data.vcurrency());
+		const auto payment = Get<HistoryServicePayment>();
+		const auto id = fullId();
+		const auto owner = &_history->owner();
+		payment->slug = data.vinvoice_slug().value_or_empty();
+		payment->recurringInit = data.is_recurring_init();
+		payment->recurringUsed = data.is_recurring_used();
+		payment->amount = Ui::FillAmountAndCurrency(amount, currency);
+		payment->invoiceLink = std::make_shared<LambdaClickHandler>([=](
+				ClickContext context) {
+			using namespace Payments;
+			const auto my = context.other.value<ClickHandlerContext>();
+			const auto weak = my.sessionWindow;
+			if (const auto item = owner->message(id)) {
+				CheckoutProcess::Start(
+					item,
+					Mode::Receipt,
+					crl::guard(weak, [=](auto) { weak->window().activate(); }));
+			}
+		});
+	} else if (type == mtpc_messageActionGroupCall
+		|| type == mtpc_messageActionGroupCallScheduled) {
+		const auto started = (type == mtpc_messageActionGroupCall);
+		const auto &callData = started
+			? action.c_messageActionGroupCall().vcall()
+			: action.c_messageActionGroupCallScheduled().vcall();
+		const auto duration = started
+			? action.c_messageActionGroupCall().vduration()
+			: tl::conditional<MTPint>();
+		if (duration) {
+			RemoveComponents(HistoryServiceOngoingCall::Bit());
+		} else {
+			UpdateComponents(HistoryServiceOngoingCall::Bit());
+			const auto call = Get<HistoryServiceOngoingCall>();
+			call->id = CallIdFromInput(callData);
+			call->link = GroupCallClickHandler(_history->peer, call->id);
+		}
+	} else if (type == mtpc_messageActionInviteToGroupCall) {
+		const auto &data = action.c_messageActionInviteToGroupCall();
+		const auto id = CallIdFromInput(data.vcall());
+		const auto peer = _history->peer;
+		const auto has = PeerHasThisCall(peer, id);
+		auto hasLink = !has.has_value()
+			? PeerHasThisCallValue(peer, id)
+			: (*has)
+			? PeerHasThisCallValue(
+				peer,
+				id) | rpl::skip(1) | rpl::type_erased()
+			: rpl::producer<bool>();
+		if (!hasLink) {
+			RemoveComponents(HistoryServiceOngoingCall::Bit());
+		} else {
+			UpdateComponents(HistoryServiceOngoingCall::Bit());
+			const auto call = Get<HistoryServiceOngoingCall>();
+			call->id = id;
+			call->lifetime.destroy();
+
+			const auto users = data.vusers().v;
+			std::move(hasLink) | rpl::start_with_next([=](bool has) {
+				updateServiceText(
+					prepareInvitedToCallText(
+						ParseInvitedToCallUsers(this, users),
+						has ? id : 0));
+				if (!has) {
+					RemoveComponents(HistoryServiceOngoingCall::Bit());
+				}
+			}, call->lifetime);
+		}
+	}
+	if (const auto replyTo = message.vreply_to()) {
+		replyTo->match([&](const MTPDmessageReplyHeader &data) {
+			const auto peerId = data.vreply_to_peer_id()
+				? peerFromMTP(*data.vreply_to_peer_id())
+				: _history->peer->id;
+			if (const auto dependent = GetServiceDependentData()) {
+				dependent->peerId = (peerId != _history->peer->id)
+					? peerId
+					: 0;
+				const auto id = data.vreply_to_msg_id().v;
+				dependent->msgId = id;
+				dependent->topId = data.vreply_to_top_id().value_or(id);
+				dependent->topicPost = data.is_forum_topic()
+					|| Has<HistoryServiceTopicInfo>();
+				if (!updateServiceDependent()) {
+					RequestDependentMessageData(
+						this,
+						dependent->peerId,
+						dependent->msgId);
+				}
+			}
+		});
+	}
+	setServiceMessageByAction(action);
+}
+
+void HistoryItem::setMedia(const MTPMessageMedia &media) {
+	_media = CreateMedia(this, media);
+	checkBuyButton();
+}
+
+void HistoryItem::applyServiceDateEdition(const MTPDmessageService &data) {
+	const auto date = data.vdate().v;
+	if (_date == date) {
+		return;
+	}
+	_date = date;
+}
+
+void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
+	auto prepareChatAddUserText = [this](const MTPDmessageActionChatAddUser &action) {
+		auto result = PreparedServiceText();
+		auto &users = action.vusers().v;
+		if (users.size() == 1) {
+			auto u = _history->owner().user(users[0].v);
+			if (u == _from) {
+				result.links.push_back(fromLink());
+				result.text = tr::lng_action_user_joined(
+					tr::now,
+					lt_from,
+					fromLinkText(), // Link 1.
+					Ui::Text::WithEntities);
+			} else {
+				result.links.push_back(fromLink());
+				result.links.push_back(u->createOpenLink());
+				result.text = tr::lng_action_add_user(
+					tr::now,
+					lt_from,
+					fromLinkText(), // Link 1.
+					lt_user,
+					Ui::Text::Link(u->name(), 2), // Link 2.
+					Ui::Text::WithEntities);
+			}
+		} else if (users.isEmpty()) {
+			result.links.push_back(fromLink());
+			result.text = tr::lng_action_add_user(
+				tr::now,
+				lt_from,
+				fromLinkText(), // Link 1.
+				lt_user,
+				{ .text = u"somebody"_q },
+				Ui::Text::WithEntities);
+		} else {
+			result.links.push_back(fromLink());
+			for (auto i = 0, l = int(users.size()); i != l; ++i) {
+				auto user = _history->owner().user(users[i].v);
+				result.links.push_back(user->createOpenLink());
+
+				auto linkText = Ui::Text::Link(user->name(), 2 + i);
+				if (i == 0) {
+					result.text = linkText;
+				} else if (i + 1 == l) {
+					result.text = tr::lng_action_add_users_and_last(
+						tr::now,
+						lt_accumulated,
+						result.text,
+						lt_user,
+						linkText,
+						Ui::Text::WithEntities);
+				} else {
+					result.text = tr::lng_action_add_users_and_one(
+						tr::now,
+						lt_accumulated,
+						result.text,
+						lt_user,
+						linkText,
+						Ui::Text::WithEntities);
+				}
+			}
+			result.text = tr::lng_action_add_users_many(
+				tr::now,
+				lt_from,
+				fromLinkText(), // Link 1.
+				lt_users,
+				result.text,
+				Ui::Text::WithEntities);
+		}
+		return result;
+	};
+
+	auto prepareChatJoinedByLink = [this](const MTPDmessageActionChatJoinedByLink &action) {
+		auto result = PreparedServiceText();
+		result.links.push_back(fromLink());
+		result.text = tr::lng_action_user_joined_by_link(
+			tr::now,
+			lt_from,
+			fromLinkText(), // Link 1.
+			Ui::Text::WithEntities);
+		return result;
+	};
+
+	auto prepareChatCreate = [this](const MTPDmessageActionChatCreate &action) {
+		auto result = PreparedServiceText();
+		result.links.push_back(fromLink());
+		result.text = tr::lng_action_created_chat(
+			tr::now,
+			lt_from,
+			fromLinkText(), // Link 1.
+			lt_title,
+			{ .text = qs(action.vtitle()) },
+			Ui::Text::WithEntities);
+		return result;
+	};
+
+	auto prepareChannelCreate = [this](const MTPDmessageActionChannelCreate &action) {
+		auto result = PreparedServiceText();
+		if (isPost()) {
+			result.text = tr::lng_action_created_channel(
+				tr::now,
+				Ui::Text::WithEntities);
+		} else {
+			result.links.push_back(fromLink());
+			result.text = tr::lng_action_created_chat(
+				tr::now,
+				lt_from,
+				fromLinkText(), // Link 1.
+				lt_title,
+				{ .text = qs(action.vtitle()) },
+				Ui::Text::WithEntities);
+		}
+		return result;
+	};
+
+	auto prepareChatDeletePhoto = [this] {
+		auto result = PreparedServiceText();
+		if (isPost()) {
+			result.text = tr::lng_action_removed_photo_channel(
+				tr::now,
+				Ui::Text::WithEntities);
+		} else {
+			result.links.push_back(fromLink());
+			result.text = tr::lng_action_removed_photo(
+				tr::now,
+				lt_from,
+				fromLinkText(), // Link 1.
+				Ui::Text::WithEntities);
+		}
+		return result;
+	};
+
+	auto prepareChatDeleteUser = [this](const MTPDmessageActionChatDeleteUser &action) {
+		auto result = PreparedServiceText();
+		if (peerFromUser(action.vuser_id()) == _from->id) {
+			result.links.push_back(fromLink());
+			result.text = tr::lng_action_user_left(
+				tr::now,
+				lt_from,
+				fromLinkText(), // Link 1.
+				Ui::Text::WithEntities);
+		} else {
+			auto user = _history->owner().user(action.vuser_id().v);
+			result.links.push_back(fromLink());
+			result.links.push_back(user->createOpenLink());
+			result.text = tr::lng_action_kick_user(
+				tr::now,
+				lt_from,
+				fromLinkText(), // Link 1.
+				lt_user,
+				Ui::Text::Link(user->name(), 2),  // Link 2.
+				Ui::Text::WithEntities);
+		}
+		return result;
+	};
+
+	auto prepareChatEditPhoto = [this](const MTPDmessageActionChatEditPhoto &action) {
+		auto result = PreparedServiceText();
+		if (isPost()) {
+			result.text = tr::lng_action_changed_photo_channel(
+				tr::now,
+				Ui::Text::WithEntities);
+		} else {
+			result.links.push_back(fromLink());
+			result.text = tr::lng_action_changed_photo(
+				tr::now,
+				lt_from,
+				fromLinkText(), // Link 1.
+				Ui::Text::WithEntities);
+		}
+		return result;
+	};
+
+	auto prepareChatEditTitle = [this](const MTPDmessageActionChatEditTitle &action) {
+		auto result = PreparedServiceText();
+		if (isPost()) {
+			result.text = tr::lng_action_changed_title_channel(
+				tr::now,
+				lt_title,
+				{ .text = (qs(action.vtitle())) },
+				Ui::Text::WithEntities);
+		} else {
+			result.links.push_back(fromLink());
+			result.text = tr::lng_action_changed_title(
+				tr::now,
+				lt_from,
+				fromLinkText(), // Link 1.
+				lt_title,
+				{ .text = qs(action.vtitle()) },
+				Ui::Text::WithEntities);
+		}
+		return result;
+	};
+
+	auto prepareScreenshotTaken = [this] {
+		auto result = PreparedServiceText();
+		if (out()) {
+			result.text = tr::lng_action_you_took_screenshot(
+				tr::now,
+				Ui::Text::WithEntities);
+		} else {
+			result.links.push_back(fromLink());
+			result.text = tr::lng_action_took_screenshot(
+				tr::now,
+				lt_from,
+				fromLinkText(), // Link 1.
+				Ui::Text::WithEntities);
+		}
+		return result;
+	};
+
+	auto prepareCustomAction = [&](const MTPDmessageActionCustomAction &action) {
+		auto result = PreparedServiceText();
+		result.text = { .text = qs(action.vmessage()) };
+		return result;
+	};
+
+	auto prepareBotAllowed = [&](const MTPDmessageActionBotAllowed &action) {
+		auto result = PreparedServiceText();
+		const auto domain = qs(action.vdomain());
+		result.text = tr::lng_action_bot_allowed_from_domain(
+			tr::now,
+			lt_domain,
+			Ui::Text::Link(domain, u"http://"_q + domain),
+			Ui::Text::WithEntities);
+		return result;
+	};
+
+	auto prepareSecureValuesSent = [&](const MTPDmessageActionSecureValuesSent &action) {
+		auto result = PreparedServiceText();
+		auto documents = QStringList();
+		for (const auto &type : action.vtypes().v) {
+			documents.push_back([&] {
+				switch (type.type()) {
+				case mtpc_secureValueTypePersonalDetails:
+					return tr::lng_action_secure_personal_details(tr::now);
+				case mtpc_secureValueTypePassport:
+				case mtpc_secureValueTypeDriverLicense:
+				case mtpc_secureValueTypeIdentityCard:
+				case mtpc_secureValueTypeInternalPassport:
+					return tr::lng_action_secure_proof_of_identity(tr::now);
+				case mtpc_secureValueTypeAddress:
+					return tr::lng_action_secure_address(tr::now);
+				case mtpc_secureValueTypeUtilityBill:
+				case mtpc_secureValueTypeBankStatement:
+				case mtpc_secureValueTypeRentalAgreement:
+				case mtpc_secureValueTypePassportRegistration:
+				case mtpc_secureValueTypeTemporaryRegistration:
+					return tr::lng_action_secure_proof_of_address(tr::now);
+				case mtpc_secureValueTypePhone:
+					return tr::lng_action_secure_phone(tr::now);
+				case mtpc_secureValueTypeEmail:
+					return tr::lng_action_secure_email(tr::now);
+				}
+				Unexpected("Type in prepareSecureValuesSent.");
+			}());
+		};
+		result.links.push_back(_history->peer->createOpenLink());
+		result.text = tr::lng_action_secure_values_sent(
+			tr::now,
+			lt_user,
+			Ui::Text::Link(_history->peer->name(), QString()), // Link 1.
+			lt_documents,
+			{ .text = documents.join(", ") },
+			Ui::Text::WithEntities);
+		return result;
+	};
+
+	auto prepareContactSignUp = [this] {
+		auto result = PreparedServiceText();
+		result.links.push_back(fromLink());
+		result.text = tr::lng_action_user_registered(
+			tr::now,
+			lt_from,
+			fromLinkText(), // Link 1.
+			Ui::Text::WithEntities);
+		return result;
+	};
+
+	auto prepareProximityReached = [this](const MTPDmessageActionGeoProximityReached &action) {
+		auto result = PreparedServiceText();
+		const auto fromId = peerFromMTP(action.vfrom_id());
+		const auto fromPeer = _history->owner().peer(fromId);
+		const auto toId = peerFromMTP(action.vto_id());
+		const auto toPeer = _history->owner().peer(toId);
+		const auto selfId = _from->session().userPeerId();
+		const auto distanceMeters = action.vdistance().v;
+		const auto distance = [&] {
+			if (distanceMeters >= 1000) {
+				const auto km = (10 * (distanceMeters / 10)) / 1000.;
+				return tr::lng_action_proximity_distance_km(
+					tr::now,
+					lt_count,
+					km);
+			} else {
+				return tr::lng_action_proximity_distance_m(
+					tr::now,
+					lt_count,
+					distanceMeters);
+			}
+		}();
+		result.text = [&] {
+			if (fromId == selfId) {
+				result.links.push_back(toPeer->createOpenLink());
+				return tr::lng_action_you_proximity_reached(
+					tr::now,
+					lt_distance,
+					{ .text = distance },
+					lt_user,
+					Ui::Text::Link(toPeer->name(), QString()), // Link 1.
+					Ui::Text::WithEntities);
+			} else if (toId == selfId) {
+				result.links.push_back(fromPeer->createOpenLink());
+				return tr::lng_action_proximity_reached_you(
+					tr::now,
+					lt_from,
+					Ui::Text::Link(fromPeer->name(), QString()), // Link 1.
+					lt_distance,
+					{ .text = distance },
+					Ui::Text::WithEntities);
+			} else {
+				result.links.push_back(fromPeer->createOpenLink());
+				result.links.push_back(toPeer->createOpenLink());
+				return tr::lng_action_proximity_reached(
+					tr::now,
+					lt_from,
+					Ui::Text::Link(fromPeer->name(), 1), // Link 1.
+					lt_distance,
+					{ .text = distance },
+					lt_user,
+					Ui::Text::Link(toPeer->name(), 2), // Link 2.
+					Ui::Text::WithEntities);
+			}
+		}();
+		return result;
+	};
+
+	auto prepareGroupCall = [this](const MTPDmessageActionGroupCall &action) {
+		auto result = PreparedServiceText();
+		if (const auto duration = action.vduration()) {
+			const auto seconds = duration->v;
+			const auto days = seconds / 86400;
+			const auto hours = seconds / 3600;
+			const auto minutes = seconds / 60;
+			auto text = (days > 1)
+				? tr::lng_days(tr::now, lt_count, days)
+				: (hours > 1)
+				? tr::lng_hours(tr::now, lt_count, hours)
+				: (minutes > 1)
+				? tr::lng_minutes(tr::now, lt_count, minutes)
+				: tr::lng_seconds(tr::now, lt_count, seconds);
+			if (_history->peer->isBroadcast()) {
+				result.text = tr::lng_action_group_call_finished(
+					tr::now,
+					lt_duration,
+					{ .text = text },
+					Ui::Text::WithEntities);
+			} else {
+				result.links.push_back(fromLink());
+				result.text = tr::lng_action_group_call_finished_group(
+					tr::now,
+					lt_from,
+					fromLinkText(), // Link 1.
+					lt_duration,
+					{ .text = text },
+					Ui::Text::WithEntities);
+			}
+			return result;
+		}
+		if (_history->peer->isBroadcast()) {
+			result.text = tr::lng_action_group_call_started_channel(
+				tr::now,
+				Ui::Text::WithEntities);
+		} else {
+			result.links.push_back(fromLink());
+			result.text = tr::lng_action_group_call_started_group(
+				tr::now,
+				lt_from,
+				fromLinkText(), // Link 1.
+				Ui::Text::WithEntities);
+		}
+		return result;
+	};
+
+	auto prepareInviteToGroupCall = [this](const MTPDmessageActionInviteToGroupCall &action) {
+		const auto callId = CallIdFromInput(action.vcall());
+		const auto owner = &_history->owner();
+		const auto peer = _history->peer;
+		for (const auto &id : action.vusers().v) {
+			const auto user = owner->user(id.v);
+			if (callId) {
+				owner->registerInvitedToCallUser(callId, peer, user);
+			}
+		};
+		const auto linkCallId = PeerHasThisCall(peer, callId).value_or(false)
+			? callId
+			: 0;
+		return prepareInvitedToCallText(
+			ParseInvitedToCallUsers(this, action.vusers().v),
+			linkCallId);
+	};
+
+	auto prepareSetMessagesTTL = [this](const MTPDmessageActionSetMessagesTTL &action) {
+		auto result = PreparedServiceText();
+		const auto period = action.vperiod().v;
+		const auto duration = (period == 5)
+			? u"5 seconds"_q
+			: Ui::FormatTTL(period);
+		if (const auto from = action.vauto_setting_from()) {
+			if (const auto peer = _from->owner().peer(peerFromUser(*from))) {
+				if (!peer->isSelf() && period) {
+					result.text = tr::lng_action_ttl_global(
+						tr::now,
+						lt_from,
+						fromLinkText(), // Link 1.
+						lt_duration,
+						{ .text = duration },
+						Ui::Text::WithEntities);
+					return result;
+				}
+			}
+		}
+		if (isPost()) {
+			if (!period) {
+				result.text = tr::lng_action_ttl_removed_channel(
+					tr::now,
+					Ui::Text::WithEntities);
+			} else {
+				result.text = tr::lng_action_ttl_changed_channel(
+					tr::now,
+					lt_duration,
+					{ .text = duration },
+					Ui::Text::WithEntities);
+			}
+		} else if (_from->isSelf()) {
+			if (!period) {
+				result.text = tr::lng_action_ttl_removed_you(
+					tr::now,
+					Ui::Text::WithEntities);
+			} else {
+				result.text = tr::lng_action_ttl_changed_you(
+					tr::now,
+					lt_duration,
+					{ .text = duration },
+					Ui::Text::WithEntities);
+			}
+		} else {
+			result.links.push_back(fromLink());
+			if (!period) {
+				result.text = tr::lng_action_ttl_removed(
+					tr::now,
+					lt_from,
+					fromLinkText(), // Link 1.
+					Ui::Text::WithEntities);
+			} else {
+				result.text = tr::lng_action_ttl_changed(
+					tr::now,
+					lt_from,
+					fromLinkText(), // Link 1.
+					lt_duration,
+					{ .text = duration },
+					Ui::Text::WithEntities);
+			}
+		}
+		return result;
+	};
+
+	auto prepareSetChatTheme = [this](const MTPDmessageActionSetChatTheme &action) {
+		auto result = PreparedServiceText();
+		const auto text = qs(action.vemoticon());
+		if (!text.isEmpty()) {
+			if (_from->isSelf()) {
+				result.text = tr::lng_action_you_theme_changed(
+					tr::now,
+					lt_emoji,
+					{ .text = text },
+					Ui::Text::WithEntities);
+			} else {
+				result.links.push_back(fromLink());
+				result.text = tr::lng_action_theme_changed(
+					tr::now,
+					lt_from,
+					fromLinkText(), // Link 1.
+					lt_emoji,
+					{ .text = text },
+					Ui::Text::WithEntities);
+			}
+		} else {
+			if (_from->isSelf()) {
+				result.text = tr::lng_action_you_theme_disabled(
+					tr::now,
+					Ui::Text::WithEntities);
+			} else {
+				result.links.push_back(fromLink());
+				result.text = tr::lng_action_theme_disabled(
+					tr::now,
+					lt_from,
+					fromLinkText(), // Link 1.
+					Ui::Text::WithEntities);
+			}
+		}
+		return result;
+	};
+
+	auto prepareChatJoinedByRequest = [this](const MTPDmessageActionChatJoinedByRequest &action) {
+		auto result = PreparedServiceText();
+		result.links.push_back(fromLink());
+		result.text = tr::lng_action_user_joined_by_request(
+			tr::now,
+			lt_from,
+			fromLinkText(), // Link 1.
+			Ui::Text::WithEntities);
+		return result;
+	};
+
+	auto prepareWebViewDataSent = [](const MTPDmessageActionWebViewDataSent &action) {
+		auto result = PreparedServiceText();
+		result.text = tr::lng_action_webview_data_done(
+			tr::now,
+			lt_text,
+			{ .text = qs(action.vtext()) },
+			Ui::Text::WithEntities);
+		return result;
+	};
+
+	auto prepareGiftPremium = [&](
+			const MTPDmessageActionGiftPremium &action) {
+		auto result = PreparedServiceText();
+		const auto isSelf = (_from->id == _from->session().userPeerId());
+		const auto peer = isSelf ? _history->peer : _from;
+		_history->session().giftBoxStickersPacks().load();
+		const auto amount = action.vamount().v;
+		const auto currency = qs(action.vcurrency());
+		result.links.push_back(peer->createOpenLink());
+		result.text = (isSelf
+			? tr::lng_action_gift_received_me
+			: tr::lng_action_gift_received)(
+				tr::now,
+				lt_user,
+				Ui::Text::Link(peer->name(), 1), // Link 1.
+				lt_cost,
+				{ Ui::FillAmountAndCurrency(amount, currency) },
+				Ui::Text::WithEntities);
+		return result;
+	};
+
+	auto prepareTopicCreate = [&](const MTPDmessageActionTopicCreate &action) {
+		auto result = PreparedServiceText();
+		const auto topicUrl = u"internal:url:https://t.me/c/%1/%2"_q
+			.arg(peerToChannel(_history->peer->id).bare)
+			.arg(id.bare);
+		result.text = tr::lng_action_topic_created(
+			tr::now,
+			lt_topic,
+			Ui::Text::Link(
+				Data::ForumTopicIconWithTitle(
+					id,
+					action.vicon_emoji_id().value_or_empty(),
+					qs(action.vtitle())),
+				topicUrl),
+			Ui::Text::WithEntities);
+		return result;
+	};
+
+	auto prepareTopicEdit = [&](const MTPDmessageActionTopicEdit &action) {
+		auto result = PreparedServiceText();
+		if (const auto closed = action.vclosed()) {
+			result.text = { mtpIsTrue(*closed)
+				? tr::lng_action_topic_closed_inside(tr::now)
+				: tr::lng_action_topic_reopened_inside(tr::now) };
+		} else if (const auto hidden = action.vhidden()) {
+			result.text = { mtpIsTrue(*hidden)
+				? tr::lng_action_topic_hidden_inside(tr::now)
+				: tr::lng_action_topic_unhidden_inside(tr::now) };
+		} else if (!action.vtitle()) {
+			if (const auto icon = action.vicon_emoji_id()) {
+				if (const auto iconId = icon->v) {
+					result.links.push_back(fromLink());
+					result.text = tr::lng_action_topic_icon_changed(
+						tr::now,
+						lt_from,
+						fromLinkText(), // Link 1.
+						lt_link,
+						{ tr::lng_action_topic_placeholder(tr::now) },
+						lt_emoji,
+						Data::SingleCustomEmoji(iconId),
+						Ui::Text::WithEntities);
+				} else {
+					result.links.push_back(fromLink());
+					result.text = tr::lng_action_topic_icon_removed(
+						tr::now,
+						lt_from,
+						fromLinkText(), // Link 1.
+						lt_link,
+						{ tr::lng_action_topic_placeholder(tr::now) },
+						Ui::Text::WithEntities);
+				}
+			}
+		} else {
+			result.links.push_back(fromLink());
+			result.text = tr::lng_action_topic_renamed(
+				tr::now,
+				lt_from,
+				fromLinkText(), // Link 1.
+				lt_link,
+				{ tr::lng_action_topic_placeholder(tr::now) },
+				lt_title,
+				Data::ForumTopicIconWithTitle(
+					topicRootId(),
+					action.vicon_emoji_id().value_or_empty(),
+					qs(*action.vtitle())),
+				Ui::Text::WithEntities);
+		}
+		if (result.text.empty()) {
+			result.text = { tr::lng_message_empty(tr::now) };
+		}
+		return result;
+	};
+
+	setServiceText(action.match([&](
+			const MTPDmessageActionChatAddUser &data) {
+		return prepareChatAddUserText(data);
+	}, [&](const MTPDmessageActionChatJoinedByLink &data) {
+		return prepareChatJoinedByLink(data);
+	}, [&](const MTPDmessageActionChatCreate &data) {
+		return prepareChatCreate(data);
+	}, [](const MTPDmessageActionChatMigrateTo &) {
+		return PreparedServiceText();
+	}, [](const MTPDmessageActionChannelMigrateFrom &) {
+		return PreparedServiceText();
+	}, [](const MTPDmessageActionHistoryClear &) {
+		return PreparedServiceText();
+	}, [&](const MTPDmessageActionChannelCreate &data) {
+		return prepareChannelCreate(data);
+	}, [&](const MTPDmessageActionChatDeletePhoto &) {
+		return prepareChatDeletePhoto();
+	}, [&](const MTPDmessageActionChatDeleteUser &data) {
+		return prepareChatDeleteUser(data);
+	}, [&](const MTPDmessageActionChatEditPhoto &data) {
+		return prepareChatEditPhoto(data);
+	}, [&](const MTPDmessageActionChatEditTitle &data) {
+		return prepareChatEditTitle(data);
+	}, [&](const MTPDmessageActionPinMessage &) {
+		return preparePinnedText();
+	}, [&](const MTPDmessageActionGameScore &) {
+		return prepareGameScoreText();
+	}, [&](const MTPDmessageActionPhoneCall &) -> PreparedServiceText {
+		Unexpected("PhoneCall type in setServiceMessageFromMtp.");
+	}, [&](const MTPDmessageActionPaymentSent &) {
+		return preparePaymentSentText();
+	}, [&](const MTPDmessageActionScreenshotTaken &) {
+		return prepareScreenshotTaken();
+	}, [&](const MTPDmessageActionCustomAction &data) {
+		return prepareCustomAction(data);
+	}, [&](const MTPDmessageActionBotAllowed &data) {
+		return prepareBotAllowed(data);
+	}, [&](const MTPDmessageActionSecureValuesSent &data) {
+		return prepareSecureValuesSent(data);
+	}, [&](const MTPDmessageActionContactSignUp &data) {
+		return prepareContactSignUp();
+	}, [&](const MTPDmessageActionGeoProximityReached &data) {
+		return prepareProximityReached(data);
+	}, [](const MTPDmessageActionPaymentSentMe &) {
+		LOG(("API Error: messageActionPaymentSentMe received."));
+		return PreparedServiceText{ { tr::lng_message_empty(tr::now) } };
+	}, [](const MTPDmessageActionSecureValuesSentMe &) {
+		LOG(("API Error: messageActionSecureValuesSentMe received."));
+		return PreparedServiceText{ { tr::lng_message_empty(tr::now) } };
+	}, [&](const MTPDmessageActionGroupCall &data) {
+		return prepareGroupCall(data);
+	}, [&](const MTPDmessageActionInviteToGroupCall &data) {
+		return prepareInviteToGroupCall(data);
+	}, [&](const MTPDmessageActionSetMessagesTTL &data) {
+		return prepareSetMessagesTTL(data);
+	}, [&](const MTPDmessageActionGroupCallScheduled &data) {
+		return prepareCallScheduledText(data.vschedule_date().v);
+	}, [&](const MTPDmessageActionSetChatTheme &data) {
+		return prepareSetChatTheme(data);
+	}, [&](const MTPDmessageActionChatJoinedByRequest &data) {
+		return prepareChatJoinedByRequest(data);
+	}, [&](const MTPDmessageActionWebViewDataSent &data) {
+		return prepareWebViewDataSent(data);
+	}, [&](const MTPDmessageActionGiftPremium &data) {
+		return prepareGiftPremium(data);
+	}, [&](const MTPDmessageActionTopicCreate &data) {
+		return prepareTopicCreate(data);
+	}, [&](const MTPDmessageActionTopicEdit &data) {
+		return prepareTopicEdit(data);
+	}, [&](const MTPDmessageActionWebViewDataSentMe &data) {
+		LOG(("API Error: messageActionWebViewDataSentMe received."));
+		return PreparedServiceText{ { tr::lng_message_empty(tr::now) } };
+	}, [](const MTPDmessageActionEmpty &) {
+		return PreparedServiceText{ { tr::lng_message_empty(tr::now) } };
+	}));
+
+	// Additional information.
+	applyAction(action);
+}
+
+void HistoryItem::applyAction(const MTPMessageAction &action) {
+	action.match([&](const MTPDmessageActionChatAddUser &data) {
+		if (const auto channel = _history->peer->asMegagroup()) {
+			const auto selfUserId = _history->session().userId();
+			for (const auto &item : data.vusers().v) {
+				if (peerFromUser(item) == selfUserId) {
+					channel->mgInfo->joinedMessageFound = true;
+					break;
+				}
+			}
+		}
+	}, [&](const MTPDmessageActionChatJoinedByLink &data) {
+		if (_from->isSelf()) {
+			if (const auto channel = _history->peer->asMegagroup()) {
+				channel->mgInfo->joinedMessageFound = true;
+			}
+		}
+	}, [&](const MTPDmessageActionChatEditPhoto &data) {
+		data.vphoto().match([&](const MTPDphoto &photo) {
+			_media = std::make_unique<Data::MediaPhoto>(
+				this,
+				_history->peer,
+				_history->owner().processPhoto(photo));
+		}, [](const MTPDphotoEmpty &) {
+		});
+	}, [&](const MTPDmessageActionChatCreate &) {
+		_flags |= MessageFlag::IsGroupEssential;
+	}, [&](const MTPDmessageActionChannelCreate &) {
+		_flags |= MessageFlag::IsGroupEssential;
+	}, [&](const MTPDmessageActionChatMigrateTo &) {
+		_flags |= MessageFlag::IsGroupEssential;
+	}, [&](const MTPDmessageActionChannelMigrateFrom &) {
+		_flags |= MessageFlag::IsGroupEssential;
+	}, [&](const MTPDmessageActionContactSignUp &) {
+		_flags |= MessageFlag::IsContactSignUp;
+	}, [&](const MTPDmessageActionChatJoinedByRequest &data) {
+		if (_from->isSelf()) {
+			if (const auto channel = _history->peer->asMegagroup()) {
+				channel->mgInfo->joinedMessageFound = true;
+			}
+		}
+	}, [&](const MTPDmessageActionGiftPremium &data) {
+		_media = std::make_unique<Data::MediaGiftBox>(
+			this,
+			_from,
+			data.vmonths().v);
+	}, [](const auto &) {
+	});
+}
+
+void HistoryItem::setSelfDestruct(HistoryServiceSelfDestruct::Type type, int ttlSeconds) {
+	UpdateComponents(HistoryServiceSelfDestruct::Bit());
+	auto selfdestruct = Get<HistoryServiceSelfDestruct>();
+	selfdestruct->timeToLive = ttlSeconds * 1000LL;
+	selfdestruct->type = type;
+}
+
+PreparedServiceText HistoryItem::prepareInvitedToCallText(
+		const std::vector<not_null<UserData*>> &users,
+		CallId linkCallId) {
+	const auto owner = &_history->owner();
+	auto chatText = tr::lng_action_invite_user_chat(
+		tr::now,
+		Ui::Text::WithEntities);
+	auto result = PreparedServiceText();
+	result.links.push_back(fromLink());
+	auto linkIndex = 1;
+	if (linkCallId) {
+		const auto peer = _history->peer;
+		result.links.push_back(GroupCallClickHandler(peer, linkCallId));
+		chatText = Ui::Text::Link(chatText.text, ++linkIndex);
+	}
+	if (users.size() == 1) {
+		auto user = users[0];
+		result.links.push_back(user->createOpenLink());
+		result.text = tr::lng_action_invite_user(
+			tr::now,
+			lt_from,
+			fromLinkText(), // Link 1.
+			lt_user,
+			Ui::Text::Link(user->name(), ++linkIndex), // Link N.
+			lt_chat,
+			chatText,
+			Ui::Text::WithEntities);
+	} else if (users.empty()) {
+		result.text = tr::lng_action_invite_user(
+			tr::now,
+			lt_from,
+			fromLinkText(), // Link 1.
+			lt_user,
+			{ .text = u"somebody"_q },
+			lt_chat,
+			chatText,
+			Ui::Text::WithEntities);
+	} else {
+		for (auto i = 0, l = int(users.size()); i != l; ++i) {
+			const auto user = users[i];
+			result.links.push_back(user->createOpenLink());
+
+			auto linkText = Ui::Text::Link(user->name(), ++linkIndex);
+			if (i == 0) {
+				result.text = linkText;
+			} else if (i + 1 == l) {
+				result.text = tr::lng_action_invite_users_and_last(
+					tr::now,
+					lt_accumulated,
+					result.text,
+					lt_user,
+					linkText,
+					Ui::Text::WithEntities);
+			} else {
+				result.text = tr::lng_action_invite_users_and_one(
+					tr::now,
+					lt_accumulated,
+					result.text,
+					lt_user,
+					linkText,
+					Ui::Text::WithEntities);
+			}
+		}
+		result.text = tr::lng_action_invite_users_many(
+			tr::now,
+			lt_from,
+			fromLinkText(), // Link 1.
+			lt_users,
+			result.text,
+			lt_chat,
+			chatText,
+			Ui::Text::WithEntities);
+	}
+	return result;
+}
+
+PreparedServiceText HistoryItem::preparePinnedText() {
+	auto result = PreparedServiceText();
+	auto pinned = Get<HistoryServicePinned>();
+	if (pinned && pinned->msg) {
+		const auto mediaText = [&] {
+			using TTL = HistoryServiceSelfDestruct;
+			if (const auto media = pinned->msg->media()) {
+				return media->pinnedTextSubstring();
+			} else if (const auto selfdestruct = pinned->msg->Get<TTL>()) {
+				if (selfdestruct->type == TTL::Type::Photo) {
+					return tr::lng_action_pinned_media_photo(tr::now);
+				} else if (selfdestruct->type == TTL::Type::Video) {
+					return tr::lng_action_pinned_media_video(tr::now);
+				}
+			}
+			return QString();
+		}();
+		result.links.push_back(fromLink());
+		result.links.push_back(pinned->lnk);
+		if (mediaText.isEmpty()) {
+			auto original = pinned->msg->originalText();
+			auto cutAt = 0;
+			auto limit = kPinnedMessageTextLimit;
+			auto size = original.text.size();
+			for (; limit != 0;) {
+				--limit;
+				if (cutAt >= size) break;
+				if (original.text.at(cutAt).isLowSurrogate()
+					&& (cutAt + 1 < size)
+					&& original.text.at(cutAt + 1).isHighSurrogate()) {
+					cutAt += 2;
+				} else {
+					++cutAt;
+				}
+			}
+			if (!limit && cutAt + 5 < size) {
+				original = Ui::Text::Mid(original, 0, cutAt).append(
+					Ui::kQEllipsis);
+			}
+			original = Ui::Text::Link(
+				Ui::Text::Filtered(
+					std::move(original),
+					{
+						EntityType::Spoiler,
+						EntityType::StrikeOut,
+						EntityType::Italic,
+						EntityType::CustomEmoji,
+					}),
+				2);
+			result.text = tr::lng_action_pinned_message(
+				tr::now,
+				lt_from,
+				fromLinkText(), // Link 1.
+				lt_text,
+				std::move(original), // Link 2.
+				Ui::Text::WithEntities);
+		} else {
+			result.text = tr::lng_action_pinned_media(
+				tr::now,
+				lt_from,
+				fromLinkText(), // Link 1.
+				lt_media,
+				Ui::Text::Link(mediaText, 2), // Link 2.
+				Ui::Text::WithEntities);
+		}
+	} else if (pinned && pinned->msgId) {
+		result.links.push_back(fromLink());
+		result.links.push_back(pinned->lnk);
+		result.text = tr::lng_action_pinned_media(
+			tr::now,
+			lt_from,
+			fromLinkText(), // Link 1.
+			lt_media,
+			Ui::Text::Link(tr::lng_contacts_loading(tr::now), 2), // Link 2.
+			Ui::Text::WithEntities);
+	} else {
+		result.links.push_back(fromLink());
+		result.text = tr::lng_action_pinned_media(
+			tr::now,
+			lt_from,
+			fromLinkText(), // Link 1.
+			lt_media,
+			{ .text = tr::lng_deleted_message(tr::now) },
+			Ui::Text::WithEntities);
+	}
+	return result;
+}
+
+PreparedServiceText HistoryItem::prepareGameScoreText() {
+	auto result = PreparedServiceText();
+	auto gamescore = Get<HistoryServiceGameScore>();
+
+	auto computeGameTitle = [&]() -> TextWithEntities {
+		if (gamescore && gamescore->msg) {
+			if (const auto media = gamescore->msg->media()) {
+				if (const auto game = media->game()) {
+					const auto row = 0;
+					const auto column = 0;
+					result.links.push_back(
+						std::make_shared<ReplyMarkupClickHandler>(
+							&_history->owner(),
+							row,
+							column,
+							gamescore->msg->fullId()));
+					auto titleText = game->title;
+					return Ui::Text::Link(titleText, QString());
+				}
+			}
+			return tr::lng_deleted_message(tr::now, Ui::Text::WithEntities);
+		} else if (gamescore && gamescore->msgId) {
+			return tr::lng_contacts_loading(tr::now, Ui::Text::WithEntities);
+		}
+		return {};
+	};
+
+	const auto scoreNumber = gamescore ? gamescore->score : 0;
+	if (_from->isSelf()) {
+		auto gameTitle = computeGameTitle();
+		if (gameTitle.text.isEmpty()) {
+			result.text = tr::lng_action_game_you_scored_no_game(
+				tr::now,
+				lt_count,
+				scoreNumber,
+				Ui::Text::WithEntities);
+		} else {
+			result.text = tr::lng_action_game_you_scored(
+				tr::now,
+				lt_count,
+				scoreNumber,
+				lt_game,
+				gameTitle,
+				Ui::Text::WithEntities);
+		}
+	} else {
+		result.links.push_back(fromLink());
+		auto gameTitle = computeGameTitle();
+		if (gameTitle.text.isEmpty()) {
+			result.text = tr::lng_action_game_score_no_game(
+				tr::now,
+				lt_count,
+				scoreNumber,
+				lt_from,
+				fromLinkText(), // Link 1.
+				Ui::Text::WithEntities);
+		} else {
+			result.text = tr::lng_action_game_score(
+				tr::now,
+				lt_count,
+				scoreNumber,
+				lt_from,
+				fromLinkText(), // Link 1.
+				lt_game,
+				gameTitle,
+				Ui::Text::WithEntities);
+		}
+	}
+	return result;
+}
+
+PreparedServiceText HistoryItem::preparePaymentSentText() {
+	auto result = PreparedServiceText();
+	const auto payment = Get<HistoryServicePayment>();
+	Assert(payment != nullptr);
+
+	auto invoiceTitle = [&] {
+		if (payment->msg) {
+			if (const auto media = payment->msg->media()) {
+				if (const auto invoice = media->invoice()) {
+					return Ui::Text::Link(invoice->title, QString());
+				}
+			}
+		}
+		return TextWithEntities();
+	}();
+
+	if (invoiceTitle.text.isEmpty()) {
+		if (payment->recurringUsed) {
+			result.text = tr::lng_action_payment_used_recurring(
+				tr::now,
+				lt_amount,
+				{ .text = payment->amount },
+				Ui::Text::WithEntities);
+		} else {
+			result.text = (payment->recurringInit
+				? tr::lng_action_payment_init_recurring
+				: tr::lng_action_payment_done)(
+					tr::now,
+					lt_amount,
+					{ .text = payment->amount },
+					lt_user,
+					{ .text = _history->peer->name() },
+					Ui::Text::WithEntities);
+		}
+	} else {
+		result.text = (payment->recurringInit
+			? tr::lng_action_payment_init_recurring_for
+			: tr::lng_action_payment_done_for)(
+				tr::now,
+				lt_amount,
+				{ .text = payment->amount },
+				lt_user,
+				{ .text = _history->peer->name() },
+				lt_invoice,
+				invoiceTitle,
+				Ui::Text::WithEntities);
+		if (payment->msg) {
+			result.links.push_back(payment->lnk);
+		}
+	}
+	return result;
+}
+
+PreparedServiceText HistoryItem::prepareCallScheduledText(
+		TimeId scheduleDate) {
+	const auto call = Get<HistoryServiceOngoingCall>();
+	Assert(call != nullptr);
+
+	const auto scheduled = base::unixtime::parse(scheduleDate);
+	const auto date = scheduled.date();
+	const auto now = QDateTime::currentDateTime();
+	const auto secsToDateAddDays = [&](int days) {
+		return now.secsTo(QDateTime(date.addDays(days), QTime(0, 0)));
+	};
+	auto result = PreparedServiceText();
+	const auto prepareWithDate = [&](const QString &date) {
+		if (_history->peer->isBroadcast()) {
+			result.text = tr::lng_action_group_call_scheduled_channel(
+				tr::now,
+				lt_date,
+				{ .text = date },
+				Ui::Text::WithEntities);
+		} else {
+			result.links.push_back(fromLink());
+			result.text = tr::lng_action_group_call_scheduled_group(
+				tr::now,
+				lt_from,
+				fromLinkText(), // Link 1.
+				lt_date,
+				{ .text = date },
+				Ui::Text::WithEntities);
+		}
+	};
+	const auto time = QLocale().toString(scheduled.time(), cTimeFormat());
+	const auto prepareGeneric = [&] {
+		prepareWithDate(tr::lng_group_call_starts_date(
+			tr::now,
+			lt_date,
+			langDayOfMonthFull(date),
+			lt_time,
+			time));
+	};
+	auto nextIn = TimeId(0);
+	if (now.date().addDays(1) < scheduled.date()) {
+		nextIn = secsToDateAddDays(-1);
+		prepareGeneric();
+	} else if (now.date().addDays(1) == scheduled.date()) {
+		nextIn = secsToDateAddDays(0);
+		prepareWithDate(
+			tr::lng_group_call_starts_tomorrow(tr::now, lt_time, time));
+	} else if (now.date() == scheduled.date()) {
+		nextIn = secsToDateAddDays(1);
+		prepareWithDate(
+			tr::lng_group_call_starts_today(tr::now, lt_time, time));
+	} else {
+		prepareGeneric();
+	}
+	if (nextIn) {
+		call->lifetime = base::timer_once(
+			(nextIn + 2) * crl::time(1000)
+		) | rpl::start_with_next([=] {
+			updateServiceText(prepareCallScheduledText(scheduleDate));
+		});
+	}
+	return result;
+}
+
+TextWithEntities HistoryItem::fromLinkText() const {
+	return Ui::Text::Link(_from->name(), 1);
+}
+
+ClickHandlerPtr HistoryItem::fromLink() const {
+	return _from->createOpenLink();
+}
+
+crl::time HistoryItem::getSelfDestructIn(crl::time now) {
+	if (auto selfdestruct = Get<HistoryServiceSelfDestruct>()) {
+		if (selfdestruct->destructAt > 0) {
+			if (selfdestruct->destructAt <= now) {
+				auto text = [selfdestruct] {
+					switch (selfdestruct->type) {
+					case HistoryServiceSelfDestruct::Type::Photo:
+						return tr::lng_ttl_photo_expired(tr::now);
+					case HistoryServiceSelfDestruct::Type::Video:
+						return tr::lng_ttl_video_expired(tr::now);
+					}
+					Unexpected("Type in HistoryServiceSelfDestruct::Type");
+				};
+				setServiceText({ TextWithEntities{.text = text() } });
+				return 0;
+			}
+			return selfdestruct->destructAt - now;
+		}
+	}
+	return 0;
+}
+
+void HistoryItem::setupChatThemeChange() {
+	if (const auto user = history()->peer->asUser()) {
+		auto link = std::make_shared<LambdaClickHandler>([=](
+				ClickContext context) {
+			const auto my = context.other.value<ClickHandlerContext>();
+			if (const auto controller = my.sessionWindow.get()) {
+				controller->toggleChooseChatTheme(user);
+			}
+		});
+
+		UpdateComponents(HistoryServiceChatThemeChange::Bit());
+		Get<HistoryServiceChatThemeChange>()->link = std::move(link);
+	} else {
+		RemoveComponents(HistoryServiceChatThemeChange::Bit());
+	}
+}
+
+void HistoryItem::setupTTLChange() {
+	const auto peer = history()->peer;
+	auto link = std::make_shared<LambdaClickHandler>([=](
+			ClickContext context) {
+		const auto my = context.other.value<ClickHandlerContext>();
+		if (const auto controller = my.sessionWindow.get()) {
+			const auto validator = TTLMenu::TTLValidator(
+				std::make_shared<Window::Show>(controller),
+				peer);
+			if (validator.can()) {
+				validator.showBox();
 			}
 		}
 	});
+
+	UpdateComponents(HistoryServiceTTLChange::Bit());
+	Get<HistoryServiceTTLChange>()->link = std::move(link);
 }
 
-MessageFlags FlagsFromMTP(
-		MsgId id,
-		MTPDmessage::Flags flags,
-		MessageFlags localFlags) {
-	using Flag = MessageFlag;
-	using MTP = MTPDmessage::Flag;
-	return localFlags
-		| (IsServerMsgId(id) ? Flag::HistoryEntry : Flag())
-		| ((flags & MTP::f_out) ? Flag::Outgoing : Flag())
-		| ((flags & MTP::f_mentioned) ? Flag::MentionsMe : Flag())
-		| ((flags & MTP::f_media_unread) ? Flag::MediaIsUnread : Flag())
-		| ((flags & MTP::f_silent) ? Flag::Silent : Flag())
-		| ((flags & MTP::f_post) ? Flag::Post : Flag())
-		| ((flags & MTP::f_legacy) ? Flag::Legacy : Flag())
-		| ((flags & MTP::f_edit_hide) ? Flag::HideEdited : Flag())
-		| ((flags & MTP::f_pinned) ? Flag::Pinned : Flag())
-		| ((flags & MTP::f_from_id) ? Flag::HasFromId : Flag())
-		| ((flags & MTP::f_reply_to) ? Flag::HasReplyInfo : Flag())
-		| ((flags & MTP::f_reply_markup) ? Flag::HasReplyMarkup : Flag())
-		| ((flags & MTP::f_from_scheduled) ? Flag::IsOrWasScheduled : Flag())
-		| ((flags & MTP::f_views) ? Flag::HasViews : Flag())
-		| ((flags & MTP::f_noforwards) ? Flag::NoForwards : Flag());
-}
-
-MessageFlags FlagsFromMTP(
-		MsgId id,
-		MTPDmessageService::Flags flags,
-		MessageFlags localFlags) {
-	using Flag = MessageFlag;
-	using MTP = MTPDmessageService::Flag;
-	return localFlags
-		| (IsServerMsgId(id) ? Flag::HistoryEntry : Flag())
-		| ((flags & MTP::f_out) ? Flag::Outgoing : Flag())
-		| ((flags & MTP::f_mentioned) ? Flag::MentionsMe : Flag())
-		| ((flags & MTP::f_media_unread) ? Flag::MediaIsUnread : Flag())
-		| ((flags & MTP::f_silent) ? Flag::Silent : Flag())
-		| ((flags & MTP::f_post) ? Flag::Post : Flag())
-		| ((flags & MTP::f_legacy) ? Flag::Legacy : Flag())
-		| ((flags & MTP::f_from_id) ? Flag::HasFromId : Flag())
-		| ((flags & MTP::f_reply_to) ? Flag::HasReplyInfo : Flag());
-}
-
-not_null<HistoryItem*> HistoryItem::Create(
-		not_null<History*> history,
-		MsgId id,
-		const MTPMessage &message,
-		MessageFlags localFlags) {
-	return message.match([&](const MTPDmessage &data) -> HistoryItem* {
-		const auto media = data.vmedia();
-		const auto checked = media
-			? CheckMessageMedia(*media)
-			: MediaCheckResult::Good;
-		if (checked == MediaCheckResult::Unsupported) {
-			return CreateUnsupportedMessage(
-				history,
-				id,
-				FlagsFromMTP(id, data.vflags().v, localFlags),
-				MsgId(0), // No need to pass reply_to data here.
-				data.vvia_bot_id().value_or_empty(),
-				data.vdate().v,
-				data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0));
-		} else if (checked == MediaCheckResult::Empty) {
-			auto text = HistoryService::PreparedText{
-				tr::lng_message_empty(tr::now, Ui::Text::WithEntities)
-			};
-			return history->makeServiceMessage(
-				id,
-				FlagsFromMTP(id, data.vflags().v, localFlags),
-				data.vdate().v,
-				std::move(text),
-				data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0));
-		} else if (checked == MediaCheckResult::HasTimeToLive) {
-			return history->makeServiceMessage(id, data, localFlags);
+void HistoryItem::clearDependencyMessage() {
+	if (const auto dependent = GetServiceDependentData()) {
+		if (dependent->msg) {
+			_history->owner().unregisterDependentMessage(
+				this,
+				dependent->msg);
+			dependent->msg = nullptr;
+			dependent->msgId = 0;
 		}
-		return history->makeMessage(id, data, localFlags);
-	}, [&](const MTPDmessageService &data) -> HistoryItem* {
-		if (data.vaction().type() == mtpc_messageActionPhoneCall) {
-			return history->makeMessage(id, data, localFlags);
-		}
-		return history->makeServiceMessage(id, data, localFlags);
-	}, [&](const MTPDmessageEmpty &data) -> HistoryItem* {
-		return history->makeServiceMessage(
-			id,
-			localFlags,
-			TimeId(0),
-			HistoryService::PreparedText{ tr::lng_message_empty(
-				tr::now,
-				Ui::Text::WithEntities) });
-	});
+	}
 }
diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h
index 590eafa44..b722c9646 100644
--- a/Telegram/SourceFiles/history/history_item.h
+++ b/Telegram/SourceFiles/history/history_item.h
@@ -15,9 +15,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include <any>
 
+class HiddenSenderInfo;
+class History;
+struct HistoryMessageReply;
+struct HistoryMessageViews;
+struct HistoryMessageMarkupData;
 struct HistoryMessageReplyMarkup;
+struct HistoryServiceDependentData;
+enum class HistorySelfDestructType;
+struct PreparedServiceText;
 class ReplyKeyboard;
-class HistoryMessage;
 
 namespace base {
 template <typename Enum>
@@ -47,6 +54,7 @@ struct MessageReaction;
 class MessageReactions;
 class ForumTopic;
 class Thread;
+struct SponsoredFrom;
 } // namespace Data
 
 namespace Main {
@@ -68,46 +76,120 @@ enum class CursorState : char;
 enum class PointState : char;
 enum class Context : char;
 class ElementDelegate;
+class Element;
+class Message;
+class Service;
+class ServiceMessagePainter;
 } // namespace HistoryView
 
-class HiddenSenderInfo;
-class History;
-
-[[nodiscard]] MessageFlags FlagsFromMTP(
-	MsgId id,
-	MTPDmessage::Flags flags,
-	MessageFlags localFlags);
-[[nodiscard]] MessageFlags FlagsFromMTP(
-	MsgId id,
-	MTPDmessageService::Flags flags,
-	MessageFlags localFlags);
-
-class HistoryItem : public RuntimeComposer<HistoryItem> {
+class HistoryItem final : public RuntimeComposer<HistoryItem> {
 public:
-	static not_null<HistoryItem*> Create(
+	[[nodiscard]] static std::unique_ptr<Data::Media> CreateMedia(
+		not_null<HistoryItem*> item,
+		const MTPMessageMedia &media);
+
+	HistoryItem(
 		not_null<History*> history,
 		MsgId id,
-		const MTPMessage &message,
+		const MTPDmessage &data,
 		MessageFlags localFlags);
+	HistoryItem(
+		not_null<History*> history,
+		MsgId id,
+		const MTPDmessageService &data,
+		MessageFlags localFlags);
+	HistoryItem(
+		not_null<History*> history,
+		MsgId id,
+		const MTPDmessageEmpty &data,
+		MessageFlags localFlags);
+
+	HistoryItem( // Sponsored message.
+		not_null<History*> history,
+		MsgId id,
+		Data::SponsoredFrom from,
+		const TextWithEntities &textWithEntities,
+		HistoryItem *injectedAfter);
+
+	HistoryItem( // Local message.
+		not_null<History*> history,
+		MsgId id,
+		MessageFlags flags,
+		MsgId replyTo,
+		UserId viaBotId,
+		TimeId date,
+		PeerId from,
+		const QString &postAuthor,
+		const TextWithEntities &textWithEntities,
+		const MTPMessageMedia &media,
+		HistoryMessageMarkupData &&markup,
+		uint64 groupedId);
+	HistoryItem( // Local service message.
+		not_null<History*> history,
+		MsgId id,
+		MessageFlags flags,
+		TimeId date,
+		PreparedServiceText &&message,
+		PeerId from = 0,
+		PhotoData *photo = nullptr);
+	HistoryItem( // Local forwarded.
+		not_null<History*> history,
+		MsgId id,
+		MessageFlags flags,
+		TimeId date,
+		PeerId from,
+		const QString &postAuthor,
+		not_null<HistoryItem*> original,
+		MsgId topicRootId);
+	HistoryItem( // Local photo.
+		not_null<History*> history,
+		MsgId id,
+		MessageFlags flags,
+		MsgId replyTo,
+		UserId viaBotId,
+		TimeId date,
+		PeerId from,
+		const QString &postAuthor,
+		not_null<PhotoData*> photo,
+		const TextWithEntities &caption,
+		HistoryMessageMarkupData &&markup);
+	HistoryItem( // Local document.
+		not_null<History*> history,
+		MsgId id,
+		MessageFlags flags,
+		MsgId replyTo,
+		UserId viaBotId,
+		TimeId date,
+		PeerId from,
+		const QString &postAuthor,
+		not_null<DocumentData*> document,
+		const TextWithEntities &caption,
+		HistoryMessageMarkupData &&markup);
+	HistoryItem( // Local game.
+		not_null<History*> history,
+		MsgId id,
+		MessageFlags flags,
+		MsgId replyTo,
+		UserId viaBotId,
+		TimeId date,
+		PeerId from,
+		const QString &postAuthor,
+		not_null<GameData*> game,
+		HistoryMessageMarkupData &&markup);
+	~HistoryItem();
 
 	struct Destroyer {
 		void operator()(HistoryItem *value);
 	};
 
-	virtual void dependencyItemRemoved(HistoryItem *dependency) {
-	}
-	virtual bool updateDependencyItem() {
-		return true;
-	}
-	virtual MsgId dependencyMsgId() const {
-		return 0;
-	}
-	virtual void checkBuyButton() {
-	}
-	[[nodiscard]] virtual bool notificationReady() const {
-		return true;
-	}
+	void dependencyItemRemoved(not_null<HistoryItem*> dependency);
+	void updateDependencyItem();
+	[[nodiscard]] MsgId dependencyMsgId() const;
+	[[nodiscard]] bool notificationReady() const;
 	[[nodiscard]] PeerData *specialNotificationPeer() const;
+	void checkBuyButton();
+
+	void updateServiceText(PreparedServiceText &&text);
 
 	[[nodiscard]] UserData *viaBot() const;
 	[[nodiscard]] UserData *getMessageBot() const;
@@ -165,15 +247,13 @@ public:
 	void setIsPinned(bool isPinned);
 
 	// For edit media in history_message.
-	virtual void returnSavedMedia();
+	void returnSavedMedia();
 	void savePreviousMedia();
 	[[nodiscard]] bool isEditingMedia() const;
 	void clearSavedMedia();
 
 	// Zero result means this message is not self-destructing right now.
-	virtual crl::time getSelfDestructIn(crl::time now) {
-		return 0;
-	}
+	[[nodiscard]] crl::time getSelfDestructIn(crl::time now);
 
 	[[nodiscard]] bool definesReplyKeyboard() const;
 	[[nodiscard]] ReplyMarkupFlags replyKeyboardFlags() const;
@@ -224,118 +304,75 @@ public:
 	[[nodiscard]] bool isRegular() const;
 	[[nodiscard]] bool isUploading() const;
 	void sendFailed();
-	[[nodiscard]] virtual int viewsCount() const {
-		return hasViews() ? 1 : -1;
-	}
-	[[nodiscard]] virtual int repliesCount() const {
-		return 0;
-	}
-	[[nodiscard]] virtual bool repliesAreComments() const {
-		return false;
-	}
-	[[nodiscard]] virtual bool externalReply() const {
-		return false;
-	}
+	[[nodiscard]] int viewsCount() const;
+	[[nodiscard]] int repliesCount() const;
+	[[nodiscard]] bool repliesAreComments() const;
+	[[nodiscard]] bool externalReply() const;
 	[[nodiscard]] bool hasExtendedMediaPreview() const;
 
-	virtual void setCommentsInboxReadTill(MsgId readTillId) {
-	}
-	virtual void setCommentsMaxId(MsgId maxId) {
-	}
-	virtual void setCommentsPossibleMaxId(MsgId possibleMaxId) {
-	}
-	[[nodiscard]] virtual bool areCommentsUnread() const {
-		return false;
-	}
+	void setCommentsInboxReadTill(MsgId readTillId);
+	void setCommentsMaxId(MsgId maxId);
+	void setCommentsPossibleMaxId(MsgId possibleMaxId);
+	[[nodiscard]] bool areCommentsUnread() const;
 
-	[[nodiscard]] virtual FullMsgId commentsItemId() const {
-		return FullMsgId();
-	}
-	virtual void setCommentsItemId(FullMsgId id) {
-	}
+	[[nodiscard]] FullMsgId commentsItemId() const;
+	void setCommentsItemId(FullMsgId id);
 
-	[[nodiscard]] virtual bool needCheck() const;
+	[[nodiscard]] bool needCheck() const;
 
-	[[nodiscard]] virtual bool isService() const {
-		return false;
-	}
-	virtual void applyEdition(HistoryMessageEdition &&edition) {
-	}
-	virtual void applyEdition(const MTPDmessageService &message) {
-	}
-	virtual void applyEdition(const MTPMessageExtendedMedia &media) {
-	}
-	void applyEditionToHistoryCleared();
-	virtual void updateSentContent(
+	[[nodiscard]] bool isService() const;
+	void applyEdition(HistoryMessageEdition &&edition);
+
+	void applyEdition(const MTPDmessageService &message);
+	void applyEdition(const MTPMessageExtendedMedia &media);
+	void updateForwardedInfo(const MTPMessageFwdHeader *fwd);
+	void updateSentContent(
 		const TextWithEntities &textWithEntities,
-		const MTPMessageMedia *media) {
-	}
-	virtual void updateReplyMarkup(HistoryMessageMarkupData &&markup) {
-	}
-	virtual void updateForwardedInfo(const MTPMessageFwdHeader *fwd) {
-	}
-	virtual void contributeToSlowmode(TimeId realDate = 0) {
-	}
-
-	virtual void addToUnreadThings(HistoryUnreadThings::AddType type);
-	virtual void destroyHistoryEntry() {
-	}
-	[[nodiscard]] virtual Storage::SharedMediaTypesMask sharedMediaTypes() const = 0;
-
-	virtual void applySentMessage(const MTPDmessage &data);
-	virtual void applySentMessage(
+		const MTPMessageMedia *media);
+	void applySentMessage(const MTPDmessage &data);
+	void applySentMessage(
 		const QString &text,
 		const MTPDupdateShortSentMessage &data,
 		bool wasAlready);
+	void updateReactions(const MTPMessageReactions *reactions);
+
+	void applyEditionToHistoryCleared();
+	void updateReplyMarkup(HistoryMessageMarkupData &&markup);
+	void contributeToSlowmode(TimeId realDate = 0);
+
+	void addToUnreadThings(HistoryUnreadThings::AddType type);
+	void destroyHistoryEntry();
+	[[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const;
 
 	void indexAsNewItem();
 
-	[[nodiscard]] virtual QString notificationHeader() const {
-		return QString();
-	}
-	[[nodiscard]] virtual TextWithEntities notificationText() const;
+	[[nodiscard]] QString notificationHeader() const;
+	[[nodiscard]] TextWithEntities notificationText() const;
 
 	using ToPreviewOptions = HistoryView::ToPreviewOptions;
 	using ItemPreview = HistoryView::ItemPreview;
 
 	// Returns text with link-start and link-end commands for service-color highlighting.
 	// Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text"
-	[[nodiscard]] virtual ItemPreview toPreview(
-		ToPreviewOptions options) const;
-	[[nodiscard]] virtual TextWithEntities inReplyText() const;
-	[[nodiscard]] virtual TextWithEntities originalText() const {
-		return TextWithEntities();
-	}
-	[[nodiscard]] virtual auto originalTextWithLocalEntities() const
-	-> TextWithEntities {
-		return TextWithEntities();
-	}
-	[[nodiscard]] virtual auto customTextLinks() const
-		-> const std::vector<ClickHandlerPtr> &;
-	[[nodiscard]] virtual TextForMimeData clipboardText() const {
-		return TextForMimeData();
-	}
+	[[nodiscard]] ItemPreview toPreview(ToPreviewOptions options) const;
+	[[nodiscard]] TextWithEntities inReplyText() const;
+	[[nodiscard]] TextWithEntities originalText() const;
+	[[nodiscard]] TextWithEntities originalTextWithLocalEntities() const;
+	[[nodiscard]] const std::vector<ClickHandlerPtr> &customTextLinks() const;
+	[[nodiscard]] TextForMimeData clipboardText() const;
 
-	virtual bool changeViewsCount(int count) {
-		return false;
-	}
-	virtual void setForwardsCount(int count) {
-	}
-	virtual void setReplies(HistoryMessageRepliesData &&data) {
-	}
-	virtual void clearReplies() {
-	}
-	virtual void changeRepliesCount(int delta, PeerId replier) {
-	}
-	virtual void setReplyFields(
+	bool changeViewsCount(int count);
+	void setForwardsCount(int count);
+	void setReplies(HistoryMessageRepliesData &&data);
+	void clearReplies();
+	void changeRepliesCount(int delta, PeerId replier);
+	void setReplyFields(
 		MsgId replyTo,
 		MsgId replyToTop,
-		bool isForumPost) = 0;
-	virtual void setPostAuthor(const QString &author) {
-	}
-	virtual void setRealId(MsgId newId);
-	virtual void incrementReplyToTopCounter() {
-	}
+		bool isForumPost);
+	void setPostAuthor(const QString &author);
+	void setRealId(MsgId newId);
+	void incrementReplyToTopCounter();
 
 	[[nodiscard]] bool emptyText() const {
 		return _text.empty();
@@ -346,9 +383,9 @@ public:
 	[[nodiscard]] bool canStopPoll() const;
 	[[nodiscard]] bool forbidsForward() const;
 	[[nodiscard]] bool forbidsSaving() const;
-	[[nodiscard]] virtual bool allowsSendNow() const;
-	[[nodiscard]] virtual bool allowsForward() const;
-	[[nodiscard]] virtual bool allowsEdit(TimeId now) const;
+	[[nodiscard]] bool allowsSendNow() const;
+	[[nodiscard]] bool allowsForward() const;
+	[[nodiscard]] bool allowsEdit(TimeId now) const;
 	[[nodiscard]] bool canDelete() const;
 	[[nodiscard]] bool canDeleteForEveryone(TimeId now) const;
 	[[nodiscard]] bool suggestReport() const;
@@ -364,7 +401,6 @@ public:
 	void toggleReaction(
 		const Data::ReactionId &reaction,
 		ReactionSource source);
-	void updateReactions(const MTPMessageReactions *reactions);
 	void updateReactionsUnknown();
 	[[nodiscard]] auto reactions() const
 		-> const std::vector<Data::MessageReaction> &;
@@ -391,12 +427,11 @@ public:
 		return _media.get();
 	}
 	[[nodiscard]] bool computeDropForwardedInfo() const;
-	virtual void setText(const TextWithEntities &textWithEntities) {
-	}
+	void setText(const TextWithEntities &textWithEntities);
 
-	[[nodiscard]] virtual MsgId replyToId() const = 0;
-	[[nodiscard]] virtual MsgId replyToTop() const = 0;
-	[[nodiscard]] virtual MsgId topicRootId() const = 0;
+	[[nodiscard]] MsgId replyToId() const;
+	[[nodiscard]] MsgId replyToTop() const;
+	[[nodiscard]] MsgId topicRootId() const;
 	[[nodiscard]] bool inThread(MsgId rootId) const;
 
 	[[nodiscard]] not_null<PeerData*> author() const;
@@ -426,9 +461,9 @@ public:
 	[[nodiscard]] HistoryItem *lookupDiscussionPostOriginal() const;
 	[[nodiscard]] PeerData *displayFrom() const;
 
-	[[nodiscard]] virtual std::unique_ptr<HistoryView::Element> createView(
+	[[nodiscard]] std::unique_ptr<HistoryView::Element> createView(
 		not_null<HistoryView::ElementDelegate*> delegate,
-		HistoryView::Element *replacing = nullptr) = 0;
+		HistoryView::Element *replacing = nullptr);
 
 	void updateDate(TimeId newDate);
 	[[nodiscard]] bool canUpdateDate() const;
@@ -438,11 +473,16 @@ public:
 		return _ttlDestroyAt;
 	}
 
-	virtual ~HistoryItem();
-
 	MsgId id;
 
-protected:
+private:
+	struct CreateConfig;
+
+	struct SavedMediaData {
+		TextWithEntities text;
+		std::unique_ptr<Data::Media> media;
+	};
+
 	HistoryItem(
 		not_null<History*> history,
 		MsgId id,
@@ -450,63 +490,113 @@ protected:
 		TimeId date,
 		PeerId from);
 
-	virtual void markMediaAsReadHook() {
+	void createComponentsHelper(
+		MessageFlags flags,
+		MsgId replyTo,
+		UserId viaBotId,
+		const QString &postAuthor,
+		HistoryMessageMarkupData &&markup);
+	void createComponents(CreateConfig &&config);
+	void setupForwardedComponent(const CreateConfig &config);
+
+	[[nodiscard]] bool generateLocalEntitiesByReply() const;
+	[[nodiscard]] TextWithEntities withLocalEntities(
+		const TextWithEntities &textWithEntities) const;
+	void setTextValue(TextWithEntities text);
+	[[nodiscard]] bool isTooOldForEdit(TimeId now) const;
+	[[nodiscard]] bool isLegacyMessage() const {
+		return _flags & MessageFlag::Legacy;
 	}
 
-	void applyServiceDateEdition(const MTPDmessageService &data);
+	[[nodiscard]] bool checkCommentsLinkedChat(ChannelId id) const;
+
+	void setReplyMarkup(HistoryMessageMarkupData &&markup);
+
+	void changeReplyToTopCounter(
+		not_null<HistoryMessageReply*> reply,
+		int delta);
+	void refreshRepliesText(
+		not_null<HistoryMessageViews*> views,
+		bool forceResize = false);
+
+	[[nodiscard]] bool checkRepliesPts(
+		const HistoryMessageRepliesData &data) const;
+
+	[[nodiscard]] HistoryServiceDependentData *GetServiceDependentData();
+	[[nodiscard]] auto GetServiceDependentData() const
+		-> const HistoryServiceDependentData *;
+	void updateDependentServiceText();
+	bool updateServiceDependent(bool force = false);
+	void setServiceText(PreparedServiceText &&prepared);
+
 	void finishEdition(int oldKeyboardTop);
 	void finishEditionToEmpty();
 
+	void clearDependencyMessage();
+	void setupChatThemeChange();
+	void setupTTLChange();
+
+	void setSelfDestruct(HistorySelfDestructType type, int ttlSeconds);
+
+	TextWithEntities fromLinkText() const;
+	ClickHandlerPtr fromLink() const;
+
+	void setGroupId(MessageGroupId groupId);
+
+	static void FillForwardedInfo(
+		CreateConfig &config,
+		const MTPDmessageFwdHeader &data);
+	void createComponents(const MTPDmessage &data);
+	void setMedia(const MTPMessageMedia &media);
+	void applyServiceDateEdition(const MTPDmessageService &data);
 	void setReactions(const MTPMessageReactions *reactions);
 	[[nodiscard]] bool changeReactions(const MTPMessageReactions *reactions);
+	void setServiceMessageByAction(const MTPmessageAction &action);
+	void applyAction(const MTPMessageAction &action);
+	void refreshMedia(const MTPMessageMedia *media);
+	void refreshSentMedia(const MTPMessageMedia *media);
+	void createServiceFromMtp(const MTPDmessage &message);
+	void createServiceFromMtp(const MTPDmessageService &message);
+	void applyTTL(const MTPDmessage &data);
+	void applyTTL(const MTPDmessageService &data);
+
+	void applyTTL(TimeId destroyAt);
+
+	// For an invoice button we replace the button text with a "Receipt" key.
+	// It should show the receipt for the payed invoice. Still let mobile apps do that.
+	void replaceBuyWithReceiptInMarkup();
+
+	void setSponsoredFrom(const Data::SponsoredFrom &from);
+
+	[[nodiscard]] PreparedServiceText preparePinnedText();
+	[[nodiscard]] PreparedServiceText prepareGameScoreText();
+	[[nodiscard]] PreparedServiceText preparePaymentSentText();
+	[[nodiscard]] PreparedServiceText prepareInvitedToCallText(
+		const std::vector<not_null<UserData*>> &users,
+		CallId linkCallId);
+	[[nodiscard]] PreparedServiceText prepareCallScheduledText(
+		TimeId scheduleDate);
 
 	const not_null<History*> _history;
 	const not_null<PeerData*> _from;
 	MessageFlags _flags = 0;
 
-	void setGroupId(MessageGroupId groupId);
-
-	void applyTTL(const MTPDmessage &data);
-	void applyTTL(const MTPDmessageService &data);
-	void applyTTL(TimeId destroyAt);
-
 	TextWithEntities _text;
 
-	struct SavedMediaData {
-		TextWithEntities text;
-		std::unique_ptr<Data::Media> media;
-	};
-
 	std::unique_ptr<SavedMediaData> _savedLocalEditMediaData;
 	std::unique_ptr<Data::Media> _media;
 	std::unique_ptr<Data::MessageReactions> _reactions;
 	crl::time _reactionsLastRefreshed = 0;
 
-private:
 	TimeId _date = 0;
 	TimeId _ttlDestroyAt = 0;
 
 	HistoryView::Element *_mainView = nullptr;
-	friend class HistoryView::Element;
-
 	MessageGroupId _groupId = MessageGroupId();
 
+	friend class HistoryView::Element;
+	friend class HistoryView::Message;
+	friend class HistoryView::Service;
+	friend class HistoryView::ServiceMessagePainter;
+
 };
-
-[[nodiscard]] Main::Session *SessionByUniqueId(uint64 sessionUniqueId);
-[[nodiscard]] HistoryItem *MessageByGlobalId(GlobalMsgId globalId);
-
-[[nodiscard]] QDateTime ItemDateTime(not_null<const HistoryItem*> item);
-[[nodiscard]] QString ItemDateText(
-	not_null<const HistoryItem*> item,
-	bool isUntilOnline);
-[[nodiscard]] bool IsItemScheduledUntilOnline(
-	not_null<const HistoryItem*> item);
-
-ClickHandlerPtr goToMessageClickHandler(
-	not_null<PeerData*> peer,
-	MsgId msgId,
-	FullMsgId returnToId = FullMsgId());
-ClickHandlerPtr goToMessageClickHandler(
-	not_null<HistoryItem*> item,
-	FullMsgId returnToId = FullMsgId());
diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp
index c9832365d..532a04359 100644
--- a/Telegram/SourceFiles/history/history_item_components.cpp
+++ b/Telegram/SourceFiles/history/history_item_components.cpp
@@ -18,7 +18,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/chat/chat_theme.h"
 #include "ui/painter.h"
 #include "history/history.h"
-#include "history/history_message.h"
+#include "history/history_item.h"
+#include "history/history_item_helpers.h"
 #include "history/view/history_view_message.h" // FromNameFg.
 #include "history/view/history_view_service_message.h"
 #include "history/view/media/history_view_document.h"
@@ -244,7 +245,7 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
 	text.setMarkedText(st::fwdTextStyle, phrase);
 
 	text.setLink(1, fromChannel
-		? goToMessageClickHandler(originalSender, originalId)
+		? JumpToMessageClickHandler(originalSender, originalId)
 		: originalSender
 		? originalSender->openLink()
 		: HiddenSenderInfo::ForwardClickHandler());
@@ -254,7 +255,7 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
 }
 
 bool HistoryMessageReply::updateData(
-		not_null<HistoryMessage*> holder,
+		not_null<HistoryItem*> holder,
 		bool force) {
 	const auto guard = gsl::finally([&] { refreshReplyToMedia(); });
 	if (!force) {
@@ -322,13 +323,13 @@ bool HistoryMessageReply::updateData(
 }
 
 void HistoryMessageReply::setReplyToLinkFrom(
-		not_null<HistoryMessage*> holder) {
+		not_null<HistoryItem*> holder) {
 	replyToLnk = replyToMsg
-		? goToMessageClickHandler(replyToMsg.get(), holder->fullId())
+		? JumpToMessageClickHandler(replyToMsg.get(), holder->fullId())
 		: nullptr;
 }
 
-void HistoryMessageReply::clearData(not_null<HistoryMessage*> holder) {
+void HistoryMessageReply::clearData(not_null<HistoryItem*> holder) {
 	replyToVia = nullptr;
 	if (replyToMsg) {
 		holder->history()->owner().unregisterDependentMessage(
@@ -341,7 +342,7 @@ void HistoryMessageReply::clearData(not_null<HistoryMessage*> holder) {
 }
 
 PeerData *HistoryMessageReply::replyToFrom(
-		not_null<HistoryMessage*> holder) const {
+		not_null<HistoryItem*> holder) const {
 	if (!replyToMsg) {
 		return nullptr;
 	} else if (holder->Has<HistoryMessageForwarded>()) {
@@ -356,7 +357,7 @@ PeerData *HistoryMessageReply::replyToFrom(
 }
 
 QString HistoryMessageReply::replyToFromName(
-		not_null<HistoryMessage*> holder) const {
+		not_null<HistoryItem*> holder) const {
 	if (!replyToMsg) {
 		return QString();
 	} else if (holder->Has<HistoryMessageForwarded>()) {
@@ -381,7 +382,7 @@ QString HistoryMessageReply::replyToFromName(
 }
 
 bool HistoryMessageReply::isNameUpdated(
-		not_null<HistoryMessage*> holder) const {
+		not_null<HistoryItem*> holder) const {
 	if (const auto from = replyToFrom(holder)) {
 		if (replyToVersion < from->nameVersion()) {
 			updateName(holder);
@@ -392,7 +393,7 @@ bool HistoryMessageReply::isNameUpdated(
 }
 
 void HistoryMessageReply::updateName(
-		not_null<HistoryMessage*> holder) const {
+		not_null<HistoryItem*> holder) const {
 	if (const auto name = replyToFromName(holder); !name.isEmpty()) {
 		replyToName.setText(st::fwdTextStyle, name, Ui::NameTextOptions());
 		if (const auto from = replyToFrom(holder)) {
@@ -423,7 +424,7 @@ void HistoryMessageReply::resize(int width) const {
 }
 
 void HistoryMessageReply::itemRemoved(
-		HistoryMessage *holder,
+		HistoryItem *holder,
 		HistoryItem *removed) {
 	if (replyToMsg.get() == removed) {
 		clearData(holder);
diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h
index 2b04d7784..1626a90cb 100644
--- a/Telegram/SourceFiles/history/history_item_components.h
+++ b/Telegram/SourceFiles/history/history_item_components.h
@@ -195,20 +195,19 @@ struct HistoryMessageReply
 		Expects(replyToVia == nullptr);
 	}
 
-	bool updateData(not_null<HistoryMessage*> holder, bool force = false);
+	bool updateData(not_null<HistoryItem*> holder, bool force = false);
 
 	// Must be called before destructor.
-	void clearData(not_null<HistoryMessage*> holder);
+	void clearData(not_null<HistoryItem*> holder);
 
-	[[nodiscard]] PeerData *replyToFrom(
-		not_null<HistoryMessage*> holder) const;
+	[[nodiscard]] PeerData *replyToFrom(not_null<HistoryItem*> holder) const;
 	[[nodiscard]] QString replyToFromName(
-		not_null<HistoryMessage*> holder) const;
+		not_null<HistoryItem*> holder) const;
 	[[nodiscard]] QString replyToFromName(not_null<PeerData*> peer) const;
-	[[nodiscard]] bool isNameUpdated(not_null<HistoryMessage*> holder) const;
-	void updateName(not_null<HistoryMessage*> holder) const;
+	[[nodiscard]] bool isNameUpdated(not_null<HistoryItem*> holder) const;
+	void updateName(not_null<HistoryItem*> holder) const;
 	void resize(int width) const;
-	void itemRemoved(HistoryMessage *holder, HistoryItem *removed);
+	void itemRemoved(HistoryItem *holder, HistoryItem *removed);
 
 	void paint(
 		Painter &p,
@@ -234,8 +233,7 @@ struct HistoryMessageReply
 	[[nodiscard]] ClickHandlerPtr replyToLink() const {
 		return replyToLnk;
 	}
-	void setReplyToLinkFrom(
-		not_null<HistoryMessage*> holder);
+	void setReplyToLinkFrom(not_null<HistoryItem*> holder);
 
 	void refreshReplyToMedia();
 
@@ -452,7 +450,7 @@ private:
 
 // Special type of Component for the channel actions log.
 struct HistoryMessageLogEntryOriginal
-	: public RuntimeComponent<HistoryMessageLogEntryOriginal, HistoryItem> {
+: public RuntimeComponent<HistoryMessageLogEntryOriginal, HistoryItem> {
 	HistoryMessageLogEntryOriginal();
 	HistoryMessageLogEntryOriginal(HistoryMessageLogEntryOriginal &&other);
 	HistoryMessageLogEntryOriginal &operator=(HistoryMessageLogEntryOriginal &&other);
@@ -462,6 +460,94 @@ struct HistoryMessageLogEntryOriginal
 
 };
 
+struct HistoryServiceData
+: public RuntimeComponent<HistoryServiceData, HistoryItem> {
+	std::vector<ClickHandlerPtr> textLinks;
+};
+
+struct HistoryServiceDependentData {
+	PeerId peerId = 0;
+	HistoryItem *msg = nullptr;
+	ClickHandlerPtr lnk;
+	MsgId msgId = 0;
+	MsgId topId = 0;
+	bool topicPost = false;
+};
+
+struct HistoryServicePinned
+: public RuntimeComponent<HistoryServicePinned, HistoryItem>
+, public HistoryServiceDependentData {
+};
+
+struct HistoryServiceTopicInfo
+: public RuntimeComponent<HistoryServiceTopicInfo, HistoryItem>
+, public HistoryServiceDependentData {
+	QString title;
+	DocumentId iconId = 0;
+	bool closed = false;
+	bool reopened = false;
+	bool reiconed = false;
+	bool renamed = false;
+	bool hidden = false;
+	bool unhidden = false;
+
+	[[nodiscard]] bool created() const {
+		return !closed
+			&& !reopened
+			&& !reiconed
+			&& !renamed
+			&& !hidden
+			&& !unhidden;
+	}
+};
+
+struct HistoryServiceGameScore
+: public RuntimeComponent<HistoryServiceGameScore, HistoryItem>
+, public HistoryServiceDependentData {
+	int score = 0;
+};
+
+struct HistoryServicePayment
+: public RuntimeComponent<HistoryServicePayment, HistoryItem>
+, public HistoryServiceDependentData {
+	QString slug;
+	QString amount;
+	ClickHandlerPtr invoiceLink;
+	bool recurringInit = false;
+	bool recurringUsed = false;
+};
+
+enum class HistorySelfDestructType {
+	Photo,
+	Video,
+};
+
+struct HistoryServiceSelfDestruct
+: public RuntimeComponent<HistoryServiceSelfDestruct, HistoryItem> {
+	using Type = HistorySelfDestructType;
+
+	Type type = Type::Photo;
+	crl::time timeToLive = 0;
+	crl::time destructAt = 0;
+};
+
+struct HistoryServiceOngoingCall
+: public RuntimeComponent<HistoryServiceOngoingCall, HistoryItem> {
+	CallId id = 0;
+	ClickHandlerPtr link;
+	rpl::lifetime lifetime;
+};
+
+struct HistoryServiceChatThemeChange
+: public RuntimeComponent<HistoryServiceChatThemeChange, HistoryItem> {
+	ClickHandlerPtr link;
+};
+
+struct HistoryServiceTTLChange
+: public RuntimeComponent<HistoryServiceTTLChange, HistoryItem> {
+	ClickHandlerPtr link;
+};
+
 class FileClickHandler;
 struct HistoryDocumentThumbed : public RuntimeComponent<HistoryDocumentThumbed, HistoryView::Document> {
 	std::shared_ptr<FileClickHandler> linksavel;
diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp
new file mode 100644
index 000000000..dd569d5c8
--- /dev/null
+++ b/Telegram/SourceFiles/history/history_item_helpers.cpp
@@ -0,0 +1,656 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "history/history_item_helpers.h"
+
+#include "calls/calls_instance.h"
+#include "data/notify/data_notify_settings.h"
+#include "data/data_chat_participant_status.h"
+#include "data/data_channel.h"
+#include "data/data_chat.h"
+#include "data/data_changes.h"
+#include "data/data_group_call.h"
+#include "data/data_forum.h"
+#include "data/data_forum_topic.h"
+#include "data/data_media_types.h"
+#include "data/data_message_reactions.h"
+#include "data/data_scheduled_messages.h"
+#include "data/data_session.h"
+#include "data/data_user.h"
+#include "history/history.h"
+#include "history/history_item.h"
+#include "history/history_item_components.h"
+#include "main/main_account.h"
+#include "main/main_domain.h"
+#include "main/main_session.h"
+#include "main/main_session_settings.h"
+#include "window/window_controller.h"
+#include "window/window_session_controller.h"
+#include "apiwrap.h"
+#include "base/unixtime.h"
+#include "core/application.h"
+#include "ui/text/format_values.h"
+#include "ui/text/text_utilities.h"
+#include "ui/text/text_entity.h"
+#include "ui/item_text_options.h"
+#include "lang/lang_keys.h"
+
+namespace {
+
+[[nodiscard]] bool HasInlineItems(const HistoryItemsList &items) {
+	for (const auto &item : items) {
+		if (item->viaBot()) {
+			return true;
+		}
+	}
+	return false;
+}
+
+bool PeerCallKnown(not_null<PeerData*> peer) {
+	if (peer->groupCall() != nullptr) {
+		return true;
+	} else if (const auto chat = peer->asChat()) {
+		return !(chat->flags() & ChatDataFlag::CallActive);
+	} else if (const auto channel = peer->asChannel()) {
+		return !(channel->flags() & ChannelDataFlag::CallActive);
+	}
+	return true;
+}
+
+} // namespace
+
+QString GetErrorTextForSending(
+		not_null<PeerData*> peer,
+		SendingErrorRequest request) {
+	const auto forum = request.topicRootId ? peer->forum() : nullptr;
+	const auto topic = forum
+		? forum->topicFor(request.topicRootId)
+		: nullptr;
+	if (!(topic ? topic->canWrite() : peer->canWrite())) {
+		return tr::lng_forward_cant(tr::now);
+	}
+
+	if (request.forward) {
+		for (const auto &item : *request.forward) {
+			if (const auto media = item->media()) {
+				const auto error = media->errorTextForForward(peer);
+				if (!error.isEmpty() && error != u"skip"_q) {
+					return error;
+				}
+			}
+		}
+	}
+	const auto error = Data::RestrictionError(
+		peer,
+		ChatRestriction::SendInline);
+	if (error && request.forward && HasInlineItems(*request.forward)) {
+		return *error;
+	}
+
+	if (peer->slowmodeApplied()) {
+		const auto hasText = (request.text && !request.text->empty());
+		const auto count = (hasText ? 1 : 0)
+			+ (request.forward ? int(request.forward->size()) : 0);
+		if (const auto history = peer->owner().historyLoaded(peer)) {
+			if (!request.ignoreSlowmodeCountdown
+				&& (history->latestSendingMessage() != nullptr)
+				&& (count > 0)) {
+				return tr::lng_slowmode_no_many(tr::now);
+			}
+		}
+		if (request.text && request.text->text.size() > MaxMessageSize) {
+			return tr::lng_slowmode_too_long(tr::now);
+		} else if (hasText && count > 1) {
+			return tr::lng_slowmode_no_many(tr::now);
+		} else if (count > 1) {
+			const auto albumForward = [&] {
+				const auto first = request.forward->front();
+				if (const auto groupId = first->groupId()) {
+					for (const auto &item : *request.forward) {
+						if (item->groupId() != groupId) {
+							return false;
+						}
+					}
+					return true;
+				}
+				return false;
+			}();
+			if (!albumForward) {
+				return tr::lng_slowmode_no_many(tr::now);
+			}
+		}
+	}
+	if (const auto left = peer->slowmodeSecondsLeft()) {
+		if (!request.ignoreSlowmodeCountdown) {
+			return tr::lng_slowmode_enabled(
+				tr::now,
+				lt_left,
+				Ui::FormatDurationWordsSlowmode(left));
+		}
+	}
+
+	return QString();
+}
+
+QString GetErrorTextForSending(
+		not_null<Data::Thread*> thread,
+		SendingErrorRequest request) {
+	request.topicRootId = thread->topicRootId();
+	return GetErrorTextForSending(thread->peer(), std::move(request));
+}
+
+void RequestDependentMessageData(
+		not_null<HistoryItem*> item,
+		PeerId peerId,
+		MsgId msgId) {
+	if (!IsServerMsgId(msgId)) {
+		return;
+	}
+	const auto fullId = item->fullId();
+	const auto history = item->history();
+	const auto session = &history->session();
+	const auto done = [=] {
+		if (const auto item = session->data().message(fullId)) {
+			item->updateDependencyItem();
+		}
+	};
+	history->session().api().requestMessageData(
+		(peerId ? history->owner().peer(peerId) : history->peer),
+		msgId,
+		done);
+}
+
+MessageFlags NewMessageFlags(not_null<PeerData*> peer) {
+	return MessageFlag::BeingSent
+		| (peer->isSelf() ? MessageFlag() : MessageFlag::Outgoing);
+}
+
+bool ShouldSendSilent(
+		not_null<PeerData*> peer,
+		const Api::SendOptions &options) {
+	return options.silent
+		|| (peer->isBroadcast()
+			&& peer->owner().notifySettings().silentPosts(peer))
+		|| (peer->session().supportMode()
+			&& peer->session().settings().supportAllSilent());
+}
+
+HistoryItem *LookupReplyTo(not_null<History*> history, MsgId replyToId) {
+	const auto &owner = history->owner();
+	return owner.message(history->peer, replyToId);
+}
+
+MsgId LookupReplyToTop(HistoryItem *replyTo) {
+	return replyTo ? replyTo->replyToTop() : 0;
+}
+
+bool LookupReplyIsTopicPost(HistoryItem *replyTo) {
+	return replyTo
+		&& (replyTo->topicRootId() != Data::ForumTopic::kGeneralId);
+}
+
+TextWithEntities DropCustomEmoji(TextWithEntities text) {
+	text.entities.erase(
+		ranges::remove(
+			text.entities,
+			EntityType::CustomEmoji,
+			&EntityInText::type),
+		text.entities.end());
+	return text;
+}
+
+Main::Session *SessionByUniqueId(uint64 sessionUniqueId) {
+	if (!sessionUniqueId) {
+		return nullptr;
+	}
+	for (const auto &[index, account] : Core::App().domain().accounts()) {
+		if (const auto session = account->maybeSession()) {
+			if (session->uniqueId() == sessionUniqueId) {
+				return session;
+			}
+		}
+	}
+	return nullptr;
+}
+
+HistoryItem *MessageByGlobalId(GlobalMsgId globalId) {
+	const auto sessionId = globalId.itemId ? globalId.sessionUniqueId : 0;
+	if (const auto session = SessionByUniqueId(sessionId)) {
+		return session->data().message(globalId.itemId);
+	}
+	return nullptr;
+}
+
+QDateTime ItemDateTime(not_null<const HistoryItem*> item) {
+	return base::unixtime::parse(item->date());
+}
+
+QString ItemDateText(not_null<const HistoryItem*> item, bool isUntilOnline) {
+	const auto dateText = langDayOfMonthFull(ItemDateTime(item).date());
+	return !item->isScheduled()
+		? dateText
+		: isUntilOnline
+			? tr::lng_scheduled_date_until_online(tr::now)
+			: tr::lng_scheduled_date(tr::now, lt_date, dateText);
+}
+
+bool IsItemScheduledUntilOnline(not_null<const HistoryItem*> item) {
+	return item->isScheduled()
+		&& (item->date() ==
+			Data::ScheduledMessages::kScheduledUntilOnlineTimestamp);
+}
+
+ClickHandlerPtr JumpToMessageClickHandler(
+		not_null<HistoryItem*> item,
+		FullMsgId returnToId) {
+	return JumpToMessageClickHandler(
+		item->history()->peer,
+		item->id,
+		returnToId);
+}
+
+ClickHandlerPtr JumpToMessageClickHandler(
+		not_null<PeerData*> peer,
+		MsgId msgId,
+		FullMsgId returnToId) {
+	return std::make_shared<LambdaClickHandler>([=] {
+		const auto separate = Core::App().separateWindowForPeer(peer);
+		const auto controller = separate
+			? separate->sessionController()
+			: peer->session().tryResolveWindow();
+		if (controller) {
+			auto params = Window::SectionShow{
+				Window::SectionShow::Way::Forward
+			};
+			params.origin = Window::SectionShow::OriginMessage{
+				returnToId
+			};
+			if (const auto item = peer->owner().message(peer, msgId)) {
+				controller->showMessage(item, params);
+			} else {
+				controller->showPeerHistory(peer, params, msgId);
+			}
+		}
+	});
+}
+
+MessageFlags FlagsFromMTP(
+		MsgId id,
+		MTPDmessage::Flags flags,
+		MessageFlags localFlags) {
+	using Flag = MessageFlag;
+	using MTP = MTPDmessage::Flag;
+	return localFlags
+		| (IsServerMsgId(id) ? Flag::HistoryEntry : Flag())
+		| ((flags & MTP::f_out) ? Flag::Outgoing : Flag())
+		| ((flags & MTP::f_mentioned) ? Flag::MentionsMe : Flag())
+		| ((flags & MTP::f_media_unread) ? Flag::MediaIsUnread : Flag())
+		| ((flags & MTP::f_silent) ? Flag::Silent : Flag())
+		| ((flags & MTP::f_post) ? Flag::Post : Flag())
+		| ((flags & MTP::f_legacy) ? Flag::Legacy : Flag())
+		| ((flags & MTP::f_edit_hide) ? Flag::HideEdited : Flag())
+		| ((flags & MTP::f_pinned) ? Flag::Pinned : Flag())
+		| ((flags & MTP::f_from_id) ? Flag::HasFromId : Flag())
+		| ((flags & MTP::f_reply_to) ? Flag::HasReplyInfo : Flag())
+		| ((flags & MTP::f_reply_markup) ? Flag::HasReplyMarkup : Flag())
+		| ((flags & MTP::f_from_scheduled) ? Flag::IsOrWasScheduled : Flag())
+		| ((flags & MTP::f_views) ? Flag::HasViews : Flag())
+		| ((flags & MTP::f_noforwards) ? Flag::NoForwards : Flag());
+}
+
+MessageFlags FlagsFromMTP(
+		MsgId id,
+		MTPDmessageService::Flags flags,
+		MessageFlags localFlags) {
+	using Flag = MessageFlag;
+	using MTP = MTPDmessageService::Flag;
+	return localFlags
+		| (IsServerMsgId(id) ? Flag::HistoryEntry : Flag())
+		| ((flags & MTP::f_out) ? Flag::Outgoing : Flag())
+		| ((flags & MTP::f_mentioned) ? Flag::MentionsMe : Flag())
+		| ((flags & MTP::f_media_unread) ? Flag::MediaIsUnread : Flag())
+		| ((flags & MTP::f_silent) ? Flag::Silent : Flag())
+		| ((flags & MTP::f_post) ? Flag::Post : Flag())
+		| ((flags & MTP::f_legacy) ? Flag::Legacy : Flag())
+		| ((flags & MTP::f_from_id) ? Flag::HasFromId : Flag())
+		| ((flags & MTP::f_reply_to) ? Flag::HasReplyInfo : Flag());
+}
+
+MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) {
+	if (const auto id = action.replyTo) {
+		const auto to = LookupReplyTo(action.history, id);
+		if (const auto replyToTop = LookupReplyToTop(to)) {
+			using Flag = MTPDmessageReplyHeader::Flag;
+			return MTP_messageReplyHeader(
+				MTP_flags(Flag::f_reply_to_top_id
+					| (LookupReplyIsTopicPost(to)
+						? Flag::f_forum_topic
+						: Flag(0))),
+				MTP_int(id),
+				MTPPeer(),
+				MTP_int(replyToTop));
+		}
+		return MTP_messageReplyHeader(
+			MTP_flags(0),
+			MTP_int(id),
+			MTPPeer(),
+			MTPint());
+	}
+	return MTPMessageReplyHeader();
+}
+
+MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
+	using Result = MediaCheckResult;
+	return media.match([](const MTPDmessageMediaEmpty &) {
+		return Result::Good;
+	}, [](const MTPDmessageMediaContact &) {
+		return Result::Good;
+	}, [](const MTPDmessageMediaGeo &data) {
+		return data.vgeo().match([](const MTPDgeoPoint &) {
+			return Result::Good;
+		}, [](const MTPDgeoPointEmpty &) {
+			return Result::Empty;
+		});
+	}, [](const MTPDmessageMediaVenue &data) {
+		return data.vgeo().match([](const MTPDgeoPoint &) {
+			return Result::Good;
+		}, [](const MTPDgeoPointEmpty &) {
+			return Result::Empty;
+		});
+	}, [](const MTPDmessageMediaGeoLive &data) {
+		return data.vgeo().match([](const MTPDgeoPoint &) {
+			return Result::Good;
+		}, [](const MTPDgeoPointEmpty &) {
+			return Result::Empty;
+		});
+	}, [](const MTPDmessageMediaPhoto &data) {
+		const auto photo = data.vphoto();
+		if (data.vttl_seconds()) {
+			return Result::HasTimeToLive;
+		} else if (!photo) {
+			return Result::Empty;
+		}
+		return photo->match([](const MTPDphoto &) {
+			return Result::Good;
+		}, [](const MTPDphotoEmpty &) {
+			return Result::Empty;
+		});
+	}, [](const MTPDmessageMediaDocument &data) {
+		const auto document = data.vdocument();
+		if (data.vttl_seconds()) {
+			return Result::HasTimeToLive;
+		} else if (!document) {
+			return Result::Empty;
+		}
+		return document->match([](const MTPDdocument &) {
+			return Result::Good;
+		}, [](const MTPDdocumentEmpty &) {
+			return Result::Empty;
+		});
+	}, [](const MTPDmessageMediaWebPage &data) {
+		return data.vwebpage().match([](const MTPDwebPage &) {
+			return Result::Good;
+		}, [](const MTPDwebPageEmpty &) {
+			return Result::Good;
+		}, [](const MTPDwebPagePending &) {
+			return Result::Good;
+		}, [](const MTPDwebPageNotModified &) {
+			return Result::Unsupported;
+		});
+	}, [](const MTPDmessageMediaGame &data) {
+		return data.vgame().match([](const MTPDgame &) {
+			return Result::Good;
+		});
+	}, [](const MTPDmessageMediaInvoice &) {
+		return Result::Good;
+	}, [](const MTPDmessageMediaPoll &) {
+		return Result::Good;
+	}, [](const MTPDmessageMediaDice &) {
+		return Result::Good;
+	}, [](const MTPDmessageMediaUnsupported &) {
+		return Result::Unsupported;
+	});
+}
+
+[[nodiscard]] CallId CallIdFromInput(const MTPInputGroupCall &data) {
+	return data.match([&](const MTPDinputGroupCall &data) {
+		return data.vid().v;
+	});
+}
+
+std::vector<not_null<UserData*>> ParseInvitedToCallUsers(
+		not_null<HistoryItem*> item,
+		const QVector<MTPlong> &users) {
+	auto &owner = item->history()->owner();
+	return ranges::views::all(
+		users
+	) | ranges::views::transform([&](const MTPlong &id) {
+		return owner.user(id.v);
+	}) | ranges::to_vector;
+}
+
+PreparedServiceText GenerateJoinedText(
+		not_null<History*> history,
+		not_null<UserData*> inviter,
+		bool viaRequest) {
+	if (inviter->id != history->session().userPeerId()) {
+		auto result = PreparedServiceText();
+		result.links.push_back(inviter->createOpenLink());
+		result.text = (history->peer->isMegagroup()
+			? tr::lng_action_add_you_group
+			: tr::lng_action_add_you)(
+				tr::now,
+				lt_from,
+				Ui::Text::Link(inviter->name(), QString()),
+				Ui::Text::WithEntities);
+		return result;
+	} else if (history->peer->isMegagroup()) {
+		if (viaRequest) {
+			return { tr::lng_action_you_joined_by_request(
+				tr::now,
+				Ui::Text::WithEntities) };
+		}
+		auto self = history->session().user();
+		auto result = PreparedServiceText();
+		result.links.push_back(self->createOpenLink());
+		result.text = tr::lng_action_user_joined(
+			tr::now,
+			lt_from,
+			Ui::Text::Link(self->name(), QString()),
+			Ui::Text::WithEntities);
+		return result;
+	}
+	return { viaRequest
+		? tr::lng_action_you_joined_by_request_channel(
+			tr::now,
+			Ui::Text::WithEntities)
+		: tr::lng_action_you_joined(tr::now, Ui::Text::WithEntities) };
+}
+
+not_null<HistoryItem*> GenerateJoinedMessage(
+		not_null<History*> history,
+		TimeId inviteDate,
+		not_null<UserData*> inviter,
+		bool viaRequest) {
+	return history->makeMessage(
+		history->owner().nextLocalMessageId(),
+		MessageFlag::Local,
+		inviteDate,
+		GenerateJoinedText(history, inviter, viaRequest));
+}
+
+std::optional<bool> PeerHasThisCall(
+		not_null<PeerData*> peer,
+		CallId id) {
+	const auto call = peer->groupCall();
+	return call
+		? std::make_optional(call->id() == id)
+		: PeerCallKnown(peer)
+		? std::make_optional(false)
+		: std::nullopt;
+}
+[[nodiscard]] rpl::producer<bool> PeerHasThisCallValue(
+		not_null<PeerData*> peer,
+		CallId id) {
+	return peer->session().changes().peerFlagsValue(
+		peer,
+		Data::PeerUpdate::Flag::GroupCall
+	) | rpl::filter([=] {
+		return PeerCallKnown(peer);
+	}) | rpl::map([=] {
+		const auto call = peer->groupCall();
+		return (call && call->id() == id);
+	}) | rpl::distinct_until_changed(
+	) | rpl::take_while([=](bool hasThisCall) {
+		return hasThisCall;
+	}) | rpl::then(
+		rpl::single(false)
+	);
+}
+
+[[nodiscard]] ClickHandlerPtr GroupCallClickHandler(
+		not_null<PeerData*> peer,
+		CallId callId) {
+	return std::make_shared<LambdaClickHandler>([=] {
+		const auto call = peer->groupCall();
+		if (call && call->id() == callId) {
+			const auto &windows = peer->session().windows();
+			if (windows.empty()) {
+				Core::App().domain().activate(&peer->session().account());
+				if (windows.empty()) {
+					return;
+				}
+			}
+			windows.front()->startOrJoinGroupCall(peer, {});
+		}
+	});
+}
+
+[[nodiscard]] MessageFlags FinalizeMessageFlags(MessageFlags flags) {
+	if (!(flags & MessageFlag::FakeHistoryItem)
+		&& !(flags & MessageFlag::IsOrWasScheduled)
+		&& !(flags & MessageFlag::AdminLogEntry)) {
+		flags |= MessageFlag::HistoryEntry;
+	}
+	return flags;
+}
+
+using OnStackUsers = std::array<UserData*, kMaxUnreadReactions>;
+[[nodiscard]] OnStackUsers LookupRecentUnreadReactedUsers(
+		not_null<HistoryItem*> item) {
+	auto result = OnStackUsers();
+	auto index = 0;
+	for (const auto &[emoji, reactions] : item->recentReactions()) {
+		for (const auto &reaction : reactions) {
+			if (!reaction.unread) {
+				continue;
+			}
+			if (const auto user = reaction.peer->asUser()) {
+				result[index++] = user;
+				if (index == result.size()) {
+					return result;
+				}
+			}
+		}
+	}
+	return result;
+}
+
+void CheckReactionNotificationSchedule(
+		not_null<HistoryItem*> item,
+		const OnStackUsers &wasUsers) {
+	// Call to addToUnreadThings may have read the reaction already.
+	if (!item->hasUnreadReaction()) {
+		return;
+	}
+	for (const auto &[emoji, reactions] : item->recentReactions()) {
+		for (const auto &reaction : reactions) {
+			if (!reaction.unread) {
+				continue;
+			}
+			const auto user = reaction.peer->asUser();
+			if (!user
+				|| !user->isContact()
+				|| ranges::contains(wasUsers, user)) {
+				continue;
+			}
+			using Status = PeerData::BlockStatus;
+			if (user->blockStatus() == Status::Unknown) {
+				user->updateFull();
+			}
+			const auto notification = Data::ItemNotification{
+				.item = item,
+				.reactionSender = user,
+				.type = Data::ItemNotificationType::Reaction,
+			};
+			item->notificationThread()->pushNotification(notification);
+			Core::App().notifications().schedule(notification);
+			return;
+		}
+	}
+}
+
+[[nodiscard]] MessageFlags NewForwardedFlags(
+		not_null<PeerData*> peer,
+		PeerId from,
+		not_null<HistoryItem*> fwd) {
+	auto result = NewMessageFlags(peer);
+	if (from) {
+		result |= MessageFlag::HasFromId;
+	}
+	if (const auto media = fwd->media()) {
+		if ((!peer->isChannel() || peer->isMegagroup())
+			&& media->forwardedBecomesUnread()) {
+			result |= MessageFlag::MediaIsUnread;
+		}
+	}
+	if (fwd->hasViews()) {
+		result |= MessageFlag::HasViews;
+	}
+	return result;
+}
+
+[[nodiscard]] bool CopyMarkupToForward(not_null<const HistoryItem*> item) {
+	auto mediaOriginal = item->media();
+	if (mediaOriginal && mediaOriginal->game()) {
+		// Copy inline keyboard when forwarding messages with a game.
+		return true;
+	}
+	const auto markup = item->inlineReplyMarkup();
+	if (!markup) {
+		return false;
+	}
+	using Type = HistoryMessageMarkupButton::Type;
+	for (const auto &row : markup->data.rows) {
+		for (const auto &button : row) {
+			const auto switchInline = (button.type == Type::SwitchInline)
+				|| (button.type == Type::SwitchInlineSame);
+			const auto url = (button.type == Type::Url)
+				|| (button.type == Type::Auth);
+			if ((!switchInline || !item->viaBot()) && !url) {
+				return false;
+			}
+		}
+	}
+	return true;
+}
+
+[[nodiscard]] TextWithEntities EnsureNonEmpty(
+		const TextWithEntities &text) {
+	return !text.text.isEmpty() ? text : TextWithEntities{ u":-("_q };
+}
+
+[[nodiscard]] TextWithEntities UnsupportedMessageText() {
+	const auto siteLink = u"https://desktop.telegram.org"_q;
+	auto result = TextWithEntities{
+		tr::lng_message_unsupported(tr::now, lt_link, siteLink)
+	};
+	TextUtilities::ParseEntities(result, Ui::ItemTextNoMonoOptions().flags);
+	result.entities.push_front(
+		EntityInText(EntityType::Italic, 0, result.text.size()));
+	return result;
+}
diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h
new file mode 100644
index 000000000..46b772517
--- /dev/null
+++ b/Telegram/SourceFiles/history/history_item_helpers.h
@@ -0,0 +1,133 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+class History;
+
+namespace Api {
+struct SendOptions;
+struct SendAction;
+} // namespace Api
+
+namespace Data {
+class Thread;
+} // namespace Data
+
+namespace Main {
+class Session;
+} // namespace Main
+
+struct PreparedServiceText {
+	TextWithEntities text;
+	std::vector<ClickHandlerPtr> links;
+};
+
+[[nodiscard]] MessageFlags FlagsFromMTP(
+	MsgId id,
+	MTPDmessage::Flags flags,
+	MessageFlags localFlags);
+[[nodiscard]] MessageFlags FlagsFromMTP(
+	MsgId id,
+	MTPDmessageService::Flags flags,
+	MessageFlags localFlags);
+[[nodiscard]] MTPMessageReplyHeader NewMessageReplyHeader(
+	const Api::SendAction &action);
+
+enum class MediaCheckResult {
+	Good,
+	Unsupported,
+	Empty,
+	HasTimeToLive,
+};
+[[nodiscard]] MediaCheckResult CheckMessageMedia(
+	const MTPMessageMedia &media);
+[[nodiscard]] CallId CallIdFromInput(const MTPInputGroupCall &data);
+
+[[nodiscard]] std::vector<not_null<UserData*>> ParseInvitedToCallUsers(
+	not_null<HistoryItem*> item,
+	const QVector<MTPlong> &users);
+
+inline constexpr auto kMaxUnreadReactions = 5; // Now 3, but just in case.
+using OnStackUsers = std::array<UserData*, kMaxUnreadReactions>;
+[[nodiscard]] OnStackUsers LookupRecentUnreadReactedUsers(
+	not_null<HistoryItem*> item);
+void CheckReactionNotificationSchedule(
+	not_null<HistoryItem*> item,
+	const OnStackUsers &wasUsers);
+[[nodiscard]] MessageFlags NewForwardedFlags(
+	not_null<PeerData*> peer,
+	PeerId from,
+	not_null<HistoryItem*> fwd);
+[[nodiscard]] MessageFlags FinalizeMessageFlags(MessageFlags flags);
+[[nodiscard]] bool CopyMarkupToForward(not_null<const HistoryItem*> item);
+[[nodiscard]] TextWithEntities EnsureNonEmpty(
+	const TextWithEntities &text = TextWithEntities());
+[[nodiscard]] TextWithEntities UnsupportedMessageText();
+
+void RequestDependentMessageData(
+	not_null<HistoryItem*> item,
+	PeerId peerId,
+	MsgId msgId);
+[[nodiscard]] MessageFlags NewMessageFlags(not_null<PeerData*> peer);
+[[nodiscard]] bool ShouldSendSilent(
+	not_null<PeerData*> peer,
+	const Api::SendOptions &options);
+[[nodiscard]] HistoryItem *LookupReplyTo(
+	not_null<History*> history,
+	MsgId replyToId);
+[[nodiscard]] MsgId LookupReplyToTop(HistoryItem *replyTo);
+[[nodiscard]] bool LookupReplyIsTopicPost(HistoryItem *replyTo);
+
+struct SendingErrorRequest {
+	MsgId topicRootId = 0;
+	const HistoryItemsList *forward = nullptr;
+	const TextWithTags *text = nullptr;
+	bool ignoreSlowmodeCountdown = false;
+};
+[[nodiscard]] QString GetErrorTextForSending(
+	not_null<PeerData*> peer,
+	SendingErrorRequest request);
+[[nodiscard]] QString GetErrorTextForSending(
+	not_null<Data::Thread*> thread,
+	SendingErrorRequest request);
+
+[[nodiscard]] TextWithEntities DropCustomEmoji(TextWithEntities text);
+
+[[nodiscard]] Main::Session *SessionByUniqueId(uint64 sessionUniqueId);
+[[nodiscard]] HistoryItem *MessageByGlobalId(GlobalMsgId globalId);
+
+[[nodiscard]] QDateTime ItemDateTime(not_null<const HistoryItem*> item);
+[[nodiscard]] QString ItemDateText(
+	not_null<const HistoryItem*> item,
+	bool isUntilOnline);
+[[nodiscard]] bool IsItemScheduledUntilOnline(
+	not_null<const HistoryItem*> item);
+
+[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler(
+	not_null<PeerData*> peer,
+	MsgId msgId,
+	FullMsgId returnToId = FullMsgId());
+[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler(
+	not_null<HistoryItem*> item,
+	FullMsgId returnToId = FullMsgId());
+
+[[nodiscard]] not_null<HistoryItem*> GenerateJoinedMessage(
+	not_null<History*> history,
+	TimeId inviteDate,
+	not_null<UserData*> inviter,
+	bool viaRequest);
+
+[[nodiscard]] std::optional<bool> PeerHasThisCall(
+	not_null<PeerData*> peer,
+	CallId id);
+[[nodiscard]] rpl::producer<bool> PeerHasThisCallValue(
+	not_null<PeerData*> peer,
+	CallId id);
+[[nodiscard]] ClickHandlerPtr GroupCallClickHandler(
+	not_null<PeerData*> peer,
+	CallId callId);
diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp
deleted file mode 100644
index b9d939634..000000000
--- a/Telegram/SourceFiles/history/history_message.cpp
+++ /dev/null
@@ -1,1883 +0,0 @@
-/*
-This file is part of Telegram Desktop,
-the official desktop application for the Telegram messaging service.
-
-For license and copyright information please follow this link:
-https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
-*/
-#include "history/history_message.h"
-
-#include "lang/lang_keys.h"
-#include "apiwrap.h"
-#include "api/api_text_entities.h"
-#include "history/history.h"
-#include "history/history_service.h"
-#include "history/history_item_components.h"
-#include "history/history_unread_things.h"
-#include "history/view/history_view_service_message.h"
-#include "history/view/history_view_context_menu.h" // CopyPostLink.
-#include "history/view/media/history_view_media.h" // AddTimestampLinks.
-#include "chat_helpers/stickers_emoji_pack.h"
-#include "main/main_session.h"
-#include "main/main_session_settings.h"
-#include "api/api_updates.h"
-#include "boxes/share_box.h"
-#include "ui/text/text_isolated_emoji.h"
-#include "ui/text/format_values.h"
-#include "storage/storage_shared_media.h"
-#include "mtproto/mtproto_config.h"
-#include "data/notify/data_notify_settings.h"
-#include "data/data_session.h"
-#include "data/data_changes.h"
-#include "data/data_media_types.h"
-#include "data/data_channel.h"
-#include "data/data_forum_topic.h"
-#include "data/data_forum.h"
-#include "data/data_user.h"
-#include "data/data_web_page.h"
-#include "data/data_sponsored_messages.h"
-#include "data/data_scheduled_messages.h"
-#include "styles/style_dialogs.h"
-#include "styles/style_widgets.h"
-#include "styles/style_chat.h"
-#include "styles/style_window.h"
-
-namespace {
-
-[[nodiscard]] MessageFlags NewForwardedFlags(
-		not_null<PeerData*> peer,
-		PeerId from,
-		not_null<HistoryItem*> fwd) {
-	auto result = NewMessageFlags(peer);
-	if (from) {
-		result |= MessageFlag::HasFromId;
-	}
-	if (const auto media = fwd->media()) {
-		if ((!peer->isChannel() || peer->isMegagroup())
-			&& media->forwardedBecomesUnread()) {
-			result |= MessageFlag::MediaIsUnread;
-		}
-	}
-	if (fwd->hasViews()) {
-		result |= MessageFlag::HasViews;
-	}
-	return result;
-}
-
-[[nodiscard]] bool CopyMarkupToForward(not_null<const HistoryItem*> item) {
-	auto mediaOriginal = item->media();
-	if (mediaOriginal && mediaOriginal->game()) {
-		// Copy inline keyboard when forwarding messages with a game.
-		return true;
-	}
-	const auto markup = item->inlineReplyMarkup();
-	if (!markup) {
-		return false;
-	}
-	using Type = HistoryMessageMarkupButton::Type;
-	for (const auto &row : markup->data.rows) {
-		for (const auto &button : row) {
-			const auto switchInline = (button.type == Type::SwitchInline)
-				|| (button.type == Type::SwitchInlineSame);
-			const auto url = (button.type == Type::Url)
-				|| (button.type == Type::Auth);
-			if ((!switchInline || !item->viaBot()) && !url) {
-				return false;
-			}
-		}
-	}
-	return true;
-}
-
-[[nodiscard]] bool HasInlineItems(const HistoryItemsList &items) {
-	for (const auto &item : items) {
-		if (item->viaBot()) {
-			return true;
-		}
-	}
-	return false;
-}
-
-[[nodiscard]] TextWithEntities EnsureNonEmpty(
-		const TextWithEntities &text = TextWithEntities()) {
-	return !text.text.isEmpty() ? text : TextWithEntities{ u":-("_q };
-}
-
-} // namespace
-
-QString GetErrorTextForSending(
-		not_null<PeerData*> peer,
-		SendingErrorRequest request) {
-	const auto forum = request.topicRootId ? peer->forum() : nullptr;
-	const auto topic = forum
-		? forum->topicFor(request.topicRootId)
-		: nullptr;
-	if (!(topic ? topic->canWrite() : peer->canWrite())) {
-		return tr::lng_forward_cant(tr::now);
-	}
-
-	if (request.forward) {
-		for (const auto &item : *request.forward) {
-			if (const auto media = item->media()) {
-				const auto error = media->errorTextForForward(peer);
-				if (!error.isEmpty() && error != u"skip"_q) {
-					return error;
-				}
-			}
-		}
-	}
-	const auto error = Data::RestrictionError(
-		peer,
-		ChatRestriction::SendInline);
-	if (error && request.forward && HasInlineItems(*request.forward)) {
-		return *error;
-	}
-
-	if (peer->slowmodeApplied()) {
-		const auto hasText = (request.text && !request.text->empty());
-		const auto count = (hasText ? 1 : 0)
-			+ (request.forward ? int(request.forward->size()) : 0);
-		if (const auto history = peer->owner().historyLoaded(peer)) {
-			if (!request.ignoreSlowmodeCountdown
-				&& (history->latestSendingMessage() != nullptr)
-				&& (count > 0)) {
-				return tr::lng_slowmode_no_many(tr::now);
-			}
-		}
-		if (request.text && request.text->text.size() > MaxMessageSize) {
-			return tr::lng_slowmode_too_long(tr::now);
-		} else if (hasText && count > 1) {
-			return tr::lng_slowmode_no_many(tr::now);
-		} else if (count > 1) {
-			const auto albumForward = [&] {
-				const auto first = request.forward->front();
-				if (const auto groupId = first->groupId()) {
-					for (const auto &item : *request.forward) {
-						if (item->groupId() != groupId) {
-							return false;
-						}
-					}
-					return true;
-				}
-				return false;
-			}();
-			if (!albumForward) {
-				return tr::lng_slowmode_no_many(tr::now);
-			}
-		}
-	}
-	if (const auto left = peer->slowmodeSecondsLeft()) {
-		if (!request.ignoreSlowmodeCountdown) {
-			return tr::lng_slowmode_enabled(
-				tr::now,
-				lt_left,
-				Ui::FormatDurationWordsSlowmode(left));
-		}
-	}
-
-	return QString();
-}
-
-QString GetErrorTextForSending(
-		not_null<Data::Thread*> thread,
-		SendingErrorRequest request) {
-	request.topicRootId = thread->topicRootId();
-	return GetErrorTextForSending(thread->peer(), std::move(request));
-}
-
-void RequestDependentMessageData(
-		not_null<HistoryItem*> item,
-		PeerId peerId,
-		MsgId msgId) {
-	if (!IsServerMsgId(msgId)) {
-		return;
-	}
-	const auto fullId = item->fullId();
-	const auto history = item->history();
-	const auto session = &history->session();
-	const auto done = [=] {
-		if (const auto item = session->data().message(fullId)) {
-			item->updateDependencyItem();
-		}
-	};
-	history->session().api().requestMessageData(
-		(peerId ? history->owner().peer(peerId) : history->peer),
-		msgId,
-		done);
-}
-
-MessageFlags NewMessageFlags(not_null<PeerData*> peer) {
-	return MessageFlag::BeingSent
-		| (peer->isSelf() ? MessageFlag() : MessageFlag::Outgoing);
-}
-
-bool ShouldSendSilent(
-		not_null<PeerData*> peer,
-		const Api::SendOptions &options) {
-	return options.silent
-		|| (peer->isBroadcast()
-			&& peer->owner().notifySettings().silentPosts(peer))
-		|| (peer->session().supportMode()
-			&& peer->session().settings().supportAllSilent());
-}
-
-HistoryItem *LookupReplyTo(not_null<History*> history, MsgId replyToId) {
-	const auto &owner = history->owner();
-	return owner.message(history->peer, replyToId);
-}
-
-MsgId LookupReplyToTop(HistoryItem *replyTo) {
-	return replyTo ? replyTo->replyToTop() : 0;
-}
-
-bool LookupReplyIsTopicPost(HistoryItem *replyTo) {
-	return replyTo
-		&& (replyTo->topicRootId() != Data::ForumTopic::kGeneralId);
-}
-
-MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) {
-	if (const auto id = action.replyTo) {
-		const auto to = LookupReplyTo(action.history, id);
-		if (const auto replyToTop = LookupReplyToTop(to)) {
-			using Flag = MTPDmessageReplyHeader::Flag;
-			return MTP_messageReplyHeader(
-				MTP_flags(Flag::f_reply_to_top_id
-					| (LookupReplyIsTopicPost(to)
-						? Flag::f_forum_topic
-						: Flag(0))),
-				MTP_int(id),
-				MTPPeer(),
-				MTP_int(replyToTop));
-		}
-		return MTP_messageReplyHeader(
-			MTP_flags(0),
-			MTP_int(id),
-			MTPPeer(),
-			MTPint());
-	}
-	return MTPMessageReplyHeader();
-}
-
-TextWithEntities DropCustomEmoji(TextWithEntities text) {
-	text.entities.erase(
-		ranges::remove(
-			text.entities,
-			EntityType::CustomEmoji,
-			&EntityInText::type),
-		text.entities.end());
-	return text;
-}
-
-struct HistoryMessage::CreateConfig {
-	PeerId replyToPeer = 0;
-	MsgId replyTo = 0;
-	MsgId replyToTop = 0;
-	bool replyIsTopicPost = false;
-	UserId viaBotId = 0;
-	int viewsCount = -1;
-	QString author;
-	PeerId senderOriginal = 0;
-	QString senderNameOriginal;
-	QString forwardPsaType;
-	MsgId originalId = 0;
-	PeerId savedFromPeer = 0;
-	MsgId savedFromMsgId = 0;
-	QString authorOriginal;
-	TimeId originalDate = 0;
-	TimeId editDate = 0;
-	HistoryMessageMarkupData markup;
-	HistoryMessageRepliesData replies;
-	bool imported = false;
-
-	// For messages created from existing messages (forwarded).
-	const HistoryMessageReplyMarkup *inlineMarkup = nullptr;
-};
-
-void HistoryMessage::FillForwardedInfo(
-		CreateConfig &config,
-		const MTPDmessageFwdHeader &data) {
-	if (const auto fromId = data.vfrom_id()) {
-		config.senderOriginal = peerFromMTP(*fromId);
-	}
-	config.originalDate = data.vdate().v;
-	config.senderNameOriginal = qs(data.vfrom_name().value_or_empty());
-	config.forwardPsaType = qs(data.vpsa_type().value_or_empty());
-	config.originalId = data.vchannel_post().value_or_empty();
-	config.authorOriginal = qs(data.vpost_author().value_or_empty());
-	const auto savedFromPeer = data.vsaved_from_peer();
-	const auto savedFromMsgId = data.vsaved_from_msg_id();
-	if (savedFromPeer && savedFromMsgId) {
-		config.savedFromPeer = peerFromMTP(*savedFromPeer);
-		config.savedFromMsgId = savedFromMsgId->v;
-	}
-	config.imported = data.is_imported();
-}
-
-HistoryMessage::HistoryMessage(
-	not_null<History*> history,
-	MsgId id,
-	const MTPDmessage &data,
-	MessageFlags localFlags)
-: HistoryItem(
-		history,
-		id,
-		FlagsFromMTP(id, data.vflags().v, localFlags),
-		data.vdate().v,
-		data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) {
-	auto config = CreateConfig();
-	if (const auto forwarded = data.vfwd_from()) {
-		forwarded->match([&](const MTPDmessageFwdHeader &data) {
-			FillForwardedInfo(config, data);
-		});
-	}
-	if (const auto reply = data.vreply_to()) {
-		reply->match([&](const MTPDmessageReplyHeader &data) {
-			if (const auto peer = data.vreply_to_peer_id()) {
-				config.replyToPeer = peerFromMTP(*peer);
-				if (config.replyToPeer == history->peer->id) {
-					config.replyToPeer = 0;
-				}
-			}
-			const auto id = data.vreply_to_msg_id().v;
-			config.replyTo = data.is_reply_to_scheduled()
-				? history->owner().scheduledMessages().localMessageId(id)
-				: id;
-			config.replyToTop = data.vreply_to_top_id().value_or(id);
-			config.replyIsTopicPost = data.is_forum_topic();
-		});
-	}
-	config.viaBotId = data.vvia_bot_id().value_or_empty();
-	config.viewsCount = data.vviews().value_or(-1);
-	config.replies = isScheduled()
-		? HistoryMessageRepliesData()
-		: HistoryMessageRepliesData(data.vreplies());
-	config.markup = HistoryMessageMarkupData(data.vreply_markup());
-	config.editDate = data.vedit_date().value_or_empty();
-	config.author = qs(data.vpost_author().value_or_empty());
-	createComponents(std::move(config));
-
-	if (const auto media = data.vmedia()) {
-		setMedia(*media);
-	}
-	auto textWithEntities = TextWithEntities{
-		qs(data.vmessage()),
-		Api::EntitiesFromMTP(
-			&history->session(),
-			data.ventities().value_or_empty())
-	};
-	setText(_media ? textWithEntities : EnsureNonEmpty(textWithEntities));
-	if (const auto groupedId = data.vgrouped_id()) {
-		setGroupId(
-			MessageGroupId::FromRaw(history->peer->id, groupedId->v));
-	}
-	setReactions(data.vreactions());
-	applyTTL(data);
-}
-
-HistoryMessage::HistoryMessage(
-	not_null<History*> history,
-	MsgId id,
-	const MTPDmessageService &data,
-	MessageFlags localFlags)
-: HistoryItem(
-		history,
-		id,
-		FlagsFromMTP(id, data.vflags().v, localFlags),
-		data.vdate().v,
-		data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) {
-	auto config = CreateConfig();
-	if (const auto reply = data.vreply_to()) {
-		reply->match([&](const MTPDmessageReplyHeader &data) {
-			const auto peer = data.vreply_to_peer_id()
-				? peerFromMTP(*data.vreply_to_peer_id())
-				: history->peer->id;
-			if (!peer || peer == history->peer->id) {
-				const auto id = data.vreply_to_msg_id().v;
-				config.replyTo = id;
-				config.replyToTop = data.vreply_to_top_id().value_or(id);
-			}
-		});
-	}
-	createComponents(std::move(config));
-
-	data.vaction().match([&](const MTPDmessageActionPhoneCall &data) {
-		_media = std::make_unique<Data::MediaCall>(
-			this,
-			Data::ComputeCallData(data));
-		setTextValue({});
-	}, [](const auto &) {
-		Unexpected("Service message action type in HistoryMessage.");
-	});
-
-	applyTTL(data);
-}
-
-HistoryMessage::HistoryMessage(
-	not_null<History*> history,
-	MsgId id,
-	MessageFlags flags,
-	TimeId date,
-	PeerId from,
-	const QString &postAuthor,
-	not_null<HistoryItem*> original,
-	MsgId topicRootId)
-: HistoryItem(
-		history,
-		id,
-		(NewForwardedFlags(history->peer, from, original) | flags),
-		date,
-		from) {
-	const auto peer = history->peer;
-
-	auto config = CreateConfig();
-
-	const auto originalMedia = original->media();
-	const auto dropForwardInfo = original->computeDropForwardedInfo();
-	config.replyTo = config.replyToTop = topicRootId;
-	config.replyIsTopicPost = (topicRootId != 0);
-	if (!dropForwardInfo) {
-		config.originalDate = original->dateOriginal();
-		if (const auto info = original->hiddenSenderInfo()) {
-			config.senderNameOriginal = info->name;
-		} else if (const auto senderOriginal = original->senderOriginal()) {
-			config.senderOriginal = senderOriginal->id;
-			if (senderOriginal->isChannel()) {
-				config.originalId = original->idOriginal();
-			}
-		} else {
-			Unexpected("Corrupt forwarded information in message.");
-		}
-		config.authorOriginal = original->authorOriginal();
-	}
-	if (peer->isSelf()) {
-		//
-		// iOS app sends you to the original post if we forward a forward from channel.
-		// But server returns not the original post but the forward in saved_from_...
-		//
-		//if (config.originalId) {
-		//	config.savedFromPeer = config.senderOriginal;
-		//	config.savedFromMsgId = config.originalId;
-		//} else {
-			config.savedFromPeer = original->history()->peer->id;
-			config.savedFromMsgId = original->id;
-		//}
-	}
-	if (flags & MessageFlag::HasPostAuthor) {
-		config.author = postAuthor;
-	}
-	if (const auto fwdViaBot = original->viaBot()) {
-		config.viaBotId = peerToUser(fwdViaBot->id);
-	} else if (originalMedia && originalMedia->game()) {
-		if (const auto sender = original->senderOriginal()) {
-			if (const auto user = sender->asUser()) {
-				if (user->isBot()) {
-					config.viaBotId = peerToUser(user->id);
-				}
-			}
-		}
-	}
-	const auto fwdViewsCount = original->viewsCount();
-	if (fwdViewsCount > 0) {
-		config.viewsCount = fwdViewsCount;
-	} else if ((isPost() && !isScheduled())
-		|| (original->senderOriginal()
-			&& original->senderOriginal()->isChannel())) {
-		config.viewsCount = 1;
-	}
-
-	const auto mediaOriginal = original->media();
-	if (CopyMarkupToForward(original)) {
-		config.inlineMarkup = original->inlineReplyMarkup();
-	}
-	createComponents(std::move(config));
-
-	const auto ignoreMedia = [&] {
-		if (mediaOriginal && mediaOriginal->webpage()) {
-			if (peer->amRestricted(ChatRestriction::EmbedLinks)) {
-				return true;
-			}
-		}
-		return false;
-	};
-	if (mediaOriginal && !ignoreMedia()) {
-		_media = mediaOriginal->clone(this);
-	}
-
-	const auto dropCustomEmoji = dropForwardInfo
-		&& !history->session().premium()
-		&& !history->peer->isSelf();
-	setText(dropCustomEmoji
-		? DropCustomEmoji(original->originalText())
-		: original->originalText());
-}
-
-HistoryMessage::HistoryMessage(
-	not_null<History*> history,
-	MsgId id,
-	MessageFlags flags,
-	MsgId replyTo,
-	UserId viaBotId,
-	TimeId date,
-	PeerId from,
-	const QString &postAuthor,
-	const TextWithEntities &textWithEntities,
-	const MTPMessageMedia &media,
-	HistoryMessageMarkupData &&markup,
-	uint64 groupedId)
-: HistoryItem(
-		history,
-		id,
-		flags,
-		date,
-		(flags & MessageFlag::HasFromId) ? from : 0) {
-	createComponentsHelper(
-		flags,
-		replyTo,
-		viaBotId,
-		postAuthor,
-		std::move(markup));
-	setMedia(media);
-	setText(textWithEntities);
-	if (groupedId) {
-		setGroupId(MessageGroupId::FromRaw(history->peer->id, groupedId));
-	}
-}
-
-HistoryMessage::HistoryMessage(
-	not_null<History*> history,
-	MsgId id,
-	MessageFlags flags,
-	MsgId replyTo,
-	UserId viaBotId,
-	TimeId date,
-	PeerId from,
-	const QString &postAuthor,
-	not_null<DocumentData*> document,
-	const TextWithEntities &caption,
-	HistoryMessageMarkupData &&markup)
-: HistoryItem(
-		history,
-		id,
-		flags,
-		date,
-		(flags & MessageFlag::HasFromId) ? from : 0) {
-	createComponentsHelper(
-		flags,
-		replyTo,
-		viaBotId,
-		postAuthor,
-		std::move(markup));
-
-	const auto skipPremiumEffect = !history->session().premium();
-	_media = std::make_unique<Data::MediaFile>(
-		this,
-		document,
-		skipPremiumEffect);
-	setText(caption);
-}
-
-HistoryMessage::HistoryMessage(
-	not_null<History*> history,
-	MsgId id,
-	MessageFlags flags,
-	MsgId replyTo,
-	UserId viaBotId,
-	TimeId date,
-	PeerId from,
-	const QString &postAuthor,
-	not_null<PhotoData*> photo,
-	const TextWithEntities &caption,
-	HistoryMessageMarkupData &&markup)
-: HistoryItem(
-		history,
-		id,
-		flags,
-		date,
-		(flags & MessageFlag::HasFromId) ? from : 0) {
-	createComponentsHelper(
-		flags,
-		replyTo,
-		viaBotId,
-		postAuthor,
-		std::move(markup));
-
-	_media = std::make_unique<Data::MediaPhoto>(this, photo);
-	setText(caption);
-}
-
-HistoryMessage::HistoryMessage(
-	not_null<History*> history,
-	MsgId id,
-	MessageFlags flags,
-	MsgId replyTo,
-	UserId viaBotId,
-	TimeId date,
-	PeerId from,
-	const QString &postAuthor,
-	not_null<GameData*> game,
-	HistoryMessageMarkupData &&markup)
-: HistoryItem(
-		history,
-		id,
-		flags,
-		date,
-		(flags & MessageFlag::HasFromId) ? from : 0) {
-	createComponentsHelper(
-		flags,
-		replyTo,
-		viaBotId,
-		postAuthor,
-		std::move(markup));
-
-	_media = std::make_unique<Data::MediaGame>(this, game);
-	setTextValue({});
-}
-
-HistoryMessage::HistoryMessage(
-	not_null<History*> history,
-	MsgId id,
-	Data::SponsoredFrom from,
-	const TextWithEntities &textWithEntities,
-	HistoryItem *injectedAfter)
-: HistoryItem(
-		history,
-		id,
-		((history->peer->isChannel() ? MessageFlag::Post : MessageFlag(0))
-			//| (from.peer ? MessageFlag::HasFromId : MessageFlag(0))
-			| MessageFlag::Local),
-		HistoryItem::NewMessageDate(injectedAfter
-			? injectedAfter->date()
-			: 0),
-		/*from.peer ? from.peer->id : */PeerId(0)) {
-	createComponentsHelper(
-		_flags,
-		MsgId(0), // replyTo
-		UserId(0), // viaBotId
-		QString(), // postAuthor
-		HistoryMessageMarkupData());
-	setText(textWithEntities);
-	setSponsoredFrom(from);
-}
-
-void HistoryMessage::createComponentsHelper(
-		MessageFlags flags,
-		MsgId replyTo,
-		UserId viaBotId,
-		const QString &postAuthor,
-		HistoryMessageMarkupData &&markup) {
-	auto config = CreateConfig();
-	config.viaBotId = viaBotId;
-	if (flags & MessageFlag::HasReplyInfo) {
-		config.replyTo = replyTo;
-		const auto to = LookupReplyTo(history(), replyTo);
-		const auto replyToTop = LookupReplyToTop(to);
-		config.replyToTop = replyToTop ? replyToTop : replyTo;
-		const auto forum = history()->asForum();
-		config.replyIsTopicPost = LookupReplyIsTopicPost(to)
-			|| (to && to->Has<HistoryServiceTopicInfo>())
-			|| (forum && forum->creating(config.replyToTop));
-	}
-	config.markup = std::move(markup);
-	if (flags & MessageFlag::HasPostAuthor) config.author = postAuthor;
-	if (flags & MessageFlag::HasViews) config.viewsCount = 1;
-
-	createComponents(std::move(config));
-}
-
-int HistoryMessage::viewsCount() const {
-	if (const auto views = Get<HistoryMessageViews>()) {
-		return std::max(views->views.count, 0);
-	}
-	return HistoryItem::viewsCount();
-}
-
-bool HistoryMessage::checkCommentsLinkedChat(ChannelId id) const {
-	if (!id) {
-		return true;
-	} else if (const auto channel = history()->peer->asChannel()) {
-		if (channel->linkedChatKnown()
-			|| !(channel->flags() & ChannelDataFlag::HasLink)) {
-			const auto linked = channel->linkedChat();
-			if (!linked || peerToChannel(linked->id) != id) {
-				return false;
-			}
-		}
-		return true;
-	}
-	return false;
-}
-
-int HistoryMessage::repliesCount() const {
-	if (const auto views = Get<HistoryMessageViews>()) {
-		if (!checkCommentsLinkedChat(views->commentsMegagroupId)) {
-			return 0;
-		}
-		return std::max(views->replies.count, 0);
-	}
-	return HistoryItem::repliesCount();
-}
-
-bool HistoryMessage::repliesAreComments() const {
-	if (const auto views = Get<HistoryMessageViews>()) {
-		return (views->commentsMegagroupId != 0)
-			&& checkCommentsLinkedChat(views->commentsMegagroupId);
-	}
-	return HistoryItem::repliesAreComments();
-}
-
-bool HistoryMessage::externalReply() const {
-	if (!history()->peer->isRepliesChat()) {
-		return false;
-	} else if (const auto forwarded = Get<HistoryMessageForwarded>()) {
-		return forwarded->savedFromPeer && forwarded->savedFromMsgId;
-	}
-	return false;
-}
-
-void HistoryMessage::setCommentsInboxReadTill(MsgId readTillId) {
-	const auto views = Get<HistoryMessageViews>();
-	if (!views) {
-		return;
-	}
-	const auto newReadTillId = std::max(readTillId.bare, int64(1));
-	const auto ignore = (newReadTillId < views->commentsInboxReadTillId);
-	if (ignore) {
-		return;
-	}
-	const auto changed = (newReadTillId > views->commentsInboxReadTillId);
-	if (!changed) {
-		return;
-	}
-	const auto wasUnread = areCommentsUnread();
-	views->commentsInboxReadTillId = newReadTillId;
-	if (wasUnread && !areCommentsUnread()) {
-		history()->owner().requestItemRepaint(this);
-	}
-}
-
-void HistoryMessage::setCommentsMaxId(MsgId maxId) {
-	if (const auto views = Get<HistoryMessageViews>()) {
-		if (views->commentsMaxId != maxId) {
-			const auto wasUnread = areCommentsUnread();
-			views->commentsMaxId = maxId;
-			if (wasUnread != areCommentsUnread()) {
-				history()->owner().requestItemRepaint(this);
-			}
-		}
-	}
-}
-
-void HistoryMessage::setCommentsPossibleMaxId(MsgId possibleMaxId) {
-	if (const auto views = Get<HistoryMessageViews>()) {
-		if (views->commentsMaxId < possibleMaxId) {
-			const auto wasUnread = areCommentsUnread();
-			views->commentsMaxId = possibleMaxId;
-			if (!wasUnread && areCommentsUnread()) {
-				history()->owner().requestItemRepaint(this);
-			}
-		}
-	}
-}
-
-bool HistoryMessage::areCommentsUnread() const {
-	const auto views = Get<HistoryMessageViews>();
-	if (!views
-		|| !views->commentsMegagroupId
-		|| !checkCommentsLinkedChat(views->commentsMegagroupId)) {
-		return false;
-	}
-	const auto till = views->commentsInboxReadTillId;
-	if (views->commentsInboxReadTillId < 2 || views->commentsMaxId <= till) {
-		return false;
-	}
-	const auto group = views->commentsMegagroupId
-		? history()->owner().historyLoaded(
-			peerFromChannel(views->commentsMegagroupId))
-		: history().get();
-	return !group || (views->commentsMaxId > group->inboxReadTillId());
-}
-
-FullMsgId HistoryMessage::commentsItemId() const {
-	if (const auto views = Get<HistoryMessageViews>()) {
-		return FullMsgId(
-			PeerId(views->commentsMegagroupId),
-			views->commentsRootId);
-	}
-	return FullMsgId();
-}
-
-void HistoryMessage::setCommentsItemId(FullMsgId id) {
-	if (id.peer == _history->peer->id) {
-		if (id.msg != this->id) {
-			if (const auto reply = Get<HistoryMessageReply>()) {
-				reply->replyToMsgTop = id.msg;
-			}
-		}
-	} else if (const auto views = Get<HistoryMessageViews>()) {
-		if (const auto channelId = peerToChannel(id.peer)) {
-			if (views->commentsMegagroupId != channelId) {
-				views->commentsMegagroupId = channelId;
-				history()->owner().requestItemResize(this);
-			}
-			views->commentsRootId = id.msg;
-		}
-	}
-}
-
-bool HistoryMessage::updateDependencyItem() {
-	if (const auto reply = Get<HistoryMessageReply>()) {
-		const auto documentId = reply->replyToDocumentId;
-		const auto webpageId = reply->replyToWebPageId;
-		const auto result = reply->updateData(this, true);
-		const auto mediaIdChanged = (documentId != reply->replyToDocumentId)
-			|| (webpageId != reply->replyToWebPageId);
-		if (mediaIdChanged && generateLocalEntitiesByReply()) {
-			history()->owner().requestItemTextRefresh(this);
-		}
-		return result;
-	}
-	return true;
-}
-
-void HistoryMessage::applySentMessage(const MTPDmessage &data) {
-	HistoryItem::applySentMessage(data);
-
-	if (const auto period = data.vttl_period(); period && period->v > 0) {
-		applyTTL(data.vdate().v + period->v);
-	} else {
-		applyTTL(0);
-	}
-}
-
-void HistoryMessage::applySentMessage(
-		const QString &text,
-		const MTPDupdateShortSentMessage &data,
-		bool wasAlready) {
-	HistoryItem::applySentMessage(text, data, wasAlready);
-
-	if (const auto period = data.vttl_period(); period && period->v > 0) {
-		applyTTL(data.vdate().v + period->v);
-	} else {
-		applyTTL(0);
-	}
-}
-
-bool HistoryMessage::allowsForward() const {
-	return isRegular()
-		&& !forbidsForward()
-		&& history()->peer->allowsForwarding()
-		&& (!_media || _media->allowsForward());
-}
-
-bool HistoryMessage::allowsSendNow() const {
-	return isScheduled() && !isSending() && !hasFailed() && !isEditingMedia();
-}
-
-bool HistoryMessage::isTooOldForEdit(TimeId now) const {
-	return !_history->peer->canEditMessagesIndefinitely()
-		&& !isScheduled()
-		&& (now - date() >= _history->session().serverConfig().editTimeLimit);
-}
-
-bool HistoryMessage::allowsEdit(TimeId now) const {
-	return canBeEdited()
-		&& !isTooOldForEdit(now)
-		&& (!_media || _media->allowsEdit())
-		&& !isLegacyMessage()
-		&& !isEditingMedia();
-}
-
-void HistoryMessage::createComponents(CreateConfig &&config) {
-	uint64 mask = 0;
-	if (config.replyTo) {
-		mask |= HistoryMessageReply::Bit();
-	}
-	if (config.viaBotId) {
-		mask |= HistoryMessageVia::Bit();
-	}
-	if (config.viewsCount >= 0 || !config.replies.isNull) {
-		mask |= HistoryMessageViews::Bit();
-	}
-	if (!config.author.isEmpty()) {
-		mask |= HistoryMessageSigned::Bit();
-	} else if (_history->peer->isMegagroup() // Discussion posts signatures.
-		&& config.savedFromPeer
-		&& !config.authorOriginal.isEmpty()) {
-		const auto savedFrom = _history->owner().peerLoaded(
-			config.savedFromPeer);
-		if (savedFrom && savedFrom->isChannel()) {
-			mask |= HistoryMessageSigned::Bit();
-		}
-	} else if ((_history->peer->isSelf() || _history->peer->isRepliesChat())
-		&& !config.authorOriginal.isEmpty()) {
-		mask |= HistoryMessageSigned::Bit();
-	}
-	if (config.editDate != TimeId(0)) {
-		mask |= HistoryMessageEdited::Bit();
-	}
-	if (config.originalDate != 0) {
-		mask |= HistoryMessageForwarded::Bit();
-	}
-	if (!config.markup.isTrivial()) {
-		mask |= HistoryMessageReplyMarkup::Bit();
-	} else if (config.inlineMarkup) {
-		mask |= HistoryMessageReplyMarkup::Bit();
-	}
-
-	UpdateComponents(mask);
-
-	if (const auto reply = Get<HistoryMessageReply>()) {
-		reply->replyToPeerId = config.replyToPeer;
-		reply->replyToMsgId = config.replyTo;
-		reply->replyToMsgTop = isScheduled() ? 0 : config.replyToTop;
-		reply->topicPost = config.replyIsTopicPost;
-		if (!reply->updateData(this)) {
-			RequestDependentMessageData(
-				this,
-				reply->replyToPeerId,
-				reply->replyToMsgId);
-		}
-	}
-	if (const auto via = Get<HistoryMessageVia>()) {
-		via->create(&history()->owner(), config.viaBotId);
-	}
-	if (const auto views = Get<HistoryMessageViews>()) {
-		changeViewsCount(config.viewsCount);
-		if (config.replies.isNull
-			&& isSending()
-			&& config.markup.isNull()) {
-			if (const auto broadcast = history()->peer->asBroadcast()) {
-				if (const auto linked = broadcast->linkedChat()) {
-					config.replies.isNull = false;
-					config.replies.channelId = peerToChannel(linked->id);
-				}
-			}
-		}
-		setReplies(std::move(config.replies));
-	}
-	if (const auto edited = Get<HistoryMessageEdited>()) {
-		edited->date = config.editDate;
-	}
-	if (const auto msgsigned = Get<HistoryMessageSigned>()) {
-		msgsigned->author = config.author.isEmpty()
-			? config.authorOriginal
-			: config.author;
-		msgsigned->isAnonymousRank = !isDiscussionPost()
-			&& author()->isMegagroup();
-	}
-	setupForwardedComponent(config);
-	if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
-		if (!config.markup.isTrivial()) {
-			markup->updateData(std::move(config.markup));
-		} else if (config.inlineMarkup) {
-			markup->createForwarded(*config.inlineMarkup);
-		}
-		if (markup->data.flags & ReplyMarkupFlag::HasSwitchInlineButton) {
-			_flags |= MessageFlag::HasSwitchInlineButton;
-		}
-	} else if (!config.markup.isNull()) {
-		_flags |= MessageFlag::HasReplyMarkup;
-	} else {
-		_flags &= ~MessageFlag::HasReplyMarkup;
-	}
-}
-
-bool HistoryMessage::checkRepliesPts(
-		const HistoryMessageRepliesData &data) const {
-	const auto channel = history()->peer->asChannel();
-	const auto pts = channel
-		? channel->pts()
-		: history()->session().updates().pts();
-	return (data.pts >= pts);
-}
-
-void HistoryMessage::setupForwardedComponent(const CreateConfig &config) {
-	const auto forwarded = Get<HistoryMessageForwarded>();
-	if (!forwarded) {
-		return;
-	}
-	forwarded->originalDate = config.originalDate;
-	const auto originalSender = config.senderOriginal
-		? config.senderOriginal
-		: !config.senderNameOriginal.isEmpty()
-		? PeerId()
-		: from()->id;
-	forwarded->originalSender = originalSender
-		? history()->owner().peer(originalSender).get()
-		: nullptr;
-	if (!forwarded->originalSender) {
-		forwarded->hiddenSenderInfo = std::make_unique<HiddenSenderInfo>(
-			config.senderNameOriginal,
-			config.imported);
-	}
-	forwarded->originalId = config.originalId;
-	forwarded->originalAuthor = config.authorOriginal;
-	forwarded->psaType = config.forwardPsaType;
-	forwarded->savedFromPeer = history()->owner().peerLoaded(
-		config.savedFromPeer);
-	forwarded->savedFromMsgId = config.savedFromMsgId;
-	forwarded->imported = config.imported;
-}
-
-void HistoryMessage::refreshMedia(const MTPMessageMedia *media) {
-	const auto was = (_media != nullptr);
-	if (const auto invoice = was ? _media->invoice() : nullptr) {
-		if (invoice->extendedMedia) {
-			return;
-		}
-	}
-	_media = nullptr;
-	if (media) {
-		setMedia(*media);
-	}
-	if (was || _media) {
-		if (const auto views = Get<HistoryMessageViews>()) {
-			refreshRepliesText(views);
-		}
-	}
-}
-
-void HistoryMessage::refreshSentMedia(const MTPMessageMedia *media) {
-	const auto wasGrouped = history()->owner().groups().isGrouped(this);
-	refreshMedia(media);
-	if (wasGrouped) {
-		history()->owner().groups().refreshMessage(this);
-	} else {
-		history()->owner().requestItemViewRefresh(this);
-	}
-}
-
-void HistoryMessage::returnSavedMedia() {
-	if (!isEditingMedia()) {
-		return;
-	}
-	const auto wasGrouped = history()->owner().groups().isGrouped(this);
-	_media = std::move(_savedLocalEditMediaData->media);
-	setText(_savedLocalEditMediaData->text);
-	clearSavedMedia();
-	if (wasGrouped) {
-		history()->owner().groups().refreshMessage(this, true);
-	} else {
-		history()->owner().requestItemViewRefresh(this);
-		history()->owner().updateDependentMessages(this);
-	}
-}
-
-void HistoryMessage::setMedia(const MTPMessageMedia &media) {
-	_media = CreateMedia(this, media);
-	checkBuyButton();
-}
-
-void HistoryMessage::checkBuyButton() {
-	if (const auto invoice = _media ? _media->invoice() : nullptr) {
-		if (invoice->receiptMsgId) {
-			replaceBuyWithReceiptInMarkup();
-		}
-	}
-}
-
-std::unique_ptr<Data::Media> HistoryMessage::CreateMedia(
-		not_null<HistoryMessage*> item,
-		const MTPMessageMedia &media) {
-	using Result = std::unique_ptr<Data::Media>;
-	return media.match([&](const MTPDmessageMediaContact &media) -> Result {
-		return std::make_unique<Data::MediaContact>(
-			item,
-			media.vuser_id().v,
-			qs(media.vfirst_name()),
-			qs(media.vlast_name()),
-			qs(media.vphone_number()));
-	}, [&](const MTPDmessageMediaGeo &media) -> Result {
-		return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result {
-			return std::make_unique<Data::MediaLocation>(
-				item,
-				Data::LocationPoint(point));
-		}, [](const MTPDgeoPointEmpty &) -> Result {
-			return nullptr;
-		});
-	}, [&](const MTPDmessageMediaGeoLive &media) -> Result {
-		return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result {
-			return std::make_unique<Data::MediaLocation>(
-				item,
-				Data::LocationPoint(point));
-		}, [](const MTPDgeoPointEmpty &) -> Result {
-			return nullptr;
-		});
-	}, [&](const MTPDmessageMediaVenue &media) -> Result {
-		return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result {
-			return std::make_unique<Data::MediaLocation>(
-				item,
-				Data::LocationPoint(point),
-				qs(media.vtitle()),
-				qs(media.vaddress()));
-		}, [](const MTPDgeoPointEmpty &data) -> Result {
-			return nullptr;
-		});
-	}, [&](const MTPDmessageMediaPhoto &media) -> Result {
-		const auto photo = media.vphoto();
-		if (media.vttl_seconds()) {
-			LOG(("App Error: "
-				"Unexpected MTPMessageMediaPhoto "
-				"with ttl_seconds in HistoryMessage."));
-			return nullptr;
-		} else if (!photo) {
-			LOG(("API Error: "
-				"Got MTPMessageMediaPhoto "
-				"without photo and without ttl_seconds."));
-			return nullptr;
-		}
-		return photo->match([&](const MTPDphoto &photo) -> Result {
-			return std::make_unique<Data::MediaPhoto>(
-				item,
-				item->history()->owner().processPhoto(photo));
-		}, [](const MTPDphotoEmpty &) -> Result {
-			return nullptr;
-		});
-	}, [&](const MTPDmessageMediaDocument &media) -> Result {
-		const auto document = media.vdocument();
-		if (media.vttl_seconds()) {
-			LOG(("App Error: "
-				"Unexpected MTPMessageMediaDocument "
-				"with ttl_seconds in HistoryMessage."));
-			return nullptr;
-		} else if (!document) {
-			LOG(("API Error: "
-				"Got MTPMessageMediaDocument "
-				"without document and without ttl_seconds."));
-			return nullptr;
-		}
-		return document->match([&](const MTPDdocument &document) -> Result {
-			return std::make_unique<Data::MediaFile>(
-				item,
-				item->history()->owner().processDocument(document),
-				media.is_nopremium());
-		}, [](const MTPDdocumentEmpty &) -> Result {
-			return nullptr;
-		});
-	}, [&](const MTPDmessageMediaWebPage &media) {
-		return media.vwebpage().match([](const MTPDwebPageEmpty &) -> Result {
-			return nullptr;
-		}, [&](const MTPDwebPagePending &webpage) -> Result {
-			return std::make_unique<Data::MediaWebPage>(
-				item,
-				item->history()->owner().processWebpage(webpage));
-		}, [&](const MTPDwebPage &webpage) -> Result {
-			return std::make_unique<Data::MediaWebPage>(
-				item,
-				item->history()->owner().processWebpage(webpage));
-		}, [](const MTPDwebPageNotModified &) -> Result {
-			LOG(("API Error: "
-				"webPageNotModified is unexpected in message media."));
-			return nullptr;
-		});
-	}, [&](const MTPDmessageMediaGame &media) -> Result {
-		return media.vgame().match([&](const MTPDgame &game) {
-			return std::make_unique<Data::MediaGame>(
-				item,
-				item->history()->owner().processGame(game));
-		});
-	}, [&](const MTPDmessageMediaInvoice &media) -> Result {
-		return std::make_unique<Data::MediaInvoice>(
-			item,
-			Data::ComputeInvoiceData(item, media));
-	}, [&](const MTPDmessageMediaPoll &media) -> Result {
-		return std::make_unique<Data::MediaPoll>(
-			item,
-			item->history()->owner().processPoll(media));
-	}, [&](const MTPDmessageMediaDice &media) -> Result {
-		return std::make_unique<Data::MediaDice>(
-			item,
-			qs(media.vemoticon()),
-			media.vvalue().v);
-	}, [](const MTPDmessageMediaEmpty &) -> Result {
-		return nullptr;
-	}, [](const MTPDmessageMediaUnsupported &) -> Result {
-		return nullptr;
-	});
-}
-
-void HistoryMessage::replaceBuyWithReceiptInMarkup() {
-	if (const auto markup = inlineReplyMarkup()) {
-		for (auto &row : markup->data.rows) {
-			for (auto &button : row) {
-				if (button.type == HistoryMessageMarkupButton::Type::Buy) {
-					const auto receipt = tr::lng_payments_receipt_button(tr::now);
-					if (button.text != receipt) {
-						button.text = receipt;
-						if (markup->inlineKeyboard) {
-							markup->inlineKeyboard = nullptr;
-							history()->owner().requestItemResize(this);
-						}
-					}
-				}
-			}
-		}
-	}
-}
-
-void HistoryMessage::applyEdition(HistoryMessageEdition &&edition) {
-	int keyboardTop = -1;
-	//if (!pendingResize()) {// #TODO edit bot message
-	//	if (auto keyboard = inlineReplyKeyboard()) {
-	//		int h = st::msgBotKbButton.margin + keyboard->naturalHeight();
-	//		keyboardTop = _height - h + st::msgBotKbButton.margin - marginBottom();
-	//	}
-	//}
-
-	if (edition.isEditHide) {
-		_flags |= MessageFlag::HideEdited;
-	} else {
-		_flags &= ~MessageFlag::HideEdited;
-	}
-
-	if (edition.editDate != -1) {
-		//_flags |= MTPDmessage::Flag::f_edit_date;
-		if (!Has<HistoryMessageEdited>()) {
-			AddComponents(HistoryMessageEdited::Bit());
-		}
-		auto edited = Get<HistoryMessageEdited>();
-		edited->date = edition.editDate;
-	}
-
-	if (!edition.useSameMarkup) {
-		setReplyMarkup(base::take(edition.replyMarkup));
-	}
-	if (!isLocalUpdateMedia()) {
-		refreshMedia(edition.mtpMedia);
-	}
-	if (!edition.useSameReactions) {
-		updateReactions(edition.mtpReactions);
-	}
-	changeViewsCount(edition.views);
-	setForwardsCount(edition.forwards);
-	setText(_media
-		? edition.textWithEntities
-		: EnsureNonEmpty(edition.textWithEntities));
-	if (!edition.useSameReplies) {
-		if (!edition.replies.isNull) {
-			if (checkRepliesPts(edition.replies)) {
-				setReplies(base::take(edition.replies));
-			}
-		} else {
-			clearReplies();
-		}
-	}
-
-	applyTTL(edition.ttl);
-
-	finishEdition(keyboardTop);
-}
-
-void HistoryMessage::applyEdition(const MTPDmessageService &message) {
-	if (message.vaction().type() == mtpc_messageActionHistoryClear) {
-		const auto wasGrouped = history()->owner().groups().isGrouped(this);
-		setReplyMarkup({});
-		refreshMedia(nullptr);
-		setTextValue({});
-		changeViewsCount(-1);
-		setForwardsCount(-1);
-		if (wasGrouped) {
-			history()->owner().groups().unregisterMessage(this);
-		}
-		finishEditionToEmpty();
-	}
-}
-
-void HistoryMessage::applyEdition(const MTPMessageExtendedMedia &media) {
-	if (const auto existing = this->media()) {
-		if (existing->updateExtendedMedia(this, media)) {
-			checkBuyButton();
-			finishEdition(-1);
-		}
-	}
-}
-
-void HistoryMessage::updateSentContent(
-		const TextWithEntities &textWithEntities,
-		const MTPMessageMedia *media) {
-	setText(textWithEntities);
-	if (_flags & MessageFlag::FromInlineBot) {
-		if (!media || !_media || !_media->updateInlineResultMedia(*media)) {
-			refreshSentMedia(media);
-		}
-		_flags &= ~MessageFlag::FromInlineBot;
-	} else if (media || _media) {
-		if (!media || !_media || !_media->updateSentMedia(*media)) {
-			refreshSentMedia(media);
-		}
-	}
-	history()->owner().requestItemResize(this);
-}
-
-void HistoryMessage::updateForwardedInfo(const MTPMessageFwdHeader *fwd) {
-	const auto forwarded = Get<HistoryMessageForwarded>();
-	if (!fwd) {
-		if (forwarded) {
-			LOG(("API Error: Server removed forwarded information."));
-		}
-		return;
-	} else if (!forwarded) {
-		LOG(("API Error: Server added forwarded information."));
-		return;
-	}
-	fwd->match([&](const MTPDmessageFwdHeader &data) {
-		auto config = CreateConfig();
-		FillForwardedInfo(config, data);
-		setupForwardedComponent(config);
-		history()->owner().requestItemResize(this);
-	});
-}
-
-void HistoryMessage::updateReplyMarkup(HistoryMessageMarkupData &&markup) {
-	setReplyMarkup(std::move(markup));
-}
-
-void HistoryMessage::contributeToSlowmode(TimeId realDate) {
-	if (const auto channel = history()->peer->asChannel()) {
-		if (out() && isRegular()) {
-			channel->growSlowmodeLastMessage(realDate ? realDate : date());
-		}
-	}
-}
-
-void HistoryMessage::addToUnreadThings(HistoryUnreadThings::AddType type) {
-	if (!isRegular()) {
-		return;
-	}
-	const auto mention = isUnreadMention();
-	const auto reaction = hasUnreadReaction();
-	if (!mention && !reaction) {
-		return;
-	}
-	const auto topic = this->topic();
-	const auto history = this->history();
-	const auto changes = &history->session().changes();
-	if (mention) {
-		if (history->unreadMentions().add(id, type)) {
-			changes->historyUpdated(
-				history,
-				Data::HistoryUpdate::Flag::UnreadMentions);
-		}
-		if (topic && topic->unreadMentions().add(id, type)) {
-			changes->topicUpdated(
-				topic,
-				Data::TopicUpdate::Flag::UnreadMentions);
-		}
-	}
-	if (reaction) {
-		const auto toHistory = history->unreadReactions().add(id, type);
-		const auto toTopic = topic && topic->unreadReactions().add(id, type);
-		if (toHistory || toTopic) {
-			if (type == HistoryUnreadThings::AddType::New) {
-				changes->messageUpdated(
-					this,
-					Data::MessageUpdate::Flag::NewUnreadReaction);
-			}
-			if (hasUnreadReaction()) {
-				if (toHistory) {
-					changes->historyUpdated(
-						history,
-						Data::HistoryUpdate::Flag::UnreadReactions);
-				}
-				if (toTopic) {
-					changes->topicUpdated(
-						topic,
-						Data::TopicUpdate::Flag::UnreadReactions);
-				}
-			}
-		}
-	}
-}
-
-void HistoryMessage::destroyHistoryEntry() {
-	if (isUnreadMention()) {
-		history()->unreadMentions().erase(id);
-		if (const auto topic = this->topic()) {
-			topic->unreadMentions().erase(id);
-		}
-	}
-	if (hasUnreadReaction()) {
-		history()->unreadReactions().erase(id);
-		if (const auto topic = this->topic()) {
-			topic->unreadReactions().erase(id);
-		}
-	}
-	if (const auto reply = Get<HistoryMessageReply>()) {
-		changeReplyToTopCounter(reply, -1);
-	}
-}
-
-Storage::SharedMediaTypesMask HistoryMessage::sharedMediaTypes() const {
-	auto result = Storage::SharedMediaTypesMask {};
-	if (const auto media = this->media()) {
-		result.set(media->sharedMediaTypes());
-	}
-	if (hasTextLinks()) {
-		result.set(Storage::SharedMediaType::Link);
-	}
-	if (isPinned()) {
-		result.set(Storage::SharedMediaType::Pinned);
-	}
-	return result;
-}
-
-bool HistoryMessage::generateLocalEntitiesByReply() const {
-	using namespace HistoryView;
-	if (!_media) {
-		return true;
-	} else if (const auto document = _media->document()) {
-		return !DurationForTimestampLinks(document);
-	} else if (const auto webpage = _media->webpage()) {
-		return (webpage->type != WebPageType::Video)
-			&& !DurationForTimestampLinks(webpage);
-	}
-	return true;
-}
-
-TextWithEntities HistoryMessage::withLocalEntities(
-		const TextWithEntities &textWithEntities) const {
-	using namespace HistoryView;
-	if (!generateLocalEntitiesByReply()) {
-		if (!_media) {
-		} else if (const auto document = _media->document()) {
-			if (const auto duration = DurationForTimestampLinks(document)) {
-				return AddTimestampLinks(
-					textWithEntities,
-					duration,
-					TimestampLinkBase(document, fullId()));
-			}
-		} else if (const auto webpage = _media->webpage()) {
-			if (const auto duration = DurationForTimestampLinks(webpage)) {
-				return AddTimestampLinks(
-					textWithEntities,
-					duration,
-					TimestampLinkBase(webpage, fullId()));
-			}
-		}
-		return textWithEntities;
-	}
-	if (const auto reply = Get<HistoryMessageReply>()) {
-		const auto document = reply->replyToDocumentId
-			? history()->owner().document(reply->replyToDocumentId).get()
-			: nullptr;
-		const auto webpage = reply->replyToWebPageId
-			? history()->owner().webpage(reply->replyToWebPageId).get()
-			: nullptr;
-		if (document) {
-			if (const auto duration = DurationForTimestampLinks(document)) {
-				const auto context = reply->replyToMsg->fullId();
-				return AddTimestampLinks(
-					textWithEntities,
-					duration,
-					TimestampLinkBase(document, context));
-			}
-		} else if (webpage) {
-			if (const auto duration = DurationForTimestampLinks(webpage)) {
-				const auto context = reply->replyToMsg->fullId();
-				return AddTimestampLinks(
-					textWithEntities,
-					duration,
-					TimestampLinkBase(webpage, context));
-			}
-		}
-	}
-	return textWithEntities;
-}
-
-MsgId HistoryMessage::replyToId() const {
-	if (const auto reply = Get<HistoryMessageReply>()) {
-		return reply->replyToId();
-	}
-	return 0;
-}
-
-MsgId HistoryMessage::replyToTop() const {
-	if (const auto reply = Get<HistoryMessageReply>()) {
-		return reply->replyToTop();
-	}
-	return 0;
-}
-
-MsgId HistoryMessage::topicRootId() const {
-	if (const auto reply = Get<HistoryMessageReply>()
-		; reply && reply->topicPost) {
-		return reply->replyToTop();
-	}
-	return Data::ForumTopic::kGeneralId;
-}
-
-void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
-	for (const auto &entity : textWithEntities.entities) {
-		auto type = entity.type();
-		if (type == EntityType::Url
-			|| type == EntityType::CustomUrl
-			|| type == EntityType::Email) {
-			_flags |= MessageFlag::HasTextLinks;
-			break;
-		}
-	}
-	setTextValue((_media && _media->consumeMessageText(textWithEntities))
-		? TextWithEntities()
-		: std::move(textWithEntities));
-}
-
-void HistoryMessage::setTextValue(TextWithEntities text) {
-	const auto had = !_text.empty();
-	_text = std::move(text);
-	if (had) {
-		history()->owner().requestItemTextRefresh(this);
-	}
-}
-
-void HistoryMessage::setReplyMarkup(HistoryMessageMarkupData &&markup) {
-	const auto requestUpdate = [&] {
-		history()->owner().requestItemResize(this);
-		history()->session().changes().messageUpdated(
-			this,
-			Data::MessageUpdate::Flag::ReplyMarkup);
-	};
-	if (markup.isNull()) {
-		if (_flags & MessageFlag::HasReplyMarkup) {
-			_flags &= ~MessageFlag::HasReplyMarkup;
-			if (Has<HistoryMessageReplyMarkup>()) {
-				RemoveComponents(HistoryMessageReplyMarkup::Bit());
-			}
-			requestUpdate();
-		}
-		return;
-	}
-
-	// optimization: don't create markup component for the case
-	// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
-	if (markup.isTrivial()) {
-		bool changed = false;
-		if (Has<HistoryMessageReplyMarkup>()) {
-			RemoveComponents(HistoryMessageReplyMarkup::Bit());
-			changed = true;
-		}
-		if (!(_flags & MessageFlag::HasReplyMarkup)) {
-			_flags |= MessageFlag::HasReplyMarkup;
-			changed = true;
-		}
-		if (changed) {
-			requestUpdate();
-		}
-	} else {
-		if (!(_flags & MessageFlag::HasReplyMarkup)) {
-			_flags |= MessageFlag::HasReplyMarkup;
-		}
-		if (!Has<HistoryMessageReplyMarkup>()) {
-			AddComponents(HistoryMessageReplyMarkup::Bit());
-		}
-		Get<HistoryMessageReplyMarkup>()->updateData(std::move(markup));
-		requestUpdate();
-	}
-}
-
-TextWithEntities HistoryMessage::originalText() const {
-	return _text;
-}
-
-TextWithEntities HistoryMessage::originalTextWithLocalEntities() const {
-	return withLocalEntities(originalText());
-}
-
-TextForMimeData HistoryMessage::clipboardText() const {
-	return TextForMimeData::WithExpandedLinks(_text);
-}
-
-bool HistoryMessage::changeViewsCount(int count) {
-	const auto views = Get<HistoryMessageViews>();
-	if (!views
-		|| views->views.count == count
-		|| (count >= 0 && views->views.count > count)) {
-		return false;
-	}
-
-	views->views.count = count;
-	return true;
-}
-
-void HistoryMessage::setForwardsCount(int count) {
-	const auto views = Get<HistoryMessageViews>();
-	if (!views
-		|| views->forwardsCount == count
-		|| (count >= 0 && views->forwardsCount > count)) {
-		return;
-	}
-
-	views->forwardsCount = count;
-	history()->owner().notifyItemDataChange(this);
-}
-
-void HistoryMessage::setPostAuthor(const QString &author) {
-	auto msgsigned = Get<HistoryMessageSigned>();
-	if (author.isEmpty()) {
-		if (!msgsigned) {
-			return;
-		}
-		RemoveComponents(HistoryMessageSigned::Bit());
-		history()->owner().requestItemResize(this);
-		return;
-	}
-	if (!msgsigned) {
-		AddComponents(HistoryMessageSigned::Bit());
-		msgsigned = Get<HistoryMessageSigned>();
-	} else if (msgsigned->author == author) {
-		return;
-	}
-	msgsigned->author = author;
-	msgsigned->isAnonymousRank = !isDiscussionPost()
-		&& this->author()->isMegagroup();
-	history()->owner().requestItemResize(this);
-}
-
-void HistoryMessage::setReplies(HistoryMessageRepliesData &&data) {
-	if (data.isNull) {
-		return;
-	}
-	auto views = Get<HistoryMessageViews>();
-	if (!views) {
-		AddComponents(HistoryMessageViews::Bit());
-		views = Get<HistoryMessageViews>();
-	}
-	const auto &repliers = data.recentRepliers;
-	const auto count = data.repliesCount;
-	const auto channelId = data.channelId;
-	const auto readTillId = data.readMaxId
-		? std::max({
-			views->commentsInboxReadTillId.bare,
-			data.readMaxId.bare,
-			int64(1),
-		})
-		: views->commentsInboxReadTillId;
-	const auto maxId = data.maxId ? data.maxId : views->commentsMaxId;
-	const auto countsChanged = (views->replies.count != count)
-		|| (views->commentsInboxReadTillId != readTillId)
-		|| (views->commentsMaxId != maxId);
-	const auto megagroupChanged = (views->commentsMegagroupId != channelId);
-	const auto recentChanged = (views->recentRepliers != repliers);
-	if (!countsChanged && !megagroupChanged && !recentChanged) {
-		return;
-	}
-	views->replies.count = count;
-	if (recentChanged) {
-		views->recentRepliers = repliers;
-	}
-	const auto wasUnread = areCommentsUnread();
-	views->commentsMegagroupId = channelId;
-	views->commentsInboxReadTillId = readTillId;
-	views->commentsMaxId = maxId;
-	if (wasUnread != areCommentsUnread()) {
-		history()->owner().requestItemRepaint(this);
-	}
-	refreshRepliesText(views, megagroupChanged);
-}
-
-void HistoryMessage::clearReplies() {
-	auto views = Get<HistoryMessageViews>();
-	if (!views) {
-		return;
-	}
-	const auto viewsPart = views->views;
-	if (viewsPart.count < 0) {
-		RemoveComponents(HistoryMessageViews::Bit());
-	} else {
-		*views = HistoryMessageViews();
-		views->views = viewsPart;
-	}
-	history()->owner().requestItemResize(this);
-}
-
-void HistoryMessage::refreshRepliesText(
-		not_null<HistoryMessageViews*> views,
-		bool forceResize) {
-	if (views->commentsMegagroupId) {
-		views->replies.text = (views->replies.count > 0)
-			? tr::lng_comments_open_count(
-				tr::now,
-				lt_count_short,
-				views->replies.count)
-			: tr::lng_comments_open_none(tr::now);
-		views->replies.textWidth = st::semiboldFont->width(
-			views->replies.text);
-		views->repliesSmall.text = (views->replies.count > 0)
-			? Lang::FormatCountToShort(views->replies.count).string
-			: QString();
-		const auto hadText = (views->repliesSmall.textWidth > 0);
-		views->repliesSmall.textWidth = (views->replies.count > 0)
-			? st::semiboldFont->width(views->repliesSmall.text)
-			: 0;
-		const auto hasText = (views->repliesSmall.textWidth > 0);
-		if (hasText != hadText) {
-			forceResize = true;
-		}
-	}
-	if (forceResize) {
-		history()->owner().requestItemResize(this);
-	} else {
-		history()->owner().requestItemRepaint(this);
-	}
-}
-
-void HistoryMessage::changeRepliesCount(int delta, PeerId replier) {
-	const auto views = Get<HistoryMessageViews>();
-	const auto limit = HistoryMessageViews::kMaxRecentRepliers;
-	if (!views) {
-		return;
-	}
-
-	// Update full count.
-	if (views->replies.count < 0) {
-		return;
-	}
-	views->replies.count = std::max(views->replies.count + delta, 0);
-	if (replier && views->commentsMegagroupId) {
-		if (delta < 0) {
-			views->recentRepliers.erase(
-				ranges::remove(views->recentRepliers, replier),
-				end(views->recentRepliers));
-		} else if (!ranges::contains(views->recentRepliers, replier)) {
-			views->recentRepliers.insert(views->recentRepliers.begin(), replier);
-			while (views->recentRepliers.size() > limit) {
-				views->recentRepliers.pop_back();
-			}
-		}
-	}
-	refreshRepliesText(views);
-	history()->owner().notifyItemDataChange(this);
-}
-
-void HistoryMessage::setSponsoredFrom(const Data::SponsoredFrom &from) {
-	AddComponents(HistoryMessageSponsored::Bit());
-	const auto sponsored = Get<HistoryMessageSponsored>();
-	sponsored->sender = std::make_unique<HiddenSenderInfo>(
-		from.title,
-		false);
-	sponsored->recommended = from.isRecommended;
-	sponsored->isForceUserpicDisplay = from.isForceUserpicDisplay;
-	if (from.userpic.location.valid()) {
-		sponsored->sender->customUserpic.set(
-			&history()->session(),
-			from.userpic);
-	}
-
-	using Type = HistoryMessageSponsored::Type;
-	sponsored->type = from.isExactPost
-		? Type::Post
-		: from.isBot
-		? Type::Bot
-		: from.isBroadcast
-		? Type::Broadcast
-		: (from.peer && from.peer->isUser())
-		? Type::User
-		: Type::Group;
-}
-
-void HistoryMessage::setReplyFields(
-		MsgId replyTo,
-		MsgId replyToTop,
-		bool isForumPost) {
-	const auto reply = Get<HistoryMessageReply>();
-	if (!reply || isScheduled()) {
-		return;
-	}
-	reply->topicPost = isForumPost;
-	if ((reply->replyToMsgId != replyTo)
-		&& !IsServerMsgId(reply->replyToMsgId)) {
-		reply->replyToMsgId = replyTo;
-		if (!reply->updateData(this)) {
-			RequestDependentMessageData(
-				this,
-				reply->replyToPeerId,
-				reply->replyToMsgId);
-		}
-	}
-	if ((reply->replyToMsgTop != replyToTop)
-		&& !IsServerMsgId(reply->replyToMsgTop)) {
-		reply->replyToMsgTop = replyToTop;
-		changeReplyToTopCounter(reply, 1);
-		if (const auto topic = this->topic()) {
-			topic->maybeSetLastMessage(this);
-		}
-	}
-}
-
-void HistoryMessage::setRealId(MsgId newId) {
-	HistoryItem::setRealId(newId);
-
-	history()->owner().groups().refreshMessage(this);
-	history()->owner().requestItemResize(this);
-	if (const auto reply = Get<HistoryMessageReply>()) {
-		if (reply->replyToLink()) {
-			reply->setReplyToLinkFrom(this);
-		}
-		changeReplyToTopCounter(reply, 1);
-	}
-}
-
-void HistoryMessage::incrementReplyToTopCounter() {
-	if (const auto reply = Get<HistoryMessageReply>()) {
-		changeReplyToTopCounter(reply, 1);
-	}
-}
-
-void HistoryMessage::changeReplyToTopCounter(
-		not_null<HistoryMessageReply*> reply,
-		int delta) {
-	if (!isRegular() || !_history->peer->isMegagroup()) {
-		return;
-	} else if (delta > 0) {
-		_history->session().changes().messageUpdated(
-			this,
-			Data::MessageUpdate::Flag::ReplyToTopAdded);
-	}
-	const auto topId = reply->replyToTop();
-	if (!topId) {
-		return;
-	}
-	const auto top = _history->owner().message(_history->peer->id, topId);
-	if (!top) {
-		return;
-	}
-	const auto from = displayFrom();
-	const auto replier = from ? from->id : PeerId();
-	top->changeRepliesCount(delta, replier);
-	if (const auto original = top->lookupDiscussionPostOriginal()) {
-		original->changeRepliesCount(delta, replier);
-	}
-}
-
-void HistoryMessage::dependencyItemRemoved(HistoryItem *dependency) {
-	if (const auto reply = Get<HistoryMessageReply>()) {
-		const auto documentId = reply->replyToDocumentId;
-		reply->itemRemoved(this, dependency);
-		if (documentId != reply->replyToDocumentId
-			&& generateLocalEntitiesByReply()) {
-			history()->owner().requestItemTextRefresh(this);
-		}
-	}
-}
-
-QString HistoryMessage::notificationHeader() const {
-	if (out() && isFromScheduled() && !_history->peer->isSelf()) {
-		return tr::lng_from_you(tr::now);
-	} else if (!_history->peer->isUser() && !isPost()) {
-		return from()->name();
-	}
-	return QString();
-}
-
-std::unique_ptr<HistoryView::Element> HistoryMessage::createView(
-		not_null<HistoryView::ElementDelegate*> delegate,
-		HistoryView::Element *replacing) {
-	return delegate->elementCreate(this, replacing);
-}
-
-HistoryMessage::~HistoryMessage() {
-	_media.reset();
-	clearSavedMedia();
-	if (auto reply = Get<HistoryMessageReply>()) {
-		reply->clearData(this);
-	}
-}
diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h
deleted file mode 100644
index 37402441f..000000000
--- a/Telegram/SourceFiles/history/history_message.h
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
-This file is part of Telegram Desktop,
-the official desktop application for the Telegram messaging service.
-
-For license and copyright information please follow this link:
-https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
-*/
-#pragma once
-
-#include "history/history_item.h"
-
-namespace Api {
-struct SendAction;
-struct SendOptions;
-} // namespace Api
-
-namespace Data {
-class Thread;
-struct SponsoredFrom;
-} // namespace Data
-
-namespace HistoryView {
-class Message;
-} // namespace HistoryView
-
-struct HistoryMessageEdited;
-struct HistoryMessageReply;
-struct HistoryMessageViews;
-struct HistoryMessageMarkupData;
-
-void RequestDependentMessageData(
-	not_null<HistoryItem*> item,
-	PeerId peerId,
-	MsgId msgId);
-[[nodiscard]] MessageFlags NewMessageFlags(not_null<PeerData*> peer);
-[[nodiscard]] bool ShouldSendSilent(
-	not_null<PeerData*> peer,
-	const Api::SendOptions &options);
-[[nodiscard]] MsgId LookupReplyToTop(
-	not_null<History*> history,
-	MsgId replyToId);
-[[nodiscard]] MTPMessageReplyHeader NewMessageReplyHeader(
-	const Api::SendAction &action);
-
-struct SendingErrorRequest {
-	MsgId topicRootId = 0;
-	const HistoryItemsList *forward = nullptr;
-	const TextWithTags *text = nullptr;
-	bool ignoreSlowmodeCountdown = false;
-};
-[[nodiscard]] QString GetErrorTextForSending(
-	not_null<PeerData*> peer,
-	SendingErrorRequest request);
-[[nodiscard]] QString GetErrorTextForSending(
-	not_null<Data::Thread*> thread,
-	SendingErrorRequest request);
-
-[[nodiscard]] TextWithEntities DropCustomEmoji(TextWithEntities text);
-
-class HistoryMessage final : public HistoryItem {
-public:
-	HistoryMessage(
-		not_null<History*> history,
-		MsgId id,
-		const MTPDmessage &data,
-		MessageFlags localFlags);
-	HistoryMessage(
-		not_null<History*> history,
-		MsgId id,
-		const MTPDmessageService &data,
-		MessageFlags localFlags);
-	HistoryMessage(
-		not_null<History*> history,
-		MsgId id,
-		MessageFlags flags,
-		TimeId date,
-		PeerId from,
-		const QString &postAuthor,
-		not_null<HistoryItem*> original,
-		MsgId topicRootId); // local forwarded
-	HistoryMessage(
-		not_null<History*> history,
-		MsgId id,
-		MessageFlags flags,
-		MsgId replyTo,
-		UserId viaBotId,
-		TimeId date,
-		PeerId from,
-		const QString &postAuthor,
-		const TextWithEntities &textWithEntities,
-		const MTPMessageMedia &media,
-		HistoryMessageMarkupData &&markup,
-		uint64 groupedId); // local message
-	HistoryMessage(
-		not_null<History*> history,
-		MsgId id,
-		MessageFlags flags,
-		MsgId replyTo,
-		UserId viaBotId,
-		TimeId date,
-		PeerId from,
-		const QString &postAuthor,
-		not_null<DocumentData*> document,
-		const TextWithEntities &caption,
-		HistoryMessageMarkupData &&markup); // local document
-	HistoryMessage(
-		not_null<History*> history,
-		MsgId id,
-		MessageFlags flags,
-		MsgId replyTo,
-		UserId viaBotId,
-		TimeId date,
-		PeerId from,
-		const QString &postAuthor,
-		not_null<PhotoData*> photo,
-		const TextWithEntities &caption,
-		HistoryMessageMarkupData &&markup); // local photo
-	HistoryMessage(
-		not_null<History*> history,
-		MsgId id,
-		MessageFlags flags,
-		MsgId replyTo,
-		UserId viaBotId,
-		TimeId date,
-		PeerId from,
-		const QString &postAuthor,
-		not_null<GameData*> game,
-		HistoryMessageMarkupData &&markup); // local game
-	HistoryMessage(
-		not_null<History*> history,
-		MsgId id,
-		Data::SponsoredFrom from,
-		const TextWithEntities &textWithEntities,
-		HistoryItem *injectedAfter); // sponsored
-
-	void refreshMedia(const MTPMessageMedia *media);
-	void refreshSentMedia(const MTPMessageMedia *media);
-	void returnSavedMedia() override;
-	void setMedia(const MTPMessageMedia &media);
-	void checkBuyButton() override;
-	[[nodiscard]] static std::unique_ptr<Data::Media> CreateMedia(
-		not_null<HistoryMessage*> item,
-		const MTPMessageMedia &media);
-
-	[[nodiscard]] bool allowsForward() const override;
-	[[nodiscard]] bool allowsSendNow() const override;
-	[[nodiscard]] bool allowsEdit(TimeId now) const override;
-
-	bool changeViewsCount(int count) override;
-	void setForwardsCount(int count) override;
-	void setReplies(HistoryMessageRepliesData &&data) override;
-	void clearReplies() override;
-	void changeRepliesCount(int delta, PeerId replier) override;
-	void setReplyFields(
-		MsgId replyTo,
-		MsgId replyToTop,
-		bool isForumPost) override;
-	void setPostAuthor(const QString &author) override;
-	void setRealId(MsgId newId) override;
-	void incrementReplyToTopCounter() override;
-
-	void dependencyItemRemoved(HistoryItem *dependency) override;
-
-	[[nodiscard]] QString notificationHeader() const override;
-
-	// Looks on:
-	//   f_edit_hide
-	//   f_edit_date
-	//   f_entities
-	//   f_reply_markup
-	//   f_media
-	//   f_views
-	//   f_forwards
-	//   f_replies
-	//   f_ttl_period
-	void applyEdition(HistoryMessageEdition &&edition) override;
-
-	void applyEdition(const MTPDmessageService &message) override;
-	void applyEdition(const MTPMessageExtendedMedia &media) override;
-	void updateSentContent(
-		const TextWithEntities &textWithEntities,
-		const MTPMessageMedia *media) override;
-	void updateReplyMarkup(HistoryMessageMarkupData &&markup) override;
-	void updateForwardedInfo(const MTPMessageFwdHeader *fwd) override;
-	void contributeToSlowmode(TimeId realDate = 0) override;
-
-	void addToUnreadThings(HistoryUnreadThings::AddType type) override;
-	void destroyHistoryEntry() override;
-	[[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const override;
-
-	[[nodiscard]] MsgId replyToId() const override;
-	[[nodiscard]] MsgId replyToTop() const override;
-	[[nodiscard]] MsgId topicRootId() const override;
-
-	void setText(const TextWithEntities &textWithEntities) override;
-	[[nodiscard]] TextWithEntities originalText() const override;
-	[[nodiscard]] auto originalTextWithLocalEntities() const
-		-> TextWithEntities override;
-	[[nodiscard]] TextForMimeData clipboardText() const override;
-
-	[[nodiscard]] int viewsCount() const override;
-	[[nodiscard]] int repliesCount() const override;
-	[[nodiscard]] bool repliesAreComments() const override;
-	[[nodiscard]] bool externalReply() const override;
-
-	void setCommentsInboxReadTill(MsgId readTillId) override;
-	void setCommentsMaxId(MsgId maxId) override;
-	void setCommentsPossibleMaxId(MsgId possibleMaxId) override;
-	[[nodiscard]] bool areCommentsUnread() const override;
-
-	[[nodiscard]] FullMsgId commentsItemId() const override;
-	void setCommentsItemId(FullMsgId id) override;
-	bool updateDependencyItem() override;
-	[[nodiscard]] MsgId dependencyMsgId() const override {
-		return replyToId();
-	}
-
-	void applySentMessage(const MTPDmessage &data) override;
-	void applySentMessage(
-		const QString &text,
-		const MTPDupdateShortSentMessage &data,
-		bool wasAlready) override;
-
-	[[nodiscard]] std::unique_ptr<HistoryView::Element> createView(
-		not_null<HistoryView::ElementDelegate*> delegate,
-		HistoryView::Element *replacing = nullptr) override;
-
-	~HistoryMessage();
-
-private:
-	void setTextValue(TextWithEntities text);
-	[[nodiscard]] bool isTooOldForEdit(TimeId now) const;
-	[[nodiscard]] bool isLegacyMessage() const {
-		return _flags & MessageFlag::Legacy;
-	}
-
-	[[nodiscard]] bool checkCommentsLinkedChat(ChannelId id) const;
-
-	// For an invoice button we replace the button text with a "Receipt" key.
-	// It should show the receipt for the payed invoice. Still let mobile apps do that.
-	void replaceBuyWithReceiptInMarkup();
-
-	void setReplyMarkup(HistoryMessageMarkupData &&markup);
-
-	struct CreateConfig;
-	void createComponentsHelper(
-		MessageFlags flags,
-		MsgId replyTo,
-		UserId viaBotId,
-		const QString &postAuthor,
-		HistoryMessageMarkupData &&markup);
-	void createComponents(CreateConfig &&config);
-	void setupForwardedComponent(const CreateConfig &config);
-	void changeReplyToTopCounter(
-		not_null<HistoryMessageReply*> reply,
-		int delta);
-	void refreshRepliesText(
-		not_null<HistoryMessageViews*> views,
-		bool forceResize = false);
-	void setSponsoredFrom(const Data::SponsoredFrom &from);
-
-	static void FillForwardedInfo(
-		CreateConfig &config,
-		const MTPDmessageFwdHeader &data);
-
-	[[nodiscard]] bool generateLocalEntitiesByReply() const;
-	[[nodiscard]] TextWithEntities withLocalEntities(
-		const TextWithEntities &textWithEntities) const;
-
-	[[nodiscard]] bool checkRepliesPts(
-		const HistoryMessageRepliesData &data) const;
-
-	friend class HistoryView::Element;
-	friend class HistoryView::Message;
-
-};
diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp
deleted file mode 100644
index 9d9ea2d3b..000000000
--- a/Telegram/SourceFiles/history/history_service.cpp
+++ /dev/null
@@ -1,1821 +0,0 @@
-/*
-This file is part of Telegram Desktop,
-the official desktop application for the Telegram messaging service.
-
-For license and copyright information please follow this link:
-https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
-*/
-#include "history/history_service.h"
-
-#include "chat_helpers/stickers_gift_box_pack.h"
-#include "lang/lang_keys.h"
-#include "mainwidget.h"
-#include "main/main_session.h"
-#include "main/main_domain.h" // Core::App().domain().activate().
-#include "menu/menu_ttl_validator.h"
-#include "apiwrap.h"
-#include "history/history.h"
-#include "history/view/media/history_view_invoice.h"
-#include "history/history_message.h"
-#include "history/history_item_components.h"
-#include "history/view/history_view_service_message.h"
-#include "history/view/history_view_item_preview.h"
-#include "data/data_folder.h"
-#include "data/data_forum.h"
-#include "data/data_forum_topic.h"
-#include "data/data_session.h"
-#include "data/data_media_types.h"
-#include "data/data_game.h"
-#include "data/data_channel.h"
-#include "data/data_user.h"
-#include "data/data_chat.h"
-#include "data/data_changes.h"
-#include "data/data_group_call.h" // Data::GroupCall::id().
-#include "data/stickers/data_custom_emoji.h"
-#include "core/application.h"
-#include "core/click_handler_types.h"
-#include "base/unixtime.h"
-#include "base/timer_rpl.h"
-#include "calls/calls_instance.h" // Core::App().calls().joinGroupCall.
-#include "window/notifications_manager.h"
-#include "window/window_controller.h"
-#include "window/window_session_controller.h"
-#include "storage/storage_shared_media.h"
-#include "payments/payments_checkout_process.h" // CheckoutProcess::Start.
-#include "ui/text/format_values.h"
-#include "ui/text/text_utilities.h"
-
-namespace {
-
-constexpr auto kPinnedMessageTextLimit = 16;
-
-using ItemPreview = HistoryView::ItemPreview;
-
-[[nodiscard]] bool PeerCallKnown(not_null<PeerData*> peer) {
-	if (peer->groupCall() != nullptr) {
-		return true;
-	} else if (const auto chat = peer->asChat()) {
-		return !(chat->flags() & ChatDataFlag::CallActive);
-	} else if (const auto channel = peer->asChannel()) {
-		return !(channel->flags() & ChannelDataFlag::CallActive);
-	}
-	return true;
-}
-
-[[nodiscard]] rpl::producer<bool> PeerHasThisCallValue(
-		not_null<PeerData*> peer,
-		CallId id) {
-	return peer->session().changes().peerFlagsValue(
-		peer,
-		Data::PeerUpdate::Flag::GroupCall
-	) | rpl::filter([=] {
-		return PeerCallKnown(peer);
-	}) | rpl::map([=] {
-		const auto call = peer->groupCall();
-		return (call && call->id() == id);
-	}) | rpl::distinct_until_changed(
-	) | rpl::take_while([=](bool hasThisCall) {
-		return hasThisCall;
-	}) | rpl::then(
-		rpl::single(false)
-	);
-}
-
-[[nodiscard]] CallId CallIdFromInput(const MTPInputGroupCall &data) {
-	return data.match([&](const MTPDinputGroupCall &data) {
-		return data.vid().v;
-	});
-}
-
-[[nodiscard]] ClickHandlerPtr GroupCallClickHandler(
-		not_null<PeerData*> peer,
-		CallId callId) {
-	return std::make_shared<LambdaClickHandler>([=] {
-		const auto call = peer->groupCall();
-		if (call && call->id() == callId) {
-			const auto &windows = peer->session().windows();
-			if (windows.empty()) {
-				Core::App().domain().activate(&peer->session().account());
-				if (windows.empty()) {
-					return;
-				}
-			}
-			windows.front()->startOrJoinGroupCall(peer, {});
-		}
-	});
-}
-
-} // namespace
-
-void HistoryService::setMessageByAction(const MTPmessageAction &action) {
-	auto prepareChatAddUserText = [this](const MTPDmessageActionChatAddUser &action) {
-		auto result = PreparedText{};
-		auto &users = action.vusers().v;
-		if (users.size() == 1) {
-			auto u = history()->owner().user(users[0].v);
-			if (u == _from) {
-				result.links.push_back(fromLink());
-				result.text = tr::lng_action_user_joined(
-					tr::now,
-					lt_from,
-					fromLinkText(), // Link 1.
-					Ui::Text::WithEntities);
-			} else {
-				result.links.push_back(fromLink());
-				result.links.push_back(u->createOpenLink());
-				result.text = tr::lng_action_add_user(
-					tr::now,
-					lt_from,
-					fromLinkText(), // Link 1.
-					lt_user,
-					Ui::Text::Link(u->name(), 2), // Link 2.
-					Ui::Text::WithEntities);
-			}
-		} else if (users.isEmpty()) {
-			result.links.push_back(fromLink());
-			result.text = tr::lng_action_add_user(
-				tr::now,
-				lt_from,
-				fromLinkText(), // Link 1.
-				lt_user,
-				{ .text = u"somebody"_q },
-				Ui::Text::WithEntities);
-		} else {
-			result.links.push_back(fromLink());
-			for (auto i = 0, l = int(users.size()); i != l; ++i) {
-				auto user = history()->owner().user(users[i].v);
-				result.links.push_back(user->createOpenLink());
-
-				auto linkText = Ui::Text::Link(user->name(), 2 + i);
-				if (i == 0) {
-					result.text = linkText;
-				} else if (i + 1 == l) {
-					result.text = tr::lng_action_add_users_and_last(
-						tr::now,
-						lt_accumulated,
-						result.text,
-						lt_user,
-						linkText,
-						Ui::Text::WithEntities);
-				} else {
-					result.text = tr::lng_action_add_users_and_one(
-						tr::now,
-						lt_accumulated,
-						result.text,
-						lt_user,
-						linkText,
-						Ui::Text::WithEntities);
-				}
-			}
-			result.text = tr::lng_action_add_users_many(
-				tr::now,
-				lt_from,
-				fromLinkText(), // Link 1.
-				lt_users,
-				result.text,
-				Ui::Text::WithEntities);
-		}
-		return result;
-	};
-
-	auto prepareChatJoinedByLink = [this](const MTPDmessageActionChatJoinedByLink &action) {
-		auto result = PreparedText{};
-		result.links.push_back(fromLink());
-		result.text = tr::lng_action_user_joined_by_link(
-			tr::now,
-			lt_from,
-			fromLinkText(), // Link 1.
-			Ui::Text::WithEntities);
-		return result;
-	};
-
-	auto prepareChatCreate = [this](const MTPDmessageActionChatCreate &action) {
-		auto result = PreparedText{};
-		result.links.push_back(fromLink());
-		result.text = tr::lng_action_created_chat(
-			tr::now,
-			lt_from,
-			fromLinkText(), // Link 1.
-			lt_title,
-			{ .text = qs(action.vtitle()) },
-			Ui::Text::WithEntities);
-		return result;
-	};
-
-	auto prepareChannelCreate = [this](const MTPDmessageActionChannelCreate &action) {
-		auto result = PreparedText {};
-		if (isPost()) {
-			result.text = tr::lng_action_created_channel(
-				tr::now,
-				Ui::Text::WithEntities);
-		} else {
-			result.links.push_back(fromLink());
-			result.text = tr::lng_action_created_chat(
-				tr::now,
-				lt_from,
-				fromLinkText(), // Link 1.
-				lt_title,
-				{ .text = qs(action.vtitle()) },
-				Ui::Text::WithEntities);
-		}
-		return result;
-	};
-
-	auto prepareChatDeletePhoto = [this] {
-		auto result = PreparedText{};
-		if (isPost()) {
-			result.text = tr::lng_action_removed_photo_channel(
-				tr::now,
-				Ui::Text::WithEntities);
-		} else {
-			result.links.push_back(fromLink());
-			result.text = tr::lng_action_removed_photo(
-				tr::now,
-				lt_from,
-				fromLinkText(), // Link 1.
-				Ui::Text::WithEntities);
-		}
-		return result;
-	};
-
-	auto prepareChatDeleteUser = [this](const MTPDmessageActionChatDeleteUser &action) {
-		auto result = PreparedText{};
-		if (peerFromUser(action.vuser_id()) == _from->id) {
-			result.links.push_back(fromLink());
-			result.text = tr::lng_action_user_left(
-				tr::now,
-				lt_from,
-				fromLinkText(), // Link 1.
-				Ui::Text::WithEntities);
-		} else {
-			auto user = history()->owner().user(action.vuser_id().v);
-			result.links.push_back(fromLink());
-			result.links.push_back(user->createOpenLink());
-			result.text = tr::lng_action_kick_user(
-				tr::now,
-				lt_from,
-				fromLinkText(), // Link 1.
-				lt_user,
-				Ui::Text::Link(user->name(), 2),  // Link 2.
-				Ui::Text::WithEntities);
-		}
-		return result;
-	};
-
-	auto prepareChatEditPhoto = [this](const MTPDmessageActionChatEditPhoto &action) {
-		auto result = PreparedText{};
-		if (isPost()) {
-			result.text = tr::lng_action_changed_photo_channel(
-				tr::now,
-				Ui::Text::WithEntities);
-		} else {
-			result.links.push_back(fromLink());
-			result.text = tr::lng_action_changed_photo(
-				tr::now,
-				lt_from,
-				fromLinkText(), // Link 1.
-				Ui::Text::WithEntities);
-		}
-		return result;
-	};
-
-	auto prepareChatEditTitle = [this](const MTPDmessageActionChatEditTitle &action) {
-		auto result = PreparedText{};
-		if (isPost()) {
-			result.text = tr::lng_action_changed_title_channel(
-				tr::now,
-				lt_title,
-				{ .text = (qs(action.vtitle())) },
-				Ui::Text::WithEntities);
-		} else {
-			result.links.push_back(fromLink());
-			result.text = tr::lng_action_changed_title(
-				tr::now,
-				lt_from,
-				fromLinkText(), // Link 1.
-				lt_title,
-				{ .text = qs(action.vtitle()) },
-				Ui::Text::WithEntities);
-		}
-		return result;
-	};
-
-	auto prepareScreenshotTaken = [this] {
-		auto result = PreparedText{};
-		if (out()) {
-			result.text = tr::lng_action_you_took_screenshot(
-				tr::now,
-				Ui::Text::WithEntities);
-		} else {
-			result.links.push_back(fromLink());
-			result.text = tr::lng_action_took_screenshot(
-				tr::now,
-				lt_from,
-				fromLinkText(), // Link 1.
-				Ui::Text::WithEntities);
-		}
-		return result;
-	};
-
-	auto prepareCustomAction = [&](const MTPDmessageActionCustomAction &action) {
-		auto result = PreparedText{};
-		result.text = { .text = qs(action.vmessage()) };
-		return result;
-	};
-
-	auto prepareBotAllowed = [&](const MTPDmessageActionBotAllowed &action) {
-		auto result = PreparedText{};
-		const auto domain = qs(action.vdomain());
-		result.text = tr::lng_action_bot_allowed_from_domain(
-			tr::now,
-			lt_domain,
-			Ui::Text::Link(domain, u"http://"_q + domain),
-			Ui::Text::WithEntities);
-		return result;
-	};
-
-	auto prepareSecureValuesSent = [&](const MTPDmessageActionSecureValuesSent &action) {
-		auto result = PreparedText{};
-		auto documents = QStringList();
-		for (const auto &type : action.vtypes().v) {
-			documents.push_back([&] {
-				switch (type.type()) {
-				case mtpc_secureValueTypePersonalDetails:
-					return tr::lng_action_secure_personal_details(tr::now);
-				case mtpc_secureValueTypePassport:
-				case mtpc_secureValueTypeDriverLicense:
-				case mtpc_secureValueTypeIdentityCard:
-				case mtpc_secureValueTypeInternalPassport:
-					return tr::lng_action_secure_proof_of_identity(tr::now);
-				case mtpc_secureValueTypeAddress:
-					return tr::lng_action_secure_address(tr::now);
-				case mtpc_secureValueTypeUtilityBill:
-				case mtpc_secureValueTypeBankStatement:
-				case mtpc_secureValueTypeRentalAgreement:
-				case mtpc_secureValueTypePassportRegistration:
-				case mtpc_secureValueTypeTemporaryRegistration:
-					return tr::lng_action_secure_proof_of_address(tr::now);
-				case mtpc_secureValueTypePhone:
-					return tr::lng_action_secure_phone(tr::now);
-				case mtpc_secureValueTypeEmail:
-					return tr::lng_action_secure_email(tr::now);
-				}
-				Unexpected("Type in prepareSecureValuesSent.");
-			}());
-		};
-		result.links.push_back(history()->peer->createOpenLink());
-		result.text = tr::lng_action_secure_values_sent(
-			tr::now,
-			lt_user,
-			Ui::Text::Link(history()->peer->name(), QString()), // Link 1.
-			lt_documents,
-			{ .text = documents.join(", ") },
-			Ui::Text::WithEntities);
-		return result;
-	};
-
-	auto prepareContactSignUp = [this] {
-		auto result = PreparedText{};
-		result.links.push_back(fromLink());
-		result.text = tr::lng_action_user_registered(
-			tr::now,
-			lt_from,
-			fromLinkText(), // Link 1.
-			Ui::Text::WithEntities);
-		return result;
-	};
-
-	auto prepareProximityReached = [this](const MTPDmessageActionGeoProximityReached &action) {
-		auto result = PreparedText{};
-		const auto fromId = peerFromMTP(action.vfrom_id());
-		const auto fromPeer = history()->owner().peer(fromId);
-		const auto toId = peerFromMTP(action.vto_id());
-		const auto toPeer = history()->owner().peer(toId);
-		const auto selfId = _from->session().userPeerId();
-		const auto distanceMeters = action.vdistance().v;
-		const auto distance = [&] {
-			if (distanceMeters >= 1000) {
-				const auto km = (10 * (distanceMeters / 10)) / 1000.;
-				return tr::lng_action_proximity_distance_km(
-					tr::now,
-					lt_count,
-					km);
-			} else {
-				return tr::lng_action_proximity_distance_m(
-					tr::now,
-					lt_count,
-					distanceMeters);
-			}
-		}();
-		result.text = [&] {
-			if (fromId == selfId) {
-				result.links.push_back(toPeer->createOpenLink());
-				return tr::lng_action_you_proximity_reached(
-					tr::now,
-					lt_distance,
-					{ .text = distance },
-					lt_user,
-					Ui::Text::Link(toPeer->name(), QString()), // Link 1.
-					Ui::Text::WithEntities);
-			} else if (toId == selfId) {
-				result.links.push_back(fromPeer->createOpenLink());
-				return tr::lng_action_proximity_reached_you(
-					tr::now,
-					lt_from,
-					Ui::Text::Link(fromPeer->name(), QString()), // Link 1.
-					lt_distance,
-					{ .text = distance },
-					Ui::Text::WithEntities);
-			} else {
-				result.links.push_back(fromPeer->createOpenLink());
-				result.links.push_back(toPeer->createOpenLink());
-				return tr::lng_action_proximity_reached(
-					tr::now,
-					lt_from,
-					Ui::Text::Link(fromPeer->name(), 1), // Link 1.
-					lt_distance,
-					{ .text = distance },
-					lt_user,
-					Ui::Text::Link(toPeer->name(), 2), // Link 2.
-					Ui::Text::WithEntities);
-			}
-		}();
-		return result;
-	};
-
-	auto prepareGroupCall = [this](const MTPDmessageActionGroupCall &action) {
-		auto result = PreparedText{};
-		if (const auto duration = action.vduration()) {
-			const auto seconds = duration->v;
-			const auto days = seconds / 86400;
-			const auto hours = seconds / 3600;
-			const auto minutes = seconds / 60;
-			auto text = (days > 1)
-				? tr::lng_days(tr::now, lt_count, days)
-				: (hours > 1)
-				? tr::lng_hours(tr::now, lt_count, hours)
-				: (minutes > 1)
-				? tr::lng_minutes(tr::now, lt_count, minutes)
-				: tr::lng_seconds(tr::now, lt_count, seconds);
-			if (history()->peer->isBroadcast()) {
-				result.text = tr::lng_action_group_call_finished(
-					tr::now,
-					lt_duration,
-					{ .text = text },
-					Ui::Text::WithEntities);
-			} else {
-				result.links.push_back(fromLink());
-				result.text = tr::lng_action_group_call_finished_group(
-					tr::now,
-					lt_from,
-					fromLinkText(), // Link 1.
-					lt_duration,
-					{ .text = text },
-					Ui::Text::WithEntities);
-			}
-			return result;
-		}
-		if (history()->peer->isBroadcast()) {
-			result.text = tr::lng_action_group_call_started_channel(
-				tr::now,
-				Ui::Text::WithEntities);
-		} else {
-			result.links.push_back(fromLink());
-			result.text = tr::lng_action_group_call_started_group(
-				tr::now,
-				lt_from,
-				fromLinkText(), // Link 1.
-				Ui::Text::WithEntities);
-		}
-		return result;
-	};
-
-	auto prepareInviteToGroupCall = [this](const MTPDmessageActionInviteToGroupCall &action) {
-		const auto callId = CallIdFromInput(action.vcall());
-		const auto owner = &history()->owner();
-		const auto peer = history()->peer;
-		for (const auto &id : action.vusers().v) {
-			const auto user = owner->user(id.v);
-			if (callId) {
-				owner->registerInvitedToCallUser(callId, peer, user);
-			}
-		};
-		const auto linkCallId = PeerHasThisCall(peer, callId).value_or(false)
-			? callId
-			: 0;
-		return prepareInvitedToCallText(action.vusers().v, linkCallId);
-	};
-
-	auto prepareSetMessagesTTL = [this](const MTPDmessageActionSetMessagesTTL &action) {
-		auto result = PreparedText{};
-		const auto period = action.vperiod().v;
-		const auto duration = (period == 5)
-			? u"5 seconds"_q
-			: Ui::FormatTTL(period);
-		if (const auto from = action.vauto_setting_from()) {
-			if (const auto peer = _from->owner().peer(peerFromUser(*from))) {
-				if (!peer->isSelf() && period) {
-					result.text = tr::lng_action_ttl_global(
-						tr::now,
-						lt_from,
-						fromLinkText(), // Link 1.
-						lt_duration,
-						{ .text = duration },
-						Ui::Text::WithEntities);
-					return result;
-				}
-			}
-		}
-		if (isPost()) {
-			if (!period) {
-				result.text = tr::lng_action_ttl_removed_channel(
-					tr::now,
-					Ui::Text::WithEntities);
-			} else {
-				result.text = tr::lng_action_ttl_changed_channel(
-					tr::now,
-					lt_duration,
-					{ .text = duration },
-					Ui::Text::WithEntities);
-			}
-		} else if (_from->isSelf()) {
-			if (!period) {
-				result.text = tr::lng_action_ttl_removed_you(
-					tr::now,
-					Ui::Text::WithEntities);
-			} else {
-				result.text = tr::lng_action_ttl_changed_you(
-					tr::now,
-					lt_duration,
-					{ .text = duration },
-					Ui::Text::WithEntities);
-			}
-		} else {
-			result.links.push_back(fromLink());
-			if (!period) {
-				result.text = tr::lng_action_ttl_removed(
-					tr::now,
-					lt_from,
-					fromLinkText(), // Link 1.
-					Ui::Text::WithEntities);
-			} else {
-				result.text = tr::lng_action_ttl_changed(
-					tr::now,
-					lt_from,
-					fromLinkText(), // Link 1.
-					lt_duration,
-					{ .text = duration },
-					Ui::Text::WithEntities);
-			}
-		}
-		return result;
-	};
-
-	auto prepareSetChatTheme = [this](const MTPDmessageActionSetChatTheme &action) {
-		auto result = PreparedText{};
-		const auto text = qs(action.vemoticon());
-		if (!text.isEmpty()) {
-			if (_from->isSelf()) {
-				result.text = tr::lng_action_you_theme_changed(
-					tr::now,
-					lt_emoji,
-					{ .text = text },
-					Ui::Text::WithEntities);
-			} else {
-				result.links.push_back(fromLink());
-				result.text = tr::lng_action_theme_changed(
-					tr::now,
-					lt_from,
-					fromLinkText(), // Link 1.
-					lt_emoji,
-					{ .text = text },
-					Ui::Text::WithEntities);
-			}
-		} else {
-			if (_from->isSelf()) {
-				result.text = tr::lng_action_you_theme_disabled(
-					tr::now,
-					Ui::Text::WithEntities);
-			} else {
-				result.links.push_back(fromLink());
-				result.text = tr::lng_action_theme_disabled(
-					tr::now,
-					lt_from,
-					fromLinkText(), // Link 1.
-					Ui::Text::WithEntities);
-			}
-		}
-		return result;
-	};
-
-	auto prepareChatJoinedByRequest = [this](const MTPDmessageActionChatJoinedByRequest &action) {
-		auto result = PreparedText{};
-		result.links.push_back(fromLink());
-		result.text = tr::lng_action_user_joined_by_request(
-			tr::now,
-			lt_from,
-			fromLinkText(), // Link 1.
-			Ui::Text::WithEntities);
-		return result;
-	};
-
-	auto prepareWebViewDataSent = [](const MTPDmessageActionWebViewDataSent &action) {
-		auto result = PreparedText{};
-		result.text = tr::lng_action_webview_data_done(
-			tr::now,
-			lt_text,
-			{ .text = qs(action.vtext()) },
-			Ui::Text::WithEntities);
-		return result;
-	};
-
-	auto prepareGiftPremium = [&](
-			const MTPDmessageActionGiftPremium &action) {
-		auto result = PreparedText{};
-		const auto isSelf = (_from->id == _from->session().userPeerId());
-		const auto peer = isSelf ? history()->peer : _from;
-		history()->session().giftBoxStickersPacks().load();
-		const auto amount = action.vamount().v;
-		const auto currency = qs(action.vcurrency());
-		result.links.push_back(peer->createOpenLink());
-		result.text = (isSelf
-			? tr::lng_action_gift_received_me
-			: tr::lng_action_gift_received)(
-				tr::now,
-				lt_user,
-				Ui::Text::Link(peer->name(), 1), // Link 1.
-				lt_cost,
-				{ Ui::FillAmountAndCurrency(amount, currency) },
-				Ui::Text::WithEntities);
-		return result;
-	};
-
-	auto prepareTopicCreate = [&](const MTPDmessageActionTopicCreate &action) {
-		auto result = PreparedText{};
-		const auto topicUrl = u"internal:url:https://t.me/c/%1/%2"_q
-			.arg(peerToChannel(history()->peer->id).bare)
-			.arg(id.bare);
-		result.text = tr::lng_action_topic_created(
-			tr::now,
-			lt_topic,
-			Ui::Text::Link(
-				Data::ForumTopicIconWithTitle(
-					id,
-					action.vicon_emoji_id().value_or_empty(),
-					qs(action.vtitle())),
-				topicUrl),
-			Ui::Text::WithEntities);
-		return result;
-	};
-
-	auto prepareTopicEdit = [&](const MTPDmessageActionTopicEdit &action) {
-		auto result = PreparedText{};
-		if (const auto closed = action.vclosed()) {
-			result.text = { mtpIsTrue(*closed)
-				? tr::lng_action_topic_closed_inside(tr::now)
-				: tr::lng_action_topic_reopened_inside(tr::now) };
-		} else if (const auto hidden = action.vhidden()) {
-			result.text = { mtpIsTrue(*hidden)
-				? tr::lng_action_topic_hidden_inside(tr::now)
-				: tr::lng_action_topic_unhidden_inside(tr::now) };
-		} else if (!action.vtitle()) {
-			if (const auto icon = action.vicon_emoji_id()) {
-				if (const auto iconId = icon->v) {
-					result.links.push_back(fromLink());
-					result.text = tr::lng_action_topic_icon_changed(
-						tr::now,
-						lt_from,
-						fromLinkText(), // Link 1.
-						lt_link,
-						{ tr::lng_action_topic_placeholder(tr::now) },
-						lt_emoji,
-						Data::SingleCustomEmoji(iconId),
-						Ui::Text::WithEntities);
-				} else {
-					result.links.push_back(fromLink());
-					result.text = tr::lng_action_topic_icon_removed(
-						tr::now,
-						lt_from,
-						fromLinkText(), // Link 1.
-						lt_link,
-						{ tr::lng_action_topic_placeholder(tr::now) },
-						Ui::Text::WithEntities);
-				}
-			}
-		} else {
-			result.links.push_back(fromLink());
-			result.text = tr::lng_action_topic_renamed(
-				tr::now,
-				lt_from,
-				fromLinkText(), // Link 1.
-				lt_link,
-				{ tr::lng_action_topic_placeholder(tr::now) },
-				lt_title,
-				Data::ForumTopicIconWithTitle(
-					topicRootId(),
-					action.vicon_emoji_id().value_or_empty(),
-					qs(*action.vtitle())),
-				Ui::Text::WithEntities);
-		}
-		if (result.text.empty()) {
-			result.text = { tr::lng_message_empty(tr::now) };
-		}
-		return result;
-	};
-
-	setServiceText(action.match([&](
-			const MTPDmessageActionChatAddUser &data) {
-		return prepareChatAddUserText(data);
-	}, [&](const MTPDmessageActionChatJoinedByLink &data) {
-		return prepareChatJoinedByLink(data);
-	}, [&](const MTPDmessageActionChatCreate &data) {
-		return prepareChatCreate(data);
-	}, [](const MTPDmessageActionChatMigrateTo &) {
-		return PreparedText();
-	}, [](const MTPDmessageActionChannelMigrateFrom &) {
-		return PreparedText();
-	}, [](const MTPDmessageActionHistoryClear &) {
-		return PreparedText();
-	}, [&](const MTPDmessageActionChannelCreate &data) {
-		return prepareChannelCreate(data);
-	}, [&](const MTPDmessageActionChatDeletePhoto &) {
-		return prepareChatDeletePhoto();
-	}, [&](const MTPDmessageActionChatDeleteUser &data) {
-		return prepareChatDeleteUser(data);
-	}, [&](const MTPDmessageActionChatEditPhoto &data) {
-		return prepareChatEditPhoto(data);
-	}, [&](const MTPDmessageActionChatEditTitle &data) {
-		return prepareChatEditTitle(data);
-	}, [&](const MTPDmessageActionPinMessage &) {
-		return preparePinnedText();
-	}, [&](const MTPDmessageActionGameScore &) {
-		return prepareGameScoreText();
-	}, [&](const MTPDmessageActionPhoneCall &) -> PreparedText {
-		Unexpected("PhoneCall type in HistoryService.");
-	}, [&](const MTPDmessageActionPaymentSent &) {
-		return preparePaymentSentText();
-	}, [&](const MTPDmessageActionScreenshotTaken &) {
-		return prepareScreenshotTaken();
-	}, [&](const MTPDmessageActionCustomAction &data) {
-		return prepareCustomAction(data);
-	}, [&](const MTPDmessageActionBotAllowed &data) {
-		return prepareBotAllowed(data);
-	}, [&](const MTPDmessageActionSecureValuesSent &data) {
-		return prepareSecureValuesSent(data);
-	}, [&](const MTPDmessageActionContactSignUp &data) {
-		return prepareContactSignUp();
-	}, [&](const MTPDmessageActionGeoProximityReached &data) {
-		return prepareProximityReached(data);
-	}, [](const MTPDmessageActionPaymentSentMe &) {
-		LOG(("API Error: messageActionPaymentSentMe received."));
-		return PreparedText{ { tr::lng_message_empty(tr::now) } };
-	}, [](const MTPDmessageActionSecureValuesSentMe &) {
-		LOG(("API Error: messageActionSecureValuesSentMe received."));
-		return PreparedText{ { tr::lng_message_empty(tr::now) } };
-	}, [&](const MTPDmessageActionGroupCall &data) {
-		return prepareGroupCall(data);
-	}, [&](const MTPDmessageActionInviteToGroupCall &data) {
-		return prepareInviteToGroupCall(data);
-	}, [&](const MTPDmessageActionSetMessagesTTL &data) {
-		return prepareSetMessagesTTL(data);
-	}, [&](const MTPDmessageActionGroupCallScheduled &data) {
-		return prepareCallScheduledText(data.vschedule_date().v);
-	}, [&](const MTPDmessageActionSetChatTheme &data) {
-		return prepareSetChatTheme(data);
-	}, [&](const MTPDmessageActionChatJoinedByRequest &data) {
-		return prepareChatJoinedByRequest(data);
-	}, [&](const MTPDmessageActionWebViewDataSent &data) {
-		return prepareWebViewDataSent(data);
-	}, [&](const MTPDmessageActionGiftPremium &data) {
-		return prepareGiftPremium(data);
-	}, [&](const MTPDmessageActionTopicCreate &data) {
-		return prepareTopicCreate(data);
-	}, [&](const MTPDmessageActionTopicEdit &data) {
-		return prepareTopicEdit(data);
-	}, [&](const MTPDmessageActionWebViewDataSentMe &data) {
-		LOG(("API Error: messageActionWebViewDataSentMe received."));
-		return PreparedText{ { tr::lng_message_empty(tr::now) } };
-	}, [](const MTPDmessageActionEmpty &) {
-		return PreparedText{ { tr::lng_message_empty(tr::now) } };
-	}));
-
-	// Additional information.
-	applyAction(action);
-}
-
-void HistoryService::applyAction(const MTPMessageAction &action) {
-	action.match([&](const MTPDmessageActionChatAddUser &data) {
-		if (const auto channel = history()->peer->asMegagroup()) {
-			const auto selfUserId = history()->session().userId();
-			for (const auto &item : data.vusers().v) {
-				if (peerFromUser(item) == selfUserId) {
-					channel->mgInfo->joinedMessageFound = true;
-					break;
-				}
-			}
-		}
-	}, [&](const MTPDmessageActionChatJoinedByLink &data) {
-		if (_from->isSelf()) {
-			if (const auto channel = history()->peer->asMegagroup()) {
-				channel->mgInfo->joinedMessageFound = true;
-			}
-		}
-	}, [&](const MTPDmessageActionChatEditPhoto &data) {
-		data.vphoto().match([&](const MTPDphoto &photo) {
-			_media = std::make_unique<Data::MediaPhoto>(
-				this,
-				history()->peer,
-				history()->owner().processPhoto(photo));
-		}, [](const MTPDphotoEmpty &) {
-		});
-	}, [&](const MTPDmessageActionChatCreate &) {
-		_flags |= MessageFlag::IsGroupEssential;
-	}, [&](const MTPDmessageActionChannelCreate &) {
-		_flags |= MessageFlag::IsGroupEssential;
-	}, [&](const MTPDmessageActionChatMigrateTo &) {
-		_flags |= MessageFlag::IsGroupEssential;
-	}, [&](const MTPDmessageActionChannelMigrateFrom &) {
-		_flags |= MessageFlag::IsGroupEssential;
-	}, [&](const MTPDmessageActionContactSignUp &) {
-		_flags |= MessageFlag::IsContactSignUp;
-	}, [&](const MTPDmessageActionChatJoinedByRequest &data) {
-		if (_from->isSelf()) {
-			if (const auto channel = history()->peer->asMegagroup()) {
-				channel->mgInfo->joinedMessageFound = true;
-			}
-		}
-	}, [&](const MTPDmessageActionGiftPremium &data) {
-		_media = std::make_unique<Data::MediaGiftBox>(
-			this,
-			_from,
-			data.vmonths().v);
-	}, [](const auto &) {
-	});
-}
-
-void HistoryService::setSelfDestruct(HistoryServiceSelfDestruct::Type type, int ttlSeconds) {
-	UpdateComponents(HistoryServiceSelfDestruct::Bit());
-	auto selfdestruct = Get<HistoryServiceSelfDestruct>();
-	selfdestruct->timeToLive = ttlSeconds * 1000LL;
-	selfdestruct->type = type;
-}
-
-bool HistoryService::updateDependent(bool force) {
-	auto dependent = GetDependentData();
-	Assert(dependent != nullptr);
-
-	if (!force) {
-		if (!dependent->msgId || dependent->msg) {
-			return true;
-		}
-	}
-
-	if (!dependent->lnk) {
-		dependent->lnk = goToMessageClickHandler(
-			(dependent->peerId
-				? history()->owner().peer(dependent->peerId)
-				: history()->peer),
-			dependent->msgId,
-			fullId());
-	}
-	auto gotDependencyItem = false;
-	if (!dependent->msg) {
-		dependent->msg = history()->owner().message(
-			(dependent->peerId
-				? dependent->peerId
-				: _history->peer->id),
-			dependent->msgId);
-		if (dependent->msg) {
-			if (dependent->msg->isEmpty()) {
-				// Really it is deleted.
-				dependent->msg = nullptr;
-				force = true;
-			} else {
-				history()->owner().registerDependentMessage(
-					this,
-					dependent->msg);
-				gotDependencyItem = true;
-			}
-		}
-	}
-	if (dependent->msg) {
-		updateDependentText();
-	} else if (force) {
-		if (dependent->msgId > 0) {
-			dependent->msgId = 0;
-			gotDependencyItem = true;
-		}
-		updateDependentText();
-	}
-	if (force && gotDependencyItem) {
-		Core::App().notifications().checkDelayed();
-	}
-	return (dependent->msg || !dependent->msgId);
-}
-
-HistoryService::PreparedText HistoryService::prepareInvitedToCallText(
-		const QVector<MTPlong> &users,
-		CallId linkCallId) {
-	const auto owner = &history()->owner();
-	auto chatText = tr::lng_action_invite_user_chat(
-		tr::now,
-		Ui::Text::WithEntities);
-	auto result = PreparedText{};
-	result.links.push_back(fromLink());
-	auto linkIndex = 1;
-	if (linkCallId) {
-		const auto peer = history()->peer;
-		result.links.push_back(GroupCallClickHandler(peer, linkCallId));
-		chatText = Ui::Text::Link(chatText.text, ++linkIndex);
-	}
-	if (users.size() == 1) {
-		auto user = owner->user(users[0].v);
-		result.links.push_back(user->createOpenLink());
-		result.text = tr::lng_action_invite_user(
-			tr::now,
-			lt_from,
-			fromLinkText(), // Link 1.
-			lt_user,
-			Ui::Text::Link(user->name(), ++linkIndex), // Link N.
-			lt_chat,
-			chatText,
-			Ui::Text::WithEntities);
-	} else if (users.isEmpty()) {
-		result.text = tr::lng_action_invite_user(
-			tr::now,
-			lt_from,
-			fromLinkText(), // Link 1.
-			lt_user,
-			{ .text = u"somebody"_q },
-			lt_chat,
-			chatText,
-			Ui::Text::WithEntities);
-	} else {
-		for (auto i = 0, l = int(users.size()); i != l; ++i) {
-			auto user = owner->user(users[i].v);
-			result.links.push_back(user->createOpenLink());
-
-			auto linkText = Ui::Text::Link(user->name(), ++linkIndex);
-			if (i == 0) {
-				result.text = linkText;
-			} else if (i + 1 == l) {
-				result.text = tr::lng_action_invite_users_and_last(
-					tr::now,
-					lt_accumulated,
-					result.text,
-					lt_user,
-					linkText,
-					Ui::Text::WithEntities);
-			} else {
-				result.text = tr::lng_action_invite_users_and_one(
-					tr::now,
-					lt_accumulated,
-					result.text,
-					lt_user,
-					linkText,
-					Ui::Text::WithEntities);
-			}
-		}
-		result.text = tr::lng_action_invite_users_many(
-			tr::now,
-			lt_from,
-			fromLinkText(), // Link 1.
-			lt_users,
-			result.text,
-			lt_chat,
-			chatText,
-			Ui::Text::WithEntities);
-	}
-	return result;
-}
-
-HistoryService::PreparedText HistoryService::preparePinnedText() {
-	auto result = PreparedText {};
-	auto pinned = Get<HistoryServicePinned>();
-	if (pinned && pinned->msg) {
-		const auto mediaText = [&] {
-			using TTL = HistoryServiceSelfDestruct;
-			if (const auto media = pinned->msg->media()) {
-				return media->pinnedTextSubstring();
-			} else if (const auto selfdestruct = pinned->msg->Get<TTL>()) {
-				if (selfdestruct->type == TTL::Type::Photo) {
-					return tr::lng_action_pinned_media_photo(tr::now);
-				} else if (selfdestruct->type == TTL::Type::Video) {
-					return tr::lng_action_pinned_media_video(tr::now);
-				}
-			}
-			return QString();
-		}();
-		result.links.push_back(fromLink());
-		result.links.push_back(pinned->lnk);
-		if (mediaText.isEmpty()) {
-			auto original = pinned->msg->originalText();
-			auto cutAt = 0;
-			auto limit = kPinnedMessageTextLimit;
-			auto size = original.text.size();
-			for (; limit != 0;) {
-				--limit;
-				if (cutAt >= size) break;
-				if (original.text.at(cutAt).isLowSurrogate()
-					&& (cutAt + 1 < size)
-					&& original.text.at(cutAt + 1).isHighSurrogate()) {
-					cutAt += 2;
-				} else {
-					++cutAt;
-				}
-			}
-			if (!limit && cutAt + 5 < size) {
-				original = Ui::Text::Mid(original, 0, cutAt).append(
-					Ui::kQEllipsis);
-			}
-			original = Ui::Text::Link(
-				Ui::Text::Filtered(
-					std::move(original),
-					{
-						EntityType::Spoiler,
-						EntityType::StrikeOut,
-						EntityType::Italic,
-						EntityType::CustomEmoji,
-					}),
-				2);
-			result.text = tr::lng_action_pinned_message(
-				tr::now,
-				lt_from,
-				fromLinkText(), // Link 1.
-				lt_text,
-				std::move(original), // Link 2.
-				Ui::Text::WithEntities);
-		} else {
-			result.text = tr::lng_action_pinned_media(
-				tr::now,
-				lt_from,
-				fromLinkText(), // Link 1.
-				lt_media,
-				Ui::Text::Link(mediaText, 2), // Link 2.
-				Ui::Text::WithEntities);
-		}
-	} else if (pinned && pinned->msgId) {
-		result.links.push_back(fromLink());
-		result.links.push_back(pinned->lnk);
-		result.text = tr::lng_action_pinned_media(
-			tr::now,
-			lt_from,
-			fromLinkText(), // Link 1.
-			lt_media,
-			Ui::Text::Link(tr::lng_contacts_loading(tr::now), 2), // Link 2.
-			Ui::Text::WithEntities);
-	} else {
-		result.links.push_back(fromLink());
-		result.text = tr::lng_action_pinned_media(
-			tr::now,
-			lt_from,
-			fromLinkText(), // Link 1.
-			lt_media,
-			{ .text = tr::lng_deleted_message(tr::now) },
-			Ui::Text::WithEntities);
-	}
-	return result;
-}
-
-HistoryService::PreparedText HistoryService::prepareGameScoreText() {
-	auto result = PreparedText {};
-	auto gamescore = Get<HistoryServiceGameScore>();
-
-	auto computeGameTitle = [&]() -> TextWithEntities {
-		if (gamescore && gamescore->msg) {
-			if (const auto media = gamescore->msg->media()) {
-				if (const auto game = media->game()) {
-					const auto row = 0;
-					const auto column = 0;
-					result.links.push_back(
-						std::make_shared<ReplyMarkupClickHandler>(
-							&history()->owner(),
-							row,
-							column,
-							gamescore->msg->fullId()));
-					auto titleText = game->title;
-					return Ui::Text::Link(titleText, QString());
-				}
-			}
-			return tr::lng_deleted_message(tr::now, Ui::Text::WithEntities);
-		} else if (gamescore && gamescore->msgId) {
-			return tr::lng_contacts_loading(tr::now, Ui::Text::WithEntities);
-		}
-		return {};
-	};
-
-	const auto scoreNumber = gamescore ? gamescore->score : 0;
-	if (_from->isSelf()) {
-		auto gameTitle = computeGameTitle();
-		if (gameTitle.text.isEmpty()) {
-			result.text = tr::lng_action_game_you_scored_no_game(
-				tr::now,
-				lt_count,
-				scoreNumber,
-				Ui::Text::WithEntities);
-		} else {
-			result.text = tr::lng_action_game_you_scored(
-				tr::now,
-				lt_count,
-				scoreNumber,
-				lt_game,
-				gameTitle,
-				Ui::Text::WithEntities);
-		}
-	} else {
-		result.links.push_back(fromLink());
-		auto gameTitle = computeGameTitle();
-		if (gameTitle.text.isEmpty()) {
-			result.text = tr::lng_action_game_score_no_game(
-				tr::now,
-				lt_count,
-				scoreNumber,
-				lt_from,
-				fromLinkText(), // Link 1.
-				Ui::Text::WithEntities);
-		} else {
-			result.text = tr::lng_action_game_score(
-				tr::now,
-				lt_count,
-				scoreNumber,
-				lt_from,
-				fromLinkText(), // Link 1.
-				lt_game,
-				gameTitle,
-				Ui::Text::WithEntities);
-		}
-	}
-	return result;
-}
-
-HistoryService::PreparedText HistoryService::preparePaymentSentText() {
-	auto result = PreparedText {};
-	const auto payment = Get<HistoryServicePayment>();
-	Assert(payment != nullptr);
-
-	auto invoiceTitle = [&] {
-		if (payment->msg) {
-			if (const auto media = payment->msg->media()) {
-				if (const auto invoice = media->invoice()) {
-					return Ui::Text::Link(invoice->title, QString());
-				}
-			}
-		}
-		return TextWithEntities();
-	}();
-
-	if (invoiceTitle.text.isEmpty()) {
-		if (payment->recurringUsed) {
-			result.text = tr::lng_action_payment_used_recurring(
-				tr::now,
-				lt_amount,
-				{ .text = payment->amount },
-				Ui::Text::WithEntities);
-		} else {
-			result.text = (payment->recurringInit
-				? tr::lng_action_payment_init_recurring
-				: tr::lng_action_payment_done)(
-					tr::now,
-					lt_amount,
-					{ .text = payment->amount },
-					lt_user,
-					{ .text = history()->peer->name() },
-					Ui::Text::WithEntities);
-		}
-	} else {
-		result.text = (payment->recurringInit
-			? tr::lng_action_payment_init_recurring_for
-			: tr::lng_action_payment_done_for)(
-				tr::now,
-				lt_amount,
-				{ .text = payment->amount },
-				lt_user,
-				{ .text = history()->peer->name() },
-				lt_invoice,
-				invoiceTitle,
-				Ui::Text::WithEntities);
-		if (payment->msg) {
-			result.links.push_back(payment->lnk);
-		}
-	}
-	return result;
-}
-
-HistoryService::PreparedText HistoryService::prepareCallScheduledText(
-		TimeId scheduleDate) {
-	const auto call = Get<HistoryServiceOngoingCall>();
-	Assert(call != nullptr);
-
-	const auto scheduled = base::unixtime::parse(scheduleDate);
-	const auto date = scheduled.date();
-	const auto now = QDateTime::currentDateTime();
-	const auto secsToDateAddDays = [&](int days) {
-		return now.secsTo(QDateTime(date.addDays(days), QTime(0, 0)));
-	};
-	auto result = PreparedText();
-	const auto prepareWithDate = [&](const QString &date) {
-		if (history()->peer->isBroadcast()) {
-			result.text = tr::lng_action_group_call_scheduled_channel(
-				tr::now,
-				lt_date,
-				{ .text = date },
-				Ui::Text::WithEntities);
-		} else {
-			result.links.push_back(fromLink());
-			result.text = tr::lng_action_group_call_scheduled_group(
-				tr::now,
-				lt_from,
-				fromLinkText(), // Link 1.
-				lt_date,
-				{ .text = date },
-				Ui::Text::WithEntities);
-		}
-	};
-	const auto time = QLocale().toString(scheduled.time(), cTimeFormat());
-	const auto prepareGeneric = [&] {
-		prepareWithDate(tr::lng_group_call_starts_date(
-			tr::now,
-			lt_date,
-			langDayOfMonthFull(date),
-			lt_time,
-			time));
-	};
-	auto nextIn = TimeId(0);
-	if (now.date().addDays(1) < scheduled.date()) {
-		nextIn = secsToDateAddDays(-1);
-		prepareGeneric();
-	} else if (now.date().addDays(1) == scheduled.date()) {
-		nextIn = secsToDateAddDays(0);
-		prepareWithDate(
-			tr::lng_group_call_starts_tomorrow(tr::now, lt_time, time));
-	} else if (now.date() == scheduled.date()) {
-		nextIn = secsToDateAddDays(1);
-		prepareWithDate(
-			tr::lng_group_call_starts_today(tr::now, lt_time, time));
-	} else {
-		prepareGeneric();
-	}
-	if (nextIn) {
-		call->lifetime = base::timer_once(
-			(nextIn + 2) * crl::time(1000)
-		) | rpl::start_with_next([=] {
-			updateText(prepareCallScheduledText(scheduleDate));
-		});
-	}
-	return result;
-}
-
-HistoryService::HistoryService(
-	not_null<History*> history,
-	MsgId id,
-	const MTPDmessage &data,
-	MessageFlags localFlags)
-: HistoryItem(
-		history,
-		id,
-		FlagsFromMTP(id, data.vflags().v, localFlags),
-		data.vdate().v,
-		data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) {
-	createFromMtp(data);
-	applyTTL(data);
-}
-
-HistoryService::HistoryService(
-	not_null<History*> history,
-	MsgId id,
-	const MTPDmessageService &data,
-	MessageFlags localFlags)
-: HistoryItem(
-		history,
-		id,
-		FlagsFromMTP(id, data.vflags().v, localFlags),
-		data.vdate().v,
-		data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) {
-	createFromMtp(data);
-	applyTTL(data);
-}
-
-HistoryService::HistoryService(
-	not_null<History*> history,
-	MsgId id,
-	MessageFlags flags,
-	TimeId date,
-	PreparedText &&message,
-	PeerId from,
-	PhotoData *photo)
-: HistoryItem(history, id, flags, date, from) {
-	setServiceText(std::move(message));
-	if (photo) {
-		_media = std::make_unique<Data::MediaPhoto>(
-			this,
-			history->peer,
-			photo);
-	}
-}
-
-bool HistoryService::updateDependencyItem() {
-	if (GetDependentData()) {
-		return updateDependent(true);
-	}
-	return HistoryItem::updateDependencyItem();
-}
-
-bool HistoryService::needCheck() const {
-	return out() && !isEmpty();
-}
-
-ItemPreview HistoryService::toPreview(ToPreviewOptions options) const {
-	// Don't show for service messages (chat photo changed).
-	// Because larger version is shown exactly to the left of the preview.
-	//auto media = _media ? _media->toPreview(options) : ItemPreview();
-	return {
-		.text = Ui::Text::Wrapped(notificationText(), EntityType::PlainLink),
-		//.images = std::move(media.images),
-		//.loadingContext = std::move(media.loadingContext),
-	};
-}
-
-TextWithEntities HistoryService::inReplyText() const {
-	auto result = HistoryService::notificationText();
-	const auto &name = author()->name();
-	TextUtilities::Trim(result);
-	if (result.text.startsWith(name)) {
-		result = Ui::Text::Mid(result, name.size());
-		TextUtilities::Trim(result);
-	}
-	return Ui::Text::Wrapped(result, EntityType::PlainLink);
-}
-
-MsgId HistoryService::replyToId() const {
-	return 0; // Don't render replies info in service, only handle threads.
-}
-
-MsgId HistoryService::replyToTop() const {
-	if (const auto data = GetDependentData()) {
-		return data->topId;
-	}
-	return 0;
-}
-
-MsgId HistoryService::topicRootId() const {
-	if (const auto data = GetDependentData()
-		; data && data->topicPost && data->topId) {
-		return data->topId;
-	} else if (const auto info = Get<HistoryServiceTopicInfo>()) {
-		if (info->created()) {
-			return id;
-		}
-	}
-	return Data::ForumTopic::kGeneralId;
-}
-
-void HistoryService::setReplyFields(
-		MsgId replyTo,
-		MsgId replyToTop,
-		bool isForumPost) {
-	const auto data = GetDependentData();
-	if (!data
-		|| (data->topId == replyToTop)
-		|| IsServerMsgId(data->topId)
-		|| isScheduled()) {
-		return;
-	}
-	data->topId = replyToTop;
-	if (isForumPost) {
-		data->topicPost = true;
-	}
-	if (const auto topic = this->topic()) {
-		topic->maybeSetLastMessage(this);
-	}
-}
-
-std::unique_ptr<HistoryView::Element> HistoryService::createView(
-		not_null<HistoryView::ElementDelegate*> delegate,
-		HistoryView::Element *replacing) {
-	return delegate->elementCreate(this, replacing);
-}
-
-TextWithEntities HistoryService::fromLinkText() const {
-	return Ui::Text::Link(_from->name(), 1);
-}
-
-ClickHandlerPtr HistoryService::fromLink() const {
-	return _from->createOpenLink();
-}
-
-void HistoryService::setServiceText(PreparedText &&prepared) {
-	const auto had = !_text.empty();
-	_text = std::move(prepared.text);
-	_textLinks = std::move(prepared.links);
-	if (had) {
-		history()->owner().requestItemTextRefresh(this);
-	}
-}
-
-void HistoryService::markMediaAsReadHook() {
-	if (const auto selfdestruct = Get<HistoryServiceSelfDestruct>()) {
-		if (!selfdestruct->destructAt) {
-			selfdestruct->destructAt = crl::now() + selfdestruct->timeToLive;
-			history()->owner().selfDestructIn(this, selfdestruct->timeToLive);
-		}
-	}
-}
-
-crl::time HistoryService::getSelfDestructIn(crl::time now) {
-	if (auto selfdestruct = Get<HistoryServiceSelfDestruct>()) {
-		if (selfdestruct->destructAt > 0) {
-			if (selfdestruct->destructAt <= now) {
-				auto text = [selfdestruct] {
-					switch (selfdestruct->type) {
-					case HistoryServiceSelfDestruct::Type::Photo: return tr::lng_ttl_photo_expired(tr::now);
-					case HistoryServiceSelfDestruct::Type::Video: return tr::lng_ttl_video_expired(tr::now);
-					}
-					Unexpected("Type in HistoryServiceSelfDestruct::Type");
-				};
-				setServiceText({ TextWithEntities{ .text = text() } });
-				return 0;
-			}
-			return selfdestruct->destructAt - now;
-		}
-	}
-	return 0;
-}
-
-void HistoryService::createFromMtp(const MTPDmessage &message) {
-	const auto media = message.vmedia();
-	Assert(media != nullptr);
-
-	const auto mediaType = media->type();
-	switch (mediaType) {
-	case mtpc_messageMediaPhoto: {
-		if (message.is_media_unread()) {
-			const auto &photo = media->c_messageMediaPhoto();
-			const auto ttl = photo.vttl_seconds();
-			Assert(ttl != nullptr);
-
-			setSelfDestruct(HistoryServiceSelfDestruct::Type::Photo, ttl->v);
-			if (out()) {
-				setServiceText({
-					tr::lng_ttl_photo_sent(tr::now, Ui::Text::WithEntities)
-				});
-			} else {
-				auto result = PreparedText();
-				result.links.push_back(fromLink());
-				result.text = tr::lng_ttl_photo_received(
-					tr::now,
-					lt_from,
-					fromLinkText(), // Link 1.
-					Ui::Text::WithEntities);
-				setServiceText(std::move(result));
-			}
-		} else {
-			setServiceText({
-				tr::lng_ttl_photo_expired(tr::now, Ui::Text::WithEntities)
-			});
-		}
-	} break;
-	case mtpc_messageMediaDocument: {
-		if (message.is_media_unread()) {
-			const auto &document = media->c_messageMediaDocument();
-			const auto ttl = document.vttl_seconds();
-			Assert(ttl != nullptr);
-
-			setSelfDestruct(HistoryServiceSelfDestruct::Type::Video, ttl->v);
-			if (out()) {
-				setServiceText({
-					tr::lng_ttl_video_sent(tr::now, Ui::Text::WithEntities)
-				});
-			} else {
-				auto result = PreparedText();
-				result.links.push_back(fromLink());
-				result.text = tr::lng_ttl_video_received(
-					tr::now,
-					lt_from,
-					fromLinkText(), // Link 1.
-					Ui::Text::WithEntities);
-				setServiceText(std::move(result));
-			}
-		} else {
-			setServiceText({
-				tr::lng_ttl_video_expired(tr::now, Ui::Text::WithEntities)
-			});
-		}
-	} break;
-
-	default: Unexpected("Media type in HistoryService::createFromMtp()");
-	}
-
-	if (const auto reactions = message.vreactions()) {
-		updateReactions(reactions);
-	}
-}
-
-void HistoryService::createFromMtp(const MTPDmessageService &message) {
-	const auto &action = message.vaction();
-	const auto type = action.type();
-	if (type == mtpc_messageActionPinMessage) {
-		UpdateComponents(HistoryServicePinned::Bit());
-	} else if (type == mtpc_messageActionTopicCreate
-		|| type == mtpc_messageActionTopicEdit) {
-		UpdateComponents(HistoryServiceTopicInfo::Bit());
-		const auto info = Get<HistoryServiceTopicInfo>();
-		info->topicPost = true;
-		if (type == mtpc_messageActionTopicEdit) {
-			const auto &data = action.c_messageActionTopicEdit();
-			if (const auto title = data.vtitle()) {
-				info->title = qs(*title);
-				info->renamed = true;
-			}
-			if (const auto icon = data.vicon_emoji_id()) {
-				info->iconId = icon->v;
-				info->reiconed = true;
-			}
-			if (const auto closed = data.vclosed()) {
-				info->closed = mtpIsTrue(*closed);
-				info->reopened = !info->closed;
-			}
-			if (const auto hidden = data.vhidden()) {
-				info->hidden = mtpIsTrue(*hidden);
-				info->unhidden = !info->hidden;
-			}
-		} else {
-			const auto &data = action.c_messageActionTopicCreate();
-			info->title = qs(data.vtitle());
-			info->iconId = data.vicon_emoji_id().value_or_empty();
-		}
-	} else if (type == mtpc_messageActionSetChatTheme) {
-		setupChatThemeChange();
-	} else if (type == mtpc_messageActionSetMessagesTTL) {
-		setupTTLChange();
-	} else if (type == mtpc_messageActionGameScore) {
-		const auto &data = action.c_messageActionGameScore();
-		UpdateComponents(HistoryServiceGameScore::Bit());
-		Get<HistoryServiceGameScore>()->score = data.vscore().v;
-	} else if (type == mtpc_messageActionPaymentSent) {
-		const auto &data = action.c_messageActionPaymentSent();
-		UpdateComponents(HistoryServicePayment::Bit());
-		const auto amount = data.vtotal_amount().v;
-		const auto currency = qs(data.vcurrency());
-		const auto payment = Get<HistoryServicePayment>();
-		const auto id = fullId();
-		const auto owner = &history()->owner();
-		payment->slug = data.vinvoice_slug().value_or_empty();
-		payment->recurringInit = data.is_recurring_init();
-		payment->recurringUsed = data.is_recurring_used();
-		payment->amount = Ui::FillAmountAndCurrency(amount, currency);
-		payment->invoiceLink = std::make_shared<LambdaClickHandler>([=](
-				ClickContext context) {
-			using namespace Payments;
-			const auto my = context.other.value<ClickHandlerContext>();
-			const auto weak = my.sessionWindow;
-			if (const auto item = owner->message(id)) {
-				CheckoutProcess::Start(
-					item,
-					Mode::Receipt,
-					crl::guard(weak, [=](auto) { weak->window().activate(); }));
-			}
-		});
-	} else if (type == mtpc_messageActionGroupCall
-		|| type == mtpc_messageActionGroupCallScheduled) {
-		const auto started = (type == mtpc_messageActionGroupCall);
-		const auto &callData = started
-			? action.c_messageActionGroupCall().vcall()
-			: action.c_messageActionGroupCallScheduled().vcall();
-		const auto duration = started
-			? action.c_messageActionGroupCall().vduration()
-			: tl::conditional<MTPint>();
-		if (duration) {
-			RemoveComponents(HistoryServiceOngoingCall::Bit());
-		} else {
-			UpdateComponents(HistoryServiceOngoingCall::Bit());
-			const auto call = Get<HistoryServiceOngoingCall>();
-			call->id = CallIdFromInput(callData);
-			call->link = GroupCallClickHandler(history()->peer, call->id);
-		}
-	} else if (type == mtpc_messageActionInviteToGroupCall) {
-		const auto &data = action.c_messageActionInviteToGroupCall();
-		const auto id = CallIdFromInput(data.vcall());
-		const auto peer = history()->peer;
-		const auto has = PeerHasThisCall(peer, id);
-		auto hasLink = !has.has_value()
-			? PeerHasThisCallValue(peer, id)
-			: (*has)
-			? PeerHasThisCallValue(
-				peer,
-				id) | rpl::skip(1) | rpl::type_erased()
-			: rpl::producer<bool>();
-		if (!hasLink) {
-			RemoveComponents(HistoryServiceOngoingCall::Bit());
-		} else {
-			UpdateComponents(HistoryServiceOngoingCall::Bit());
-			const auto call = Get<HistoryServiceOngoingCall>();
-			call->id = id;
-			call->lifetime.destroy();
-
-			const auto users = data.vusers().v;
-			std::move(hasLink) | rpl::start_with_next([=](bool has) {
-				updateText(prepareInvitedToCallText(users, has ? id : 0));
-				if (!has) {
-					RemoveComponents(HistoryServiceOngoingCall::Bit());
-				}
-			}, call->lifetime);
-		}
-	}
-	if (const auto replyTo = message.vreply_to()) {
-		replyTo->match([&](const MTPDmessageReplyHeader &data) {
-			const auto peerId = data.vreply_to_peer_id()
-				? peerFromMTP(*data.vreply_to_peer_id())
-				: history()->peer->id;
-			if (const auto dependent = GetDependentData()) {
-				dependent->peerId = (peerId != history()->peer->id)
-					? peerId
-					: 0;
-				const auto id = data.vreply_to_msg_id().v;
-				dependent->msgId = id;
-				dependent->topId = data.vreply_to_top_id().value_or(id);
-				dependent->topicPost = data.is_forum_topic()
-					|| Has<HistoryServiceTopicInfo>();
-				if (!updateDependent()) {
-					RequestDependentMessageData(
-						this,
-						dependent->peerId,
-						dependent->msgId);
-				}
-			}
-		});
-	}
-	setMessageByAction(action);
-}
-
-const std::vector<ClickHandlerPtr> &HistoryService::customTextLinks() const {
-	return _textLinks;
-}
-
-void HistoryService::applyEdition(const MTPDmessageService &message) {
-	clearDependency();
-	UpdateComponents(0);
-
-	createFromMtp(message);
-	applyServiceDateEdition(message);
-
-	if (message.vaction().type() == mtpc_messageActionHistoryClear) {
-		removeMedia();
-		finishEditionToEmpty();
-	} else {
-		finishEdition(-1);
-	}
-}
-
-void HistoryService::removeMedia() {
-	if (!_media) return;
-
-	_media.reset();
-	history()->owner().requestItemResize(this);
-}
-
-Storage::SharedMediaTypesMask HistoryService::sharedMediaTypes() const {
-	if (auto media = this->media()) {
-		return media->sharedMediaTypes();
-	}
-	return {};
-}
-
-void HistoryService::updateDependentText() {
-	auto text = PreparedText{};
-	if (Has<HistoryServicePinned>()) {
-		text = preparePinnedText();
-	} else if (Has<HistoryServiceGameScore>()) {
-		text = prepareGameScoreText();
-	} else if (Has<HistoryServicePayment>()) {
-		text = preparePaymentSentText();
-	} else {
-		return;
-	}
-	updateText(std::move(text));
-}
-
-void HistoryService::updateText(PreparedText &&text) {
-	setServiceText(std::move(text));
-	history()->owner().requestItemResize(this);
-	invalidateChatListEntry();
-	history()->owner().updateDependentMessages(this);
-}
-
-void HistoryService::clearDependency() {
-	if (const auto dependent = GetDependentData()) {
-		if (dependent->msg) {
-			history()->owner().unregisterDependentMessage(
-				this,
-				dependent->msg);
-			dependent->msg = nullptr;
-			dependent->msgId = 0;
-		}
-	}
-}
-
-void HistoryService::setupChatThemeChange() {
-	if (const auto user = history()->peer->asUser()) {
-		auto link = std::make_shared<LambdaClickHandler>([=](
-				ClickContext context) {
-			const auto my = context.other.value<ClickHandlerContext>();
-			if (const auto controller = my.sessionWindow.get()) {
-				controller->toggleChooseChatTheme(user);
-			}
-		});
-
-		UpdateComponents(HistoryServiceChatThemeChange::Bit());
-		Get<HistoryServiceChatThemeChange>()->link = std::move(link);
-	} else {
-		RemoveComponents(HistoryServiceChatThemeChange::Bit());
-	}
-}
-
-void HistoryService::setupTTLChange() {
-	const auto peer = history()->peer;
-	auto link = std::make_shared<LambdaClickHandler>([=](
-			ClickContext context) {
-		const auto my = context.other.value<ClickHandlerContext>();
-		if (const auto controller = my.sessionWindow.get()) {
-			const auto validator = TTLMenu::TTLValidator(
-				std::make_shared<Window::Show>(controller),
-				peer);
-			if (validator.can()) {
-				validator.showBox();
-			}
-		}
-	});
-
-	UpdateComponents(HistoryServiceTTLChange::Bit());
-	Get<HistoryServiceTTLChange>()->link = std::move(link);
-}
-
-void HistoryService::dependencyItemRemoved(HistoryItem *dependency) {
-	clearDependency();
-	updateDependentText();
-}
-
-HistoryService::~HistoryService() {
-	clearDependency();
-	_media.reset();
-}
-
-HistoryService::PreparedText GenerateJoinedText(
-		not_null<History*> history,
-		not_null<UserData*> inviter,
-		bool viaRequest) {
-	if (inviter->id != history->session().userPeerId()) {
-		auto result = HistoryService::PreparedText{};
-		result.links.push_back(inviter->createOpenLink());
-		result.text = (history->peer->isMegagroup()
-			? tr::lng_action_add_you_group
-			: tr::lng_action_add_you)(
-				tr::now,
-				lt_from,
-				Ui::Text::Link(inviter->name(), QString()),
-				Ui::Text::WithEntities);
-		return result;
-	} else if (history->peer->isMegagroup()) {
-		if (viaRequest) {
-			return { tr::lng_action_you_joined_by_request(
-				tr::now,
-				Ui::Text::WithEntities) };
-		}
-		auto self = history->session().user();
-		auto result = HistoryService::PreparedText{};
-		result.links.push_back(self->createOpenLink());
-		result.text = tr::lng_action_user_joined(
-			tr::now,
-			lt_from,
-			Ui::Text::Link(self->name(), QString()),
-			Ui::Text::WithEntities);
-		return result;
-	}
-	return { viaRequest
-		? tr::lng_action_you_joined_by_request_channel(
-			tr::now,
-			Ui::Text::WithEntities)
-		: tr::lng_action_you_joined(tr::now, Ui::Text::WithEntities) };
-}
-
-not_null<HistoryService*> GenerateJoinedMessage(
-		not_null<History*> history,
-		TimeId inviteDate,
-		not_null<UserData*> inviter,
-		bool viaRequest) {
-	return history->makeServiceMessage(
-		history->owner().nextLocalMessageId(),
-		MessageFlag::Local,
-		inviteDate,
-		GenerateJoinedText(history, inviter, viaRequest));
-}
-
-std::optional<bool> PeerHasThisCall(
-		not_null<PeerData*> peer,
-		CallId id) {
-	const auto call = peer->groupCall();
-	return call
-		? std::make_optional(call->id() == id)
-		: PeerCallKnown(peer)
-		? std::make_optional(false)
-		: std::nullopt;
-}
diff --git a/Telegram/SourceFiles/history/history_service.h b/Telegram/SourceFiles/history/history_service.h
deleted file mode 100644
index 506f3677d..000000000
--- a/Telegram/SourceFiles/history/history_service.h
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
-This file is part of Telegram Desktop,
-the official desktop application for the Telegram messaging service.
-
-For license and copyright information please follow this link:
-https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
-*/
-#pragma once
-
-#include "history/history_item.h"
-
-namespace HistoryView {
-class Service;
-} // namespace HistoryView
-
-struct HistoryServiceDependentData {
-	PeerId peerId = 0;
-	HistoryItem *msg = nullptr;
-	ClickHandlerPtr lnk;
-	MsgId msgId = 0;
-	MsgId topId = 0;
-	bool topicPost = false;
-};
-
-struct HistoryServicePinned
-: public RuntimeComponent<HistoryServicePinned, HistoryItem>
-, public HistoryServiceDependentData {
-};
-
-struct HistoryServiceTopicInfo
-: public RuntimeComponent<HistoryServiceTopicInfo, HistoryItem>
-, public HistoryServiceDependentData {
-	QString title;
-	DocumentId iconId = 0;
-	bool closed = false;
-	bool reopened = false;
-	bool reiconed = false;
-	bool renamed = false;
-	bool hidden = false;
-	bool unhidden = false;
-
-	[[nodiscard]] bool created() const {
-		return !closed
-			&& !reopened
-			&& !reiconed
-			&& !renamed
-			&& !hidden
-			&& !unhidden;
-	}
-};
-
-struct HistoryServiceGameScore
-: public RuntimeComponent<HistoryServiceGameScore, HistoryItem>
-, public HistoryServiceDependentData {
-	int score = 0;
-};
-
-struct HistoryServicePayment
-: public RuntimeComponent<HistoryServicePayment, HistoryItem>
-, public HistoryServiceDependentData {
-	QString slug;
-	QString amount;
-	ClickHandlerPtr invoiceLink;
-	bool recurringInit = false;
-	bool recurringUsed = false;
-};
-
-struct HistoryServiceSelfDestruct
-: public RuntimeComponent<HistoryServiceSelfDestruct, HistoryItem> {
-	enum class Type {
-		Photo,
-		Video,
-	};
-	Type type = Type::Photo;
-	crl::time timeToLive = 0;
-	crl::time destructAt = 0;
-};
-
-struct HistoryServiceOngoingCall
-: public RuntimeComponent<HistoryServiceOngoingCall, HistoryItem> {
-	CallId id = 0;
-	ClickHandlerPtr link;
-	rpl::lifetime lifetime;
-};
-
-struct HistoryServiceChatThemeChange
-: public RuntimeComponent<HistoryServiceChatThemeChange, HistoryItem> {
-	ClickHandlerPtr link;
-};
-
-struct HistoryServiceTTLChange
-: public RuntimeComponent<HistoryServiceTTLChange, HistoryItem> {
-	ClickHandlerPtr link;
-};
-
-namespace HistoryView {
-class ServiceMessagePainter;
-} // namespace HistoryView
-
-class HistoryService : public HistoryItem {
-public:
-	struct PreparedText {
-		TextWithEntities text;
-		std::vector<ClickHandlerPtr> links;
-	};
-
-	HistoryService(
-		not_null<History*> history,
-		MsgId id,
-		const MTPDmessage &data,
-		MessageFlags localFlags);
-	HistoryService(
-		not_null<History*> history,
-		MsgId id,
-		const MTPDmessageService &data,
-		MessageFlags localFlags);
-	HistoryService(
-		not_null<History*> history,
-		MsgId id,
-		MessageFlags flags,
-		TimeId date,
-		PreparedText &&message,
-		PeerId from = 0,
-		PhotoData *photo = nullptr);
-
-	bool updateDependencyItem() override;
-	MsgId dependencyMsgId() const override {
-		if (auto dependent = GetDependentData()) {
-			return dependent->msgId;
-		}
-		return 0;
-	}
-	bool notificationReady() const override {
-		if (auto dependent = GetDependentData()) {
-			return (dependent->msg || !dependent->msgId);
-		}
-		return true;
-	}
-
-	const std::vector<ClickHandlerPtr> &customTextLinks() const override;
-
-	void applyEdition(const MTPDmessageService &message) override;
-	crl::time getSelfDestructIn(crl::time now) override;
-
-	Storage::SharedMediaTypesMask sharedMediaTypes() const override;
-
-	void dependencyItemRemoved(HistoryItem *dependency) override;
-
-	bool needCheck() const override;
-	bool isService() const override {
-		return true;
-	}
-	ItemPreview toPreview(ToPreviewOptions options) const override;
-	TextWithEntities inReplyText() const override;
-
-	MsgId replyToId() const override;
-	MsgId replyToTop() const override;
-	MsgId topicRootId() const override;
-	void setReplyFields(
-		MsgId replyTo,
-		MsgId replyToTop,
-		bool isForumPost) override;
-
-	std::unique_ptr<HistoryView::Element> createView(
-		not_null<HistoryView::ElementDelegate*> delegate,
-		HistoryView::Element *replacing = nullptr) override;
-
-	void setServiceText(PreparedText &&prepared);
-
-	~HistoryService();
-
-protected:
-	friend class HistoryView::ServiceMessagePainter;
-
-	void markMediaAsReadHook() override;
-
-	TextWithEntities fromLinkText() const;
-	ClickHandlerPtr fromLink() const;
-
-	void removeMedia();
-
-private:
-	HistoryServiceDependentData *GetDependentData() {
-		if (const auto pinned = Get<HistoryServicePinned>()) {
-			return pinned;
-		} else if (const auto gamescore = Get<HistoryServiceGameScore>()) {
-			return gamescore;
-		} else if (const auto payment = Get<HistoryServicePayment>()) {
-			return payment;
-		} else if (const auto info = Get<HistoryServiceTopicInfo>()) {
-			return info;
-		}
-		return nullptr;
-	}
-	const HistoryServiceDependentData *GetDependentData() const {
-		return const_cast<HistoryService*>(this)->GetDependentData();
-	}
-	bool updateDependent(bool force = false);
-	void updateDependentText();
-	void updateText(PreparedText &&text);
-	void clearDependency();
-	void setupChatThemeChange();
-	void setupTTLChange();
-
-	void createFromMtp(const MTPDmessage &message);
-	void createFromMtp(const MTPDmessageService &message);
-	void setMessageByAction(const MTPmessageAction &action);
-	void setSelfDestruct(
-		HistoryServiceSelfDestruct::Type type,
-		int ttlSeconds);
-	void applyAction(const MTPMessageAction &action);
-
-	PreparedText preparePinnedText();
-	PreparedText prepareGameScoreText();
-	PreparedText preparePaymentSentText();
-	PreparedText prepareInvitedToCallText(
-		const QVector<MTPlong> &users,
-		CallId linkCallId);
-	PreparedText prepareCallScheduledText(
-		TimeId scheduleDate);
-
-	friend class HistoryView::Service;
-
-	std::vector<ClickHandlerPtr> _textLinks;
-
-};
-
-[[nodiscard]] not_null<HistoryService*> GenerateJoinedMessage(
-	not_null<History*> history,
-	TimeId inviteDate,
-	not_null<UserData*> inviter,
-	bool viaRequest);
-[[nodiscard]] std::optional<bool> PeerHasThisCall(
-	not_null<PeerData*> peer,
-	CallId id);
diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index d9a0ec902..a6f6c57c4 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -78,7 +78,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/stickers/data_custom_emoji.h"
 #include "history/history.h"
 #include "history/history_item.h"
-#include "history/history_message.h"
+#include "history/history_item_helpers.h" // GetErrorTextForSending.
 #include "history/history_drag_area.h"
 #include "history/history_inner_widget.h"
 #include "history/history_item_components.h"
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp
index 33dfd7799..3265403d4 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp
@@ -8,7 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/controls/history_view_forward_panel.h"
 
 #include "history/history.h"
-#include "history/history_message.h"
+#include "history/history_item.h"
+#include "history/history_item_helpers.h"
 #include "history/history_item_components.h"
 #include "history/view/history_view_item_preview.h"
 #include "data/data_session.h"
diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp
index ce88d565b..8f9ed8dda 100644
--- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp
@@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/painter.h"
 #include "lang/lang_keys.h"
 #include "history/history_item_components.h"
-#include "history/history_message.h"
+#include "history/history_item.h"
 #include "history/history.h"
 #include "history/view/history_view_message.h"
 #include "history/view/history_view_cursor_state.h"
@@ -629,7 +629,7 @@ void BottomInfo::continueReactionAnimations(base::flat_map<
 
 BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
 	using Flag = BottomInfo::Data::Flag;
-	const auto item = message->message();
+	const auto item = message->data();
 
 	auto result = BottomInfo::Data();
 	result.date = message->dateTime();
diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
index 38ec61142..becd4f422 100644
--- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
@@ -19,7 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/history_view_cursor_state.h"
 #include "history/history.h"
 #include "history/history_item.h"
-#include "history/history_message.h"
 #include "history/history_item_text.h"
 #include "history/view/history_view_schedule_box.h"
 #include "history/view/media/history_view_media.h"
diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp
index b1b0783d2..73a6d572a 100644
--- a/Telegram/SourceFiles/history/view/history_view_element.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_element.cpp
@@ -10,8 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "api/api_chat_invite.h"
 #include "history/view/history_view_service_message.h"
 #include "history/view/history_view_message.h"
-#include "history/history_item_components.h"
-#include "history/history_item.h"
 #include "history/view/media/history_view_media.h"
 #include "history/view/media/history_view_media_grouped.h"
 #include "history/view/media/history_view_sticker.h"
@@ -22,7 +20,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/history_view_cursor_state.h"
 #include "history/view/history_view_spoiler_click_handler.h"
 #include "history/history.h"
-#include "history/history_service.h"
+#include "history/history_item.h"
+#include "history/history_item_components.h"
+#include "history/history_item_helpers.h"
 #include "base/unixtime.h"
 #include "core/application.h"
 #include "core/core_settings.h"
@@ -118,18 +118,6 @@ SimpleElementDelegate::SimpleElementDelegate(
 
 SimpleElementDelegate::~SimpleElementDelegate() = default;
 
-std::unique_ptr<HistoryView::Element> SimpleElementDelegate::elementCreate(
-		not_null<HistoryMessage*> message,
-		Element *replacing) {
-	return std::make_unique<HistoryView::Message>(this, message, replacing);
-}
-
-std::unique_ptr<HistoryView::Element> SimpleElementDelegate::elementCreate(
-		not_null<HistoryService*> message,
-		Element *replacing) {
-	return std::make_unique<HistoryView::Service>(this, message, replacing);
-}
-
 bool SimpleElementDelegate::elementUnderCursor(
 		not_null<const Element*> view) {
 	return false;
diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h
index 8e9e19a26..ec6807995 100644
--- a/Telegram/SourceFiles/history/view/history_view_element.h
+++ b/Telegram/SourceFiles/history/view/history_view_element.h
@@ -15,8 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 class History;
 class HistoryBlock;
 class HistoryItem;
-class HistoryMessage;
-class HistoryService;
 struct HistoryMessageReply;
 
 namespace Data {
@@ -64,12 +62,6 @@ class Element;
 class ElementDelegate {
 public:
 	virtual Context elementContext() = 0;
-	virtual std::unique_ptr<Element> elementCreate(
-		not_null<HistoryMessage*> message,
-		Element *replacing = nullptr) = 0;
-	virtual std::unique_ptr<Element> elementCreate(
-		not_null<HistoryService*> message,
-		Element *replacing = nullptr) = 0;
 	virtual bool elementUnderCursor(not_null<const Element*> view) = 0;
 	[[nodiscard]] virtual float64 elementHighlightOpacity(
 		not_null<const HistoryItem*> item) const = 0;
@@ -126,12 +118,6 @@ public:
 		Fn<void()> update);
 	~SimpleElementDelegate();
 
-	std::unique_ptr<Element> elementCreate(
-		not_null<HistoryMessage*> message,
-		Element *replacing = nullptr) override;
-	std::unique_ptr<Element> elementCreate(
-		not_null<HistoryService*> message,
-		Element *replacing = nullptr) override;
 	bool elementUnderCursor(not_null<const Element*> view) override;
 	[[nodiscard]] float64 elementHighlightOpacity(
 		not_null<const HistoryItem*> item) const override;
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
index edb83f268..1a457d178 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
@@ -10,8 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "base/unixtime.h"
 #include "base/qt/qt_key_modifiers.h"
 #include "base/qt/qt_common_adapters.h"
-#include "history/history_message.h"
+#include "history/history_item.h"
 #include "history/history_item_components.h"
+#include "history/history_item_helpers.h"
 #include "history/history_item_text.h"
 #include "history/view/media/history_view_media.h"
 #include "history/view/media/history_view_sticker.h"
@@ -1614,18 +1615,6 @@ Context ListWidget::elementContext() {
 	return _delegate->listContext();
 }
 
-std::unique_ptr<Element> ListWidget::elementCreate(
-		not_null<HistoryMessage*> message,
-		Element *replacing) {
-	return std::make_unique<Message>(this, message, replacing);
-}
-
-std::unique_ptr<Element> ListWidget::elementCreate(
-		not_null<HistoryService*> message,
-		Element *replacing) {
-	return std::make_unique<Service>(this, message, replacing);
-}
-
 bool ListWidget::elementUnderCursor(
 		not_null<const HistoryView::Element*> view) {
 	return (_overElement == view);
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h
index 0dbeb7eef..e33194d94 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.h
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h
@@ -286,12 +286,6 @@ public:
 
 	// ElementDelegate interface.
 	Context elementContext() override;
-	std::unique_ptr<Element> elementCreate(
-		not_null<HistoryMessage*> message,
-		Element *replacing = nullptr) override;
-	std::unique_ptr<Element> elementCreate(
-		not_null<HistoryService*> message,
-		Element *replacing = nullptr) override;
 	bool elementUnderCursor(not_null<const Element*> view) override;
 	[[nodiscard]] float64 elementHighlightOpacity(
 		not_null<const HistoryItem*> item) const override;
diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp
index c467ed3c1..c27aeed28 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_message.cpp
@@ -10,8 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "core/click_handler_types.h" // ClickHandlerContext
 #include "core/ui_integration.h"
 #include "history/view/history_view_cursor_state.h"
+#include "history/history_item.h"
 #include "history/history_item_components.h"
-#include "history/history_message.h"
+#include "history/history_item_helpers.h"
 #include "history/view/media/history_view_media.h"
 #include "history/view/media/history_view_web_page.h"
 #include "history/view/reactions/history_view_reactions.h"
@@ -313,7 +314,7 @@ LogEntryOriginal::~LogEntryOriginal() = default;
 
 Message::Message(
 	not_null<ElementDelegate*> delegate,
-	not_null<HistoryMessage*> data,
+	not_null<HistoryItem*> data,
 	Element *replacing)
 : Element(delegate, data, replacing, Flag(0))
 , _bottomInfo(
@@ -348,10 +349,6 @@ Message::~Message() {
 	}
 }
 
-not_null<HistoryMessage*> Message::message() const {
-	return static_cast<HistoryMessage*>(data().get());
-}
-
 void Message::refreshRightBadge() {
 	const auto text = [&] {
 		if (data()->isDiscussionPost()) {
@@ -403,7 +400,7 @@ void Message::applyGroupAdminChanges(
 }
 
 void Message::animateReaction(Ui::ReactionFlyAnimationArgs &&args) {
-	const auto item = message();
+	const auto item = data();
 	const auto media = this->media();
 
 	auto g = countGeometry();
@@ -510,7 +507,7 @@ auto Message::takeReactionAnimations()
 }
 
 QSize Message::performCountOptimalSize() {
-	const auto item = message();
+	const auto item = data();
 	const auto markup = item->inlineReplyMarkup();
 	refreshIsTopicRootReply();
 	validateText();
@@ -689,7 +686,7 @@ QSize Message::performCountOptimalSize() {
 }
 
 void Message::refreshTopicButton() {
-	const auto item = message();
+	const auto item = data();
 	if (isAttachedToPrevious() || context() != Context::History) {
 		_topicButton = nullptr;
 	} else if (const auto topic = item->topic()) {
@@ -742,7 +739,7 @@ void Message::draw(Painter &p, const PaintContext &context) const {
 		return;
 	}
 
-	const auto item = message();
+	const auto item = data();
 	const auto media = this->media();
 
 	const auto stm = context.messageStyle();
@@ -1039,7 +1036,7 @@ void Message::draw(Painter &p, const PaintContext &context) const {
 	}
 
 	if (const auto reply = displayedReply()) {
-		if (reply->isNameUpdated(message())) {
+		if (reply->isNameUpdated(data())) {
 			const_cast<Message*>(this)->setPendingResize();
 		}
 	}
@@ -1174,7 +1171,7 @@ void Message::paintFromName(
 		Painter &p,
 		QRect &trect,
 		const PaintContext &context) const {
-	const auto item = message();
+	const auto item = data();
 	if (!displayFromName()) {
 		return;
 	}
@@ -1367,7 +1364,7 @@ void Message::paintForwardedInfo(
 		QRect &trect,
 		const PaintContext &context) const {
 	if (displayForwardedFrom()) {
-		const auto item = message();
+		const auto item = data();
 		const auto st = context.st;
 		const auto stm = context.messageStyle();
 		const auto forwarded = item->Get<HistoryMessageForwarded>();
@@ -1438,7 +1435,7 @@ void Message::paintViaBotIdInfo(
 		Painter &p,
 		QRect &trect,
 		const PaintContext &context) const {
-	const auto item = message();
+	const auto item = data();
 	if (!displayFromName() && !displayForwardedFrom()) {
 		if (auto via = item->Get<HistoryMessageVia>()) {
 			const auto stm = context.messageStyle();
@@ -1479,7 +1476,7 @@ PointState Message::pointState(QPoint point) const {
 	}
 
 	const auto media = this->media();
-	const auto item = message();
+	const auto item = data();
 	const auto reactionsInBubble = _reactions && embedReactionsInBubble();
 	if (drawBubble()) {
 		if (!g.contains(point)) {
@@ -1744,7 +1741,7 @@ void Message::unloadHeavyPart() {
 
 bool Message::showForwardsFromSender(
 		not_null<HistoryMessageForwarded*> forwarded) const {
-	const auto peer = message()->history()->peer;
+	const auto peer = data()->history()->peer;
 	return peer->isSelf()
 		|| peer->isRepliesChat()
 		|| forwarded->imported;
@@ -1760,7 +1757,7 @@ bool Message::hasFromPhoto() const {
 	case Context::History:
 	case Context::Pinned:
 	case Context::Replies: {
-		const auto item = message();
+		const auto item = data();
 		if (item->isPost()) {
 			if (item->isSponsored()) {
 				if (item->history()->peer->isMegagroup()) {
@@ -1794,7 +1791,7 @@ bool Message::hasFromPhoto() const {
 TextState Message::textState(
 		QPoint point,
 		StateRequest request) const {
-	const auto item = message();
+	const auto item = data();
 	const auto media = this->media();
 
 	auto result = TextState(item);
@@ -2067,7 +2064,7 @@ bool Message::getStateFromName(
 		if (replyWidth) {
 			availableWidth -= st::msgPadding.right() + replyWidth;
 		}
-		const auto item = message();
+		const auto item = data();
 		const auto from = item->displayFrom();
 		const auto nameText = [&]() -> const Ui::Text::String * {
 			if (from) {
@@ -2138,7 +2135,7 @@ bool Message::getStateForwardedInfo(
 	if (!displayForwardedFrom()) {
 		return false;
 	}
-	const auto item = message();
+	const auto item = data();
 	const auto forwarded = item->Get<HistoryMessageForwarded>();
 	const auto skip1 = forwarded->psaType.isEmpty()
 		? 0
@@ -2252,7 +2249,7 @@ bool Message::getStateViaBotIdInfo(
 		QPoint point,
 		QRect &trect,
 		not_null<TextState*> outResult) const {
-	const auto item = message();
+	const auto item = data();
 	if (const auto via = item->Get<HistoryMessageVia>()) {
 		if (!displayFromName() && !displayForwardedFrom()) {
 			if (QRect(trect.x(), trect.y(), via->width, st::msgNameFont->height).contains(point)) {
@@ -2273,7 +2270,7 @@ bool Message::getStateText(
 	if (!hasVisibleText()) {
 		return false;
 	}
-	const auto item = message();
+	const auto item = data();
 	if (base::in_range(point.y(), trect.y(), trect.y() + trect.height())) {
 		*outResult = TextState(item, text().getState(
 			point - trect.topLeft(),
@@ -2286,7 +2283,7 @@ bool Message::getStateText(
 
 // Forward to media.
 void Message::updatePressed(QPoint point) {
-	const auto item = message();
+	const auto item = data();
 	const auto media = this->media();
 	if (!media) return;
 
@@ -2759,7 +2756,7 @@ void Message::updateViewButtonExistence() {
 }
 
 void Message::initLogEntryOriginal() {
-	if (const auto log = message()->Get<HistoryMessageLogEntryOriginal>()) {
+	if (const auto log = data()->Get<HistoryMessageLogEntryOriginal>()) {
 		AddComponents(LogEntryOriginal::Bit());
 		const auto entry = Get<LogEntryOriginal>();
 		entry->page = std::make_unique<WebPage>(this, log->page);
@@ -2767,7 +2764,7 @@ void Message::initLogEntryOriginal() {
 }
 
 void Message::initPsa() {
-	if (const auto forwarded = message()->Get<HistoryMessageForwarded>()) {
+	if (const auto forwarded = data()->Get<HistoryMessageForwarded>()) {
 		if (!forwarded->psaType.isEmpty()) {
 			AddComponents(PsaTooltipState::Bit());
 			Get<PsaTooltipState>()->type = forwarded->psaType;
@@ -2810,7 +2807,7 @@ bool Message::hasFromName() const {
 	case Context::History:
 	case Context::Pinned:
 	case Context::Replies: {
-		const auto item = message();
+		const auto item = data();
 		const auto peer = item->history()->peer;
 		if (hasOutLayout() && !item->from()->isChannel()) {
 			return false;
@@ -2841,7 +2838,7 @@ bool Message::displayFromName() const {
 }
 
 bool Message::displayForwardedFrom() const {
-	const auto item = message();
+	const auto item = data();
 	if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
 		if (showForwardsFromSender(forwarded)) {
 			return false;
@@ -2858,7 +2855,7 @@ bool Message::displayForwardedFrom() const {
 }
 
 bool Message::hasOutLayout() const {
-	const auto item = message();
+	const auto item = data();
 	if (item->history()->peer->isSelf()) {
 		return !item->Has<HistoryMessageForwarded>();
 	} else if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
@@ -2874,7 +2871,7 @@ bool Message::hasOutLayout() const {
 }
 
 bool Message::drawBubble() const {
-	const auto item = message();
+	const auto item = data();
 	if (isHidden()) {
 		return false;
 	} else if (logEntryOriginal()) {
@@ -2895,7 +2892,7 @@ TopicButton *Message::displayedTopicButton() const {
 }
 
 bool Message::unwrapped() const {
-	const auto item = message();
+	const auto item = data();
 	if (isHidden()) {
 		return true;
 	} else if (logEntryOriginal()) {
@@ -2989,7 +2986,7 @@ std::optional<QSize> Message::rightActionSize() const {
 }
 
 bool Message::displayFastShare() const {
-	const auto item = message();
+	const auto item = data();
 	const auto peer = item->history()->peer;
 	if (!item->allowsForward()) {
 		return false;
@@ -3015,7 +3012,7 @@ bool Message::displayGoToOriginal() const {
 	if (isPinnedContext()) {
 		return !hasOutLayout();
 	}
-	const auto item = message();
+	const auto item = data();
 	if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
 		return forwarded->savedFromPeer
 			&& forwarded->savedFromMsgId
@@ -3116,7 +3113,7 @@ void Message::ensureRightAction() const {
 
 ClickHandlerPtr Message::prepareRightActionLink() const {
 	if (isPinnedContext()) {
-		return goToMessageClickHandler(data());
+		return JumpToMessageClickHandler(data());
 	} else if (displayRightActionComments()) {
 		return createGoToCommentsLink();
 	}
@@ -3207,7 +3204,7 @@ bool Message::isPinnedContext() const {
 }
 
 void Message::updateMediaInBubbleState() {
-	const auto item = message();
+	const auto item = data();
 	const auto media = this->media();
 
 	if (media) {
@@ -3271,7 +3268,7 @@ void Message::updateMediaInBubbleState() {
 }
 
 void Message::fromNameUpdated(int width) const {
-	const auto item = message();
+	const auto item = data();
 	const auto replyWidth = hasFastReply()
 		? st::msgFont->width(FastReplyText())
 		: 0;
@@ -3356,7 +3353,7 @@ QRect Message::innerGeometry() const {
 		}
 		if (!displayFromName() && !displayForwardedFrom()) {
 			// See paintViaBotIdInfo().
-			if (message()->Has<HistoryMessageVia>()) {
+			if (data()->Has<HistoryMessageVia>()) {
 				result.translate(0, st::msgServiceNameFont->height);
 			}
 		}
@@ -3468,7 +3465,7 @@ int Message::resizeContentGetHeight(int newWidth) {
 
 	auto newHeight = minHeight();
 
-	const auto item = message();
+	const auto item = data();
 	const auto media = this->media();
 	const auto mediaDisplayed = media ? media->isDisplayed() : false;
 	const auto bubble = drawBubble();
@@ -3635,7 +3632,7 @@ bool Message::needInfoDisplay() const {
 }
 
 bool Message::hasVisibleText() const {
-	if (message()->emptyText()) {
+	if (data()->emptyText()) {
 		return false;
 	}
 	const auto media = this->media();
@@ -3660,7 +3657,7 @@ QSize Message::performCountCurrentSize(int newWidth) {
 }
 
 void Message::refreshInfoSkipBlock() {
-	const auto item = message();
+	const auto item = data();
 	const auto media = this->media();
 	const auto hasTextSkipBlock = [&] {
 		if (item->_text.empty()) {
@@ -3687,7 +3684,7 @@ void Message::refreshInfoSkipBlock() {
 }
 
 TimeId Message::displayedEditDate() const {
-	const auto item = message();
+	const auto item = data();
 	const auto overrided = media() && media()->overrideEditedDate();
 	if (item->hideEditedBadge() && !overrided) {
 		return TimeId(0);
@@ -3703,7 +3700,7 @@ HistoryMessageEdited *Message::displayedEditBadge() {
 			return media->displayedEditBadge();
 		}
 	}
-	return message()->Get<HistoryMessageEdited>();
+	return data()->Get<HistoryMessageEdited>();
 }
 
 const HistoryMessageEdited *Message::displayedEditBadge() const {
@@ -3712,7 +3709,7 @@ const HistoryMessageEdited *Message::displayedEditBadge() const {
 			return media->displayedEditBadge();
 		}
 	}
-	return message()->Get<HistoryMessageEdited>();
+	return data()->Get<HistoryMessageEdited>();
 }
 
 } // namespace HistoryView
diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h
index 16b76f0d3..f9fd8afa7 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.h
+++ b/Telegram/SourceFiles/history/view/history_view_message.h
@@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/history_view_bottom_info.h"
 #include "ui/effects/animations.h"
 
-class HistoryMessage;
+class HistoryItem;
 struct HistoryMessageEdited;
 struct HistoryMessageForwarded;
 struct HistoryMessageReplyMarkup;
@@ -64,7 +64,7 @@ class Message final : public Element {
 public:
 	Message(
 		not_null<ElementDelegate*> delegate,
-		not_null<HistoryMessage*> data,
+		not_null<HistoryItem*> data,
 		Element *replacing);
 	~Message();
 
@@ -72,8 +72,6 @@ public:
 		const ClickHandlerPtr &handler,
 		bool pressed) override;
 
-	not_null<HistoryMessage*> message() const;
-
 	[[nodiscard]] const HistoryMessageEdited *displayedEditBadge() const;
 	[[nodiscard]] HistoryMessageEdited *displayedEditBadge();
 
diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp
index d1d1556dc..167d5f0e2 100644
--- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp
@@ -7,11 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "history/view/history_view_service_message.h"
 
-#include "history/history.h"
-#include "history/history_service.h"
 #include "history/view/media/history_view_media.h"
-#include "history/history_item_components.h"
 #include "history/view/history_view_cursor_state.h"
+#include "history/history.h"
+#include "history/history_item.h"
+#include "history/history_item_components.h"
+#include "history/history_item_helpers.h"
 #include "data/data_abstract_structure.h"
 #include "data/data_chat.h"
 #include "data/data_channel.h"
@@ -400,15 +401,11 @@ QVector<int> ServiceMessagePainter::CountLineWidths(
 
 Service::Service(
 	not_null<ElementDelegate*> delegate,
-	not_null<HistoryService*> data,
+	not_null<HistoryItem*> data,
 	Element *replacing)
 : Element(delegate, data, replacing, Flag::ServiceMessage) {
 }
 
-not_null<HistoryService*> Service::message() const {
-	return static_cast<HistoryService*>(data().get());
-}
-
 QRect Service::innerGeometry() const {
 	return countGeometry();
 }
@@ -592,7 +589,7 @@ PointState Service::pointState(QPoint point) const {
 }
 
 TextState Service::textState(QPoint point, StateRequest request) const {
-	const auto item = message();
+	const auto item = data();
 	const auto media = this->media();
 
 	auto result = TextState(item);
diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.h b/Telegram/SourceFiles/history/view/history_view_service_message.h
index 73a51a480..5ee09aa7e 100644
--- a/Telegram/SourceFiles/history/view/history_view_service_message.h
+++ b/Telegram/SourceFiles/history/view/history_view_service_message.h
@@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "history/view/history_view_element.h"
 
-class HistoryService;
+class HistoryItem;
 
 namespace Ui {
 class ChatStyle;
@@ -30,7 +30,7 @@ class Service final : public Element {
 public:
 	Service(
 		not_null<ElementDelegate*> delegate,
-		not_null<HistoryService*> data,
+		not_null<HistoryItem*> data,
 		Element *replacing);
 
 	int marginTop() const override;
@@ -50,9 +50,7 @@ public:
 	QRect innerGeometry() const override;
 
 private:
-	not_null<HistoryService*> message() const;
-
-	QRect countGeometry() const;
+	[[nodiscard]] QRect countGeometry() const;
 
 	QSize performCountOptimalSize() override;
 	QSize performCountCurrentSize(int newWidth) override;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp
index 09d2df63e..a4310c817 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp
@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/media/history_view_media_grouped.h"
 
 #include "history/history_item_components.h"
-#include "history/history_message.h"
+#include "history/history_item.h"
 #include "history/history.h"
 #include "history/view/history_view_element.h"
 #include "history/view/history_view_cursor_state.h"
diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp
index 616f8741b..9c524d643 100644
--- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp
+++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp
@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "history/view/reactions/history_view_reactions.h"
 
-#include "history/history_message.h"
+#include "history/history_item.h"
 #include "history/history.h"
 #include "history/view/history_view_message.h"
 #include "history/view/history_view_cursor_state.h"
@@ -604,7 +604,7 @@ void InlineList::continueAnimations(base::flat_map<
 
 InlineListData InlineListDataFromMessage(not_null<Message*> message) {
 	using Flag = InlineListData::Flag;
-	const auto item = message->message();
+	const auto item = message->data();
 	auto result = InlineListData();
 	result.reactions = item->reactions();
 	if (const auto user = item->history()->peer->asUser()) {
diff --git a/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp b/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp
index 45c5f7599..36059ac33 100644
--- a/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp
+++ b/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp
@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "main/main_session.h"
 #include "main/main_account.h"
 #include "history/history_item.h"
+#include "history/history_item_helpers.h"
 #include "history/history.h"
 #include "core/application.h"
 #include "storage/storage_shared_media.h"
diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp
index 2f053fbdc..ffce0bb00 100644
--- a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp
+++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp
@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_download_manager.h"
 #include "data/data_forum_topic.h"
 #include "history/history_item.h"
+#include "history/history_item_helpers.h"
 #include "history/history.h"
 #include "history/view/history_view_cursor_state.h"
 #include "history/view/history_view_service_message.h"
@@ -892,7 +893,7 @@ void ListWidget::showContextMenu(
 			tr::lng_context_to_msg(tr::now),
 			[=] {
 				if (const auto item = MessageByGlobalId(globalId)) {
-					goToMessageClickHandler(item)->onClick({});
+					JumpToMessageClickHandler(item)->onClick({});
 				}
 			},
 			&st::menuIconShowInChat);
diff --git a/Telegram/SourceFiles/info/media/info_media_provider.cpp b/Telegram/SourceFiles/info/media/info_media_provider.cpp
index 6bec9e04b..6d17213d1 100644
--- a/Telegram/SourceFiles/info/media/info_media_provider.cpp
+++ b/Telegram/SourceFiles/info/media/info_media_provider.cpp
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "lang/lang_keys.h"
 #include "history/history.h"
 #include "history/history_item.h"
+#include "history/history_item_helpers.h"
 #include "data/data_session.h"
 #include "data/data_chat.h"
 #include "data/data_channel.h"
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp
index 50b96f1be..76cb9cb27 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp
@@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "storage/localstorage.h"
 #include "lang/lang_keys.h"
 #include "history/history.h"
-#include "history/history_message.h"
+#include "history/history_item.h"
 #include "data/data_channel.h"
 #include "ui/text/format_values.h" // Ui::FormatPhone
 
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index fb4b8fe91..66dc1c3a4 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -62,7 +62,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "dialogs/dialogs_key.h"
 #include "history/history.h"
 #include "history/history_widget.h"
-#include "history/history_message.h"
+#include "history/history_item.h"
+#include "history/history_item_helpers.h" // GetErrorTextForSending.
 #include "history/view/media/history_view_media.h"
 #include "history/view/history_view_service_message.h"
 #include "history/view/history_view_element.h"
diff --git a/Telegram/SourceFiles/media/player/media_player_widget.cpp b/Telegram/SourceFiles/media/player/media_player_widget.cpp
index 772577ceb..f43351253 100644
--- a/Telegram/SourceFiles/media/player/media_player_widget.cpp
+++ b/Telegram/SourceFiles/media/player/media_player_widget.cpp
@@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "styles/style_media_player.h"
 #include "styles/style_media_view.h"
 #include "history/history_item.h"
+#include "history/history_item_helpers.h"
 #include "storage/storage_account.h"
 #include "main/main_session.h"
 
diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
index a2ad42ffd..3dc8fe652 100644
--- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
+++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
@@ -46,7 +46,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "media/streaming/media_streaming_player.h"
 #include "media/player/media_player_instance.h"
 #include "history/history.h"
-#include "history/history_message.h"
+#include "history/history_item.h"
+#include "history/history_item_helpers.h"
 #include "history/view/media/history_view_media.h"
 #include "data/data_media_types.h"
 #include "data/data_session.h"
diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp
index 5a60b599b..64412a4f0 100644
--- a/Telegram/SourceFiles/overview/overview_layout.cpp
+++ b/Telegram/SourceFiles/overview/overview_layout.cpp
@@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history.h"
 #include "history/history_item.h"
 #include "history/history_item_components.h"
+#include "history/history_item_helpers.h"
 #include "history/view/history_view_cursor_state.h"
 #include "history/view/media/history_view_document.h" // DrawThumbnailAsSongCover
 #include "base/unixtime.h"
@@ -644,7 +645,7 @@ Voice::Voice(
 			lt_duration,
 			{ .text = Ui::FormatDurationText(duration()) },
 			Ui::Text::WithEntities));
-	_details.setLink(1, goToMessageClickHandler(parent));
+	_details.setLink(1, JumpToMessageClickHandler(parent));
 }
 
 void Voice::initDimensions() {
@@ -944,7 +945,9 @@ Document::Document(
 	const style::OverviewFileLayout &st)
 : RadialProgressItem(delegate, parent)
 , _data(fields.document)
-, _msgl(parent->isHistoryEntry() ? goToMessageClickHandler(parent) : nullptr)
+, _msgl(parent->isHistoryEntry()
+	? JumpToMessageClickHandler(parent)
+	: nullptr)
 , _namel(std::make_shared<DocumentOpenClickHandler>(
 	_data,
 	crl::guard(this, [=](FullMsgId id) {
diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp
index fa423d8a0..248f09caf 100644
--- a/Telegram/SourceFiles/payments/payments_form.cpp
+++ b/Telegram/SourceFiles/payments/payments_form.cpp
@@ -16,7 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_file_origin.h"
 #include "countries/countries_instance.h"
 #include "history/history_item.h"
-#include "history/history_service.h" // HistoryServicePayment.
+#include "history/history_item_components.h"
 #include "stripe/stripe_api_client.h"
 #include "stripe/stripe_error.h"
 #include "stripe/stripe_token.h"
diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp
index 2cb3ddbe6..8a85b73c6 100644
--- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp
+++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp
@@ -21,7 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/history_view_element.h"
 #include "history/view/history_view_message.h"
 #include "history/history_item_components.h"
-#include "history/history_message.h"
+#include "history/history_item.h"
 #include "history/history.h"
 #include "calls/calls_instance.h"
 #include "base/unixtime.h"
@@ -188,7 +188,7 @@ AdminLog::OwnedItem GenerateForwardedItem(
 			history->nextNonHistoryEntryId(),
 			data,
 			MessageFlag::FakeHistoryItem);
-	}, [](auto &&) -> not_null<HistoryMessage*> {
+	}, [](auto &&) -> not_null<HistoryItem*> {
 		Unexpected("Type in GenerateForwardedItem.");
 	});
 
diff --git a/Telegram/SourceFiles/support/support_autocomplete.cpp b/Telegram/SourceFiles/support/support_autocomplete.cpp
index b9c41b8f4..6561b8bfd 100644
--- a/Telegram/SourceFiles/support/support_autocomplete.cpp
+++ b/Telegram/SourceFiles/support/support_autocomplete.cpp
@@ -18,7 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "support/support_common.h"
 #include "history/view/history_view_message.h"
 #include "history/view/history_view_service_message.h"
-#include "history/history_message.h"
+#include "history/history_item.h"
 #include "lang/lang_keys.h"
 #include "base/unixtime.h"
 #include "base/call_delayed.h"
diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp
index 43ab08b5e..3ab37825b 100644
--- a/Telegram/SourceFiles/window/window_peer_menu.cpp
+++ b/Telegram/SourceFiles/window/window_peer_menu.cpp
@@ -54,8 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "api/api_updates.h"
 #include "mtproto/mtproto_config.h"
 #include "history/history.h"
-#include "history/history_item.h"
-#include "history/history_message.h" // GetErrorTextForSending.
+#include "history/history_item_helpers.h" // GetErrorTextForSending.
 #include "history/view/history_view_context_menu.h"
 #include "window/window_adaptive.h" // Adaptive::isThreeColumn
 #include "window/window_session_controller.h"