/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org

Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.

Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "history/history_widget.h"

#include "styles/style_history.h"
#include "styles/style_dialogs.h"
#include "styles/style_window.h"
#include "styles/style_boxes.h"
#include "styles/style_profile.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_info.h"
#include "boxes/confirm_box.h"
#include "boxes/send_files_box.h"
#include "boxes/share_box.h"
#include "core/file_utilities.h"
#include "ui/toast/toast.h"
#include "ui/special_buttons.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/inner_dropdown.h"
#include "ui/widgets/dropdown_menu.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/shadow.h"
#include "ui/effects/ripple_animation.h"
#include "inline_bots/inline_bot_result.h"
#include "data/data_drafts.h"
#include "history/history_message.h"
#include "history/history_service_layout.h"
#include "history/history_media_types.h"
#include "history/history_drag_area.h"
#include "history/history_inner_widget.h"
#include "profile/profile_block_group_members.h"
#include "info/info_memento.h"
#include "core/click_handler_types.h"
#include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h"
#include "chat_helpers/tabbed_section.h"
#include "chat_helpers/bot_keyboard.h"
#include "chat_helpers/message_field.h"
#include "lang/lang_keys.h"
#include "application.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "passcodewidget.h"
#include "mainwindow.h"
#include "storage/file_upload.h"
#include "media/media_audio.h"
#include "media/media_audio_capture.h"
#include "media/player/media_player_instance.h"
#include "storage/localstorage.h"
#include "apiwrap.h"
#include "history/history_top_bar_widget.h"
#include "observer_peer.h"
#include "base/qthelp_regex.h"
#include "ui/widgets/popup_menu.h"
#include "platform/platform_file_utilities.h"
#include "auth_session.h"
#include "window/themes/window_theme.h"
#include "window/notifications_manager.h"
#include "window/window_controller.h"
#include "window/window_slide_animation.h"
#include "window/window_peer_menu.h"
#include "inline_bots/inline_results_widget.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "core/crash_reports.h"

namespace {

constexpr auto kMessagesPerPageFirst = 30;
constexpr auto kMessagesPerPage = 50;
constexpr auto kPreloadHeightsCount = 3; // when 3 screens to scroll left make a preload request
constexpr auto kTabbedSelectorToggleTooltipTimeoutMs = 3000;
constexpr auto kTabbedSelectorToggleTooltipCount = 3;
constexpr auto kScrollToVoiceAfterScrolledMs = 1000;
constexpr auto kSkipRepaintWhileScrollMs = 100;
constexpr auto kShowMembersDropdownTimeoutMs = 300;
constexpr auto kDisplayEditTimeWarningMs = 300 * 1000;
constexpr auto kFullDayInMs = 86400 * 1000;
constexpr auto kCancelTypingActionTimeout = TimeMs(5000);

ApiWrap::RequestMessageDataCallback replyEditMessageDataCallback() {
	return [](ChannelData *channel, MsgId msgId) {
		if (App::main()) {
			App::main()->messageDataReceived(channel, msgId);
		}
	};
}

MTPVector<MTPDocumentAttribute> composeDocumentAttributes(DocumentData *document) {
	auto filenameAttribute = MTP_documentAttributeFilename(
		MTP_string(document->filename()));
	auto attributes = QVector<MTPDocumentAttribute>(1, filenameAttribute);
	if (document->dimensions.width() > 0 && document->dimensions.height() > 0) {
		int32 duration = document->duration();
		if (duration >= 0) {
			auto flags = MTPDdocumentAttributeVideo::Flags(0);
			if (document->isVideoMessage()) {
				flags |= MTPDdocumentAttributeVideo::Flag::f_round_message;
			}
			attributes.push_back(MTP_documentAttributeVideo(MTP_flags(flags), MTP_int(duration), MTP_int(document->dimensions.width()), MTP_int(document->dimensions.height())));
		} else {
			attributes.push_back(MTP_documentAttributeImageSize(MTP_int(document->dimensions.width()), MTP_int(document->dimensions.height())));
		}
	}
	if (document->type == AnimatedDocument) {
		attributes.push_back(MTP_documentAttributeAnimated());
	} else if (document->type == StickerDocument && document->sticker()) {
		attributes.push_back(MTP_documentAttributeSticker(MTP_flags(0), MTP_string(document->sticker()->alt), document->sticker()->set, MTPMaskCoords()));
	} else if (const auto song = document->song()) {
		auto flags = MTPDdocumentAttributeAudio::Flag::f_title | MTPDdocumentAttributeAudio::Flag::f_performer;
		attributes.push_back(MTP_documentAttributeAudio(MTP_flags(flags), MTP_int(song->duration), MTP_string(song->title), MTP_string(song->performer), MTPstring()));
	} else if (const auto voice = document->voice()) {
		auto flags = MTPDdocumentAttributeAudio::Flag::f_voice | MTPDdocumentAttributeAudio::Flag::f_waveform;
		attributes.push_back(MTP_documentAttributeAudio(MTP_flags(flags), MTP_int(voice->duration), MTPstring(), MTPstring(), MTP_bytes(documentWaveformEncode5bit(voice->waveform))));
	}
	return MTP_vector<MTPDocumentAttribute>(attributes);
}

} // namespace

ReportSpamPanel::ReportSpamPanel(QWidget *parent) : TWidget(parent),
_report(this, lang(lng_report_spam), st::reportSpamHide),
_hide(this, lang(lng_report_spam_hide), st::reportSpamHide),
_clear(this, lang(lng_profile_delete_conversation)) {
	resize(parent->width(), _hide->height() + st::lineWidth);

	connect(_report, SIGNAL(clicked()), this, SIGNAL(reportClicked()));
	connect(_hide, SIGNAL(clicked()), this, SIGNAL(hideClicked()));
	connect(_clear, SIGNAL(clicked()), this, SIGNAL(clearClicked()));

	_clear->hide();
}

void ReportSpamPanel::resizeEvent(QResizeEvent *e) {
	_report->resize(width() - (_hide->width() + st::reportSpamSeparator) * 2, _report->height());
	_report->moveToLeft(_hide->width() + st::reportSpamSeparator, 0);
	_hide->moveToRight(0, 0);
	_clear->move((width() - _clear->width()) / 2, height() - _clear->height() - ((height() - st::msgFont->height - _clear->height()) / 2));
}

void ReportSpamPanel::paintEvent(QPaintEvent *e) {
	Painter p(this);
	p.fillRect(QRect(0, 0, width(), height() - st::lineWidth), st::reportSpamBg);
	p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, height() - st::lineWidth, width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowFg);
	if (!_clear->isHidden()) {
		p.setPen(st::reportSpamFg);
		p.setFont(st::msgFont);
		p.drawText(QRect(_report->x(), (_clear->y() - st::msgFont->height) / 2, _report->width(), st::msgFont->height), lang(lng_report_spam_thanks), style::al_top);
	}
}

void ReportSpamPanel::setReported(bool reported, PeerData *onPeer) {
	if (reported) {
		_report->hide();
		_clear->setText(lang(onPeer->isChannel() ? (onPeer->isMegagroup() ? lng_profile_leave_group : lng_profile_leave_channel) : lng_profile_delete_conversation));
		_clear->show();
	} else {
		_report->show();
		_clear->hide();
	}
	update();
}

HistoryHider::HistoryHider(
	MainWidget *parent,
	MessageIdsList &&items)
: RpWidget(parent)
, _forwardItems(std::move(items))
, _send(this, langFactory(lng_forward_send), st::defaultBoxButton)
, _cancel(this, langFactory(lng_cancel), st::defaultBoxButton) {
	init();
}

HistoryHider::HistoryHider(MainWidget *parent) : RpWidget(parent)
, _sendPath(true)
, _send(this, langFactory(lng_forward_send), st::defaultBoxButton)
, _cancel(this, langFactory(lng_cancel), st::defaultBoxButton) {
	init();
}

HistoryHider::HistoryHider(MainWidget *parent, const QString &botAndQuery) : RpWidget(parent)
, _botAndQuery(botAndQuery)
, _send(this, langFactory(lng_forward_send), st::defaultBoxButton)
, _cancel(this, langFactory(lng_cancel), st::defaultBoxButton) {
	init();
}

HistoryHider::HistoryHider(MainWidget *parent, const QString &url, const QString &text) : RpWidget(parent)
, _shareUrl(url)
, _shareText(text)
, _send(this, langFactory(lng_forward_send), st::defaultBoxButton)
, _cancel(this, langFactory(lng_cancel), st::defaultBoxButton) {
	init();
}

void HistoryHider::init() {
	subscribe(Lang::Current().updated(), [this] { refreshLang(); });
	connect(_send, SIGNAL(clicked()), this, SLOT(forward()));
	connect(_cancel, SIGNAL(clicked()), this, SLOT(startHide()));
	subscribe(Global::RefPeerChooseCancel(), [this] { startHide(); });

	_chooseWidth = st::historyForwardChooseFont->width(lang(_botAndQuery.isEmpty() ? lng_forward_choose : lng_inline_switch_choose));

	resizeEvent(0);
	_a_opacity.start([this] { update(); }, 0., 1., st::boxDuration);
}

void HistoryHider::refreshLang() {
	InvokeQueued(this, [this] { updateControlsGeometry(); });
}

bool HistoryHider::withConfirm() const {
	return _sendPath;
}

void HistoryHider::paintEvent(QPaintEvent *e) {
	Painter p(this);
	auto opacity = _a_opacity.current(getms(), _hiding ? 0. : 1.);
	if (opacity == 0.) {
		if (_hiding) {
			QTimer::singleShot(0, this, SLOT(deleteLater()));
		}
		return;
	}

	p.setOpacity(opacity);
	if (!_hiding || !_cacheForAnim.isNull() || !_offered) {
		p.fillRect(rect(), st::layerBg);
	}
	if (_cacheForAnim.isNull() || !_offered) {
		p.setFont(st::historyForwardChooseFont);
		if (_offered) {
			Ui::Shadow::paint(p, _box, width(), st::boxRoundShadow);
			App::roundRect(p, _box, st::boxBg, BoxCorners);

			p.setPen(st::boxTextFg);
			_toText.drawLeftElided(p, _box.left() + st::boxPadding.left(), _box.y() + st::boxTopMargin + st::boxPadding.top(), _toTextWidth + 2, width(), 1, style::al_left);
		} else {
			auto w = st::historyForwardChooseMargins.left() + _chooseWidth + st::historyForwardChooseMargins.right();
			auto h = st::historyForwardChooseMargins.top() + st::historyForwardChooseFont->height + st::historyForwardChooseMargins.bottom();
			App::roundRect(p, (width() - w) / 2, (height() - h) / 2, w, h, st::historyForwardChooseBg, ForwardCorners);

			p.setPen(st::historyForwardChooseFg);
			p.drawText(_box, lang(_botAndQuery.isEmpty() ? lng_forward_choose : lng_inline_switch_choose), QTextOption(style::al_center));
		}
	} else {
		p.drawPixmap(_box.left(), _box.top(), _cacheForAnim);
	}
}

void HistoryHider::keyPressEvent(QKeyEvent *e) {
	if (e->key() == Qt::Key_Escape) {
		if (_offered) {
			_offered = nullptr;
			resizeEvent(nullptr);
			update();
			App::main()->dialogsActivate();
		} else {
			startHide();
		}
	} else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
		if (_offered) {
			forward();
		}
	}
}

void HistoryHider::mousePressEvent(QMouseEvent *e) {
	if (e->button() == Qt::LeftButton) {
		if (!_box.contains(e->pos())) {
			startHide();
		}
	}
}

void HistoryHider::startHide() {
	if (_hiding) return;
	_hiding = true;
	if (Adaptive::OneColumn()) {
		QTimer::singleShot(0, this, SLOT(deleteLater()));
	} else {
		if (_offered) _cacheForAnim = myGrab(this, _box);
		if (_forwardRequest) MTP::cancel(_forwardRequest);
		_send->hide();
		_cancel->hide();
		_a_opacity.start([this] { animationCallback(); }, 1., 0., st::boxDuration);
	}
}

void HistoryHider::animationCallback() {
	update();
	if (!_a_opacity.animating() && _hiding) {
		QTimer::singleShot(0, this, SLOT(deleteLater()));
	}
}

void HistoryHider::forward() {
	if (!_hiding && _offered) {
		if (_sendPath) {
			parent()->onSendPaths(_offered->id);
		} else if (!_shareUrl.isEmpty()) {
			parent()->shareUrl(_offered, _shareUrl, _shareText);
		} else if (!_botAndQuery.isEmpty()) {
			parent()->onInlineSwitchChosen(_offered->id, _botAndQuery);
		} else {
			parent()->setForwardDraft(_offered->id, std::move(_forwardItems));
		}
	}
	emit forwarded();
}

void HistoryHider::forwardDone() {
	_forwardRequest = 0;
	startHide();
}

MainWidget *HistoryHider::parent() {
	return static_cast<MainWidget*>(parentWidget());
}

void HistoryHider::resizeEvent(QResizeEvent *e) {
	updateControlsGeometry();
}

void HistoryHider::updateControlsGeometry() {
	auto w = st::boxWidth;
	auto h = st::boxPadding.top() + st::boxPadding.bottom();
	if (_offered) {
		if (!_hiding) {
			_send->show();
			_cancel->show();
		}
		h += st::boxTopMargin + qMax(st::boxTextFont->height, st::boxLabelStyle.lineHeight) + st::boxButtonPadding.top() + _send->height() + st::boxButtonPadding.bottom();
	} else {
		h += st::historyForwardChooseFont->height;
		_send->hide();
		_cancel->hide();
	}
	_box = QRect((width() - w) / 2, (height() - h) / 2, w, h);
	_send->moveToRight(width() - (_box.x() + _box.width()) + st::boxButtonPadding.right(), _box.y() + _box.height() - st::boxButtonPadding.bottom() - _send->height());
	_cancel->moveToRight(width() - (_box.x() + _box.width()) + st::boxButtonPadding.right() + _send->width() + st::boxButtonPadding.left(), _send->y());
}

bool HistoryHider::offerPeer(PeerId peer) {
	if (!peer) {
		_offered = nullptr;
		_toText.setText(st::boxLabelStyle, QString());
		_toTextWidth = 0;
		resizeEvent(nullptr);
		return false;
	}
	_offered = App::peer(peer);
	auto phrase = QString();
	auto recipient = _offered->isUser() ? _offered->name : '\xAB' + _offered->name + '\xBB';
	if (_sendPath) {
		auto toId = _offered->id;
		_offered = nullptr;
		if (parent()->onSendPaths(toId)) {
			startHide();
		}
		return false;
	} else if (!_shareUrl.isEmpty()) {
		auto offered = base::take(_offered);
		if (parent()->shareUrl(offered, _shareUrl, _shareText)) {
			startHide();
		}
		return false;
	} else if (!_botAndQuery.isEmpty()) {
		auto toId = _offered->id;
		_offered = nullptr;
		if (parent()->onInlineSwitchChosen(toId, _botAndQuery)) {
			startHide();
		}
		return false;
	} else {
		auto toId = _offered->id;
		_offered = nullptr;
		if (parent()->setForwardDraft(toId, std::move(_forwardItems))) {
			startHide();
		}
		return false;
	}

	_toText.setText(st::boxLabelStyle, phrase, _textNameOptions);
	_toTextWidth = _toText.maxWidth();
	if (_toTextWidth > _box.width() - st::boxPadding.left() - st::boxLayerButtonPadding.right()) {
		_toTextWidth = _box.width() - st::boxPadding.left() - st::boxLayerButtonPadding.right();
	}

	resizeEvent(nullptr);
	update();
	setFocus();

	return true;
}

QString HistoryHider::offeredText() const {
	return _toText.originalText();
}

bool HistoryHider::wasOffered() const {
	return _offered != nullptr;
}

HistoryHider::~HistoryHider() {
	if (_sendPath) cSetSendPaths(QStringList());
	parent()->noHider(this);
}

HistoryWidget::HistoryWidget(QWidget *parent, not_null<Window::Controller*> controller) : Window::AbstractSectionWidget(parent, controller)
, _fieldBarCancel(this, st::historyReplyCancel)
, _topBar(this, controller)
, _scroll(this, st::historyScroll, false)
, _historyDown(_scroll, st::historyToDown)
, _unreadMentions(_scroll, st::historyUnreadMentions)
, _fieldAutocomplete(this)
, _send(this)
, _unblock(this, lang(lng_unblock_button).toUpper(), st::historyUnblock)
, _botStart(this, lang(lng_bot_start).toUpper(), st::historyComposeButton)
, _joinChannel(this, lang(lng_profile_join_channel).toUpper(), st::historyComposeButton)
, _muteUnmute(this, lang(lng_channel_mute).toUpper(), st::historyComposeButton)
, _attachToggle(this, st::historyAttach)
, _tabbedSelectorToggle(this, st::historyAttachEmoji)
, _botKeyboardShow(this, st::historyBotKeyboardShow)
, _botKeyboardHide(this, st::historyBotKeyboardHide)
, _botCommandStart(this, st::historyBotCommandStart)
, _field(this, controller, st::historyComposeField, langFactory(lng_message_ph))
, _recordCancelWidth(st::historyRecordFont->width(lang(lng_record_cancel)))
, _a_recording(animation(this, &HistoryWidget::step_recording))
, _kbScroll(this, st::botKbScroll)
, _tabbedPanel(this, controller)
, _tabbedSelector(_tabbedPanel->getSelector())
, _attachDragDocument(this)
, _attachDragPhoto(this)
, _fileLoader(this, FileLoaderQueueStopTimeout)
, _sendActionStopTimer([this] { cancelTypingAction(); })
, _topShadow(this) {
	setAcceptDrops(true);

	subscribe(Auth().downloaderTaskFinished(), [this] { update(); });
	connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll()));
	_historyDown->setClickedCallback([this] { historyDownClicked(); });
	_unreadMentions->setClickedCallback([this] { showNextUnreadMention(); });
	connect(_fieldBarCancel, SIGNAL(clicked()), this, SLOT(onFieldBarCancel()));
	_send->setClickedCallback([this] { sendButtonClicked(); });
	connect(_unblock, SIGNAL(clicked()), this, SLOT(onUnblock()));
	connect(_botStart, SIGNAL(clicked()), this, SLOT(onBotStart()));
	connect(_joinChannel, SIGNAL(clicked()), this, SLOT(onJoinChannel()));
	connect(_muteUnmute, SIGNAL(clicked()), this, SLOT(onMuteUnmute()));
	connect(_field, SIGNAL(submitted(bool)), this, SLOT(onSend(bool)));
	connect(_field, SIGNAL(cancelled()), this, SLOT(onCancel()));
	connect(_field, SIGNAL(tabbed()), this, SLOT(onFieldTabbed()));
	connect(_field, SIGNAL(resized()), this, SLOT(onFieldResize()));
	connect(_field, SIGNAL(focused()), this, SLOT(onFieldFocused()));
	connect(_field, SIGNAL(changed()), this, SLOT(onTextChange()));
	connect(_field, SIGNAL(spacedReturnedPasted()), this, SLOT(onPreviewParse()));
	connect(_field, SIGNAL(linksChanged()), this, SLOT(onPreviewCheck()));
	connect(App::wnd()->windowHandle(), SIGNAL(visibleChanged(bool)), this, SLOT(onWindowVisibleChanged()));
	connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer()));
	connect(_tabbedSelector, SIGNAL(emojiSelected(EmojiPtr)), _field, SLOT(onEmojiInsert(EmojiPtr)));
	connect(_tabbedSelector, SIGNAL(stickerSelected(DocumentData*)), this, SLOT(onStickerSend(DocumentData*)));
	connect(_tabbedSelector, SIGNAL(photoSelected(PhotoData*)), this, SLOT(onPhotoSend(PhotoData*)));
	connect(_tabbedSelector, SIGNAL(inlineResultSelected(InlineBots::Result*,UserData*)), this, SLOT(onInlineResultSend(InlineBots::Result*,UserData*)));
	connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreviewTimeout()));
	connect(Media::Capture::instance(), SIGNAL(error()), this, SLOT(onRecordError()));
	connect(Media::Capture::instance(), SIGNAL(updated(quint16,qint32)), this, SLOT(onRecordUpdate(quint16,qint32)));
	connect(Media::Capture::instance(), SIGNAL(done(QByteArray,VoiceWaveform,qint32)), this, SLOT(onRecordDone(QByteArray,VoiceWaveform,qint32)));

	_attachToggle->setClickedCallback(App::LambdaDelayed(st::historyAttach.ripple.hideDuration, this, [this] {
		chooseAttach();
	}));

	_updateHistoryItems.setSingleShot(true);
	connect(&_updateHistoryItems, SIGNAL(timeout()), this, SLOT(onUpdateHistoryItems()));

	_scrollTimer.setSingleShot(false);

	_highlightTimer.setCallback([this] { updateHighlightedMessage(); });

	_membersDropdownShowTimer.setSingleShot(true);
	connect(&_membersDropdownShowTimer, SIGNAL(timeout()), this, SLOT(onMembersDropdownShow()));

	_saveDraftTimer.setSingleShot(true);
	connect(&_saveDraftTimer, SIGNAL(timeout()), this, SLOT(onDraftSave()));
	_saveCloudDraftTimer.setSingleShot(true);
	connect(&_saveCloudDraftTimer, SIGNAL(timeout()), this, SLOT(onCloudDraftSave()));
	connect(_field->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onDraftSaveDelayed()));
	connect(_field, SIGNAL(cursorPositionChanged()), this, SLOT(onDraftSaveDelayed()));
	connect(_field, SIGNAL(cursorPositionChanged()), this, SLOT(onCheckFieldAutocomplete()), Qt::QueuedConnection);

	_fieldBarCancel->hide();

	_topBar->hide();
	_scroll->hide();

	_keyboard = _kbScroll->setOwnedWidget(object_ptr<BotKeyboard>(this));
	_kbScroll->hide();

	updateScrollColors();

	_historyDown->installEventFilter(this);
	_unreadMentions->installEventFilter(this);

	_fieldAutocomplete->hide();
	connect(_fieldAutocomplete, SIGNAL(mentionChosen(UserData*,FieldAutocomplete::ChooseMethod)), this, SLOT(onMentionInsert(UserData*)));
	connect(_fieldAutocomplete, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)), this, SLOT(onHashtagOrBotCommandInsert(QString,FieldAutocomplete::ChooseMethod)));
	connect(_fieldAutocomplete, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod)), this, SLOT(onHashtagOrBotCommandInsert(QString,FieldAutocomplete::ChooseMethod)));
	connect(_fieldAutocomplete, SIGNAL(stickerChosen(DocumentData*,FieldAutocomplete::ChooseMethod)), this, SLOT(onStickerSend(DocumentData*)));
	connect(_fieldAutocomplete, SIGNAL(moderateKeyActivate(int,bool*)), this, SLOT(onModerateKeyActivate(int,bool*)));
	_field->installEventFilter(_fieldAutocomplete);
	_field->setInsertFromMimeDataHook([this](const QMimeData *data) {
		return confirmSendingFiles(data, CompressConfirm::Auto, data->text());
	});
	_emojiSuggestions.create(this, _field.data());
	updateFieldSubmitSettings();

	_field->hide();
	_send->hide();
	_unblock->hide();
	_botStart->hide();
	_joinChannel->hide();
	_muteUnmute->hide();

	_send->setRecordStartCallback([this] { recordStartCallback(); });
	_send->setRecordStopCallback([this](bool active) { recordStopCallback(active); });
	_send->setRecordUpdateCallback([this](QPoint globalPos) { recordUpdateCallback(globalPos); });
	_send->setRecordAnimationCallback([this] { updateField(); });

	_attachToggle->hide();
	_tabbedSelectorToggle->hide();
	_botKeyboardShow->hide();
	_botKeyboardHide->hide();
	_botCommandStart->hide();

	_tabbedSelectorToggle->installEventFilter(_tabbedPanel);
	_tabbedSelectorToggle->setClickedCallback([this] {
		toggleTabbedSelectorMode();
	});

	connect(_botKeyboardShow, SIGNAL(clicked()), this, SLOT(onKbToggle()));
	connect(_botKeyboardHide, SIGNAL(clicked()), this, SLOT(onKbToggle()));
	connect(_botCommandStart, SIGNAL(clicked()), this, SLOT(onCmdStart()));

	_tabbedPanel->hide();
	_attachDragDocument->hide();
	_attachDragPhoto->hide();

	_topShadow->hide();

	_attachDragDocument->setDroppedCallback([this](const QMimeData *data) { confirmSendingFiles(data, CompressConfirm::No); });
	_attachDragPhoto->setDroppedCallback([this](const QMimeData *data) { confirmSendingFiles(data, CompressConfirm::Yes); });

	connect(&_updateEditTimeLeftDisplay, SIGNAL(timeout()), this, SLOT(updateField()));

	subscribe(Adaptive::Changed(), [this] { update(); });
	Auth().data().itemRemoved()
		| rpl::start_with_next(
			[this](auto item) { itemRemoved(item); },
			lifetime());
	Auth().data().itemRepaintRequest()
		| rpl::start_with_next(
			[this](auto item) { repaintHistoryItem(item); },
			lifetime());
	subscribe(Auth().data().contactsLoaded(), [this](bool) {
		if (_peer) {
			updateReportSpamStatus();
			updateControlsVisibility();
		}
	});
	subscribe(Media::Player::instance()->switchToNextNotifier(), [this](const Media::Player::Instance::Switch &pair) {
		if (pair.from.type() == AudioMsgId::Type::Voice) {
			scrollToCurrentVoiceMessage(pair.from.contextId(), pair.to);
		}
	});
	using UpdateFlag = Notify::PeerUpdate::Flag;
	auto changes = UpdateFlag::ChannelRightsChanged
		| UpdateFlag::UnreadMentionsChanged
		| UpdateFlag::MigrationChanged
		| UpdateFlag::RestrictionReasonChanged
		| UpdateFlag::ChannelPinnedChanged
		| UpdateFlag::UserIsBlocked
		| UpdateFlag::AdminsChanged
		| UpdateFlag::MembersChanged
		| UpdateFlag::UserOnlineChanged
		| UpdateFlag::NotificationsEnabled
		| UpdateFlag::ChannelAmIn;
	subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(changes, [this](const Notify::PeerUpdate &update) {
		if (update.peer == _peer) {
			if (update.flags & UpdateFlag::ChannelRightsChanged) {
				onPreviewCheck();
			}
			if (update.flags & UpdateFlag::UnreadMentionsChanged) {
				updateUnreadMentionsVisibility();
			}
			if (update.flags & UpdateFlag::MigrationChanged) {
				if (auto channel = _peer->migrateTo()) {
					Ui::showPeerHistory(channel, ShowAtUnreadMsgId);
					Auth().api().requestParticipantsCountDelayed(channel);
					return;
				}
			}
			if (update.flags & UpdateFlag::NotificationsEnabled) {
				updateNotifySettings();
			}
			if (update.flags & UpdateFlag::RestrictionReasonChanged) {
				auto restriction = _peer->restrictionReason();
				if (!restriction.isEmpty()) {
					this->controller()->showBackFromStack();
					Ui::show(Box<InformBox>(restriction));
					return;
				}
			}
			if (update.flags & UpdateFlag::ChannelPinnedChanged) {
				if (pinnedMsgVisibilityUpdated()) {
					updateHistoryGeometry();
					updateControlsVisibility();
					updateControlsGeometry();
					this->update();
				}
			}
			if (update.flags & (UpdateFlag::UserIsBlocked
				| UpdateFlag::AdminsChanged
				| UpdateFlag::MembersChanged
				| UpdateFlag::UserOnlineChanged
				| UpdateFlag::ChannelAmIn)) {
				handlePeerUpdate();
			}
		}
	}));
	subscribe(Auth().data().pendingHistoryResize(), [this] { handlePendingHistoryUpdate(); });
	subscribe(Auth().data().queryItemVisibility(), [this](const AuthSessionData::ItemVisibilityQuery &query) {
		if (_a_show.animating() || _history != query.item->history() || query.item->detached() || !isVisible()) {
			return;
		}
		auto top = _list->itemTop(query.item);
		if (top >= 0) {
			auto scrollTop = _scroll->scrollTop();
			if (top + query.item->height() > scrollTop && top < scrollTop + _scroll->height()) {
				*query.isVisible = true;
			}
		}
	});
	Auth().data().itemLayoutChanged()
		| rpl::start_with_next([this](auto item) {
			if (_peer && _list) {
				if ((item == App::mousedItem())
					|| (item == App::hoveredItem())
					|| (item == App::hoveredLinkItem())) {
					_list->onUpdateSelected();
				}
			}
		}, lifetime());
	_topBar->membersShowAreaActive()
		| rpl::start_with_next([this](bool active) {
			setMembersShowAreaActive(active);
		}, _topBar->lifetime());

	Auth().api().sendActions(
	) | rpl::start_with_next([this](const ApiWrap::SendOptions &options) {
		fastShowAtEnd(options.history);
		const auto lastKeyboardUsed = lastForceReplyReplied(FullMsgId(
			options.history->channelId(),
			options.replyTo));
		if (cancelReply(lastKeyboardUsed) && !options.clearDraft) {
			onCloudDraftSave();
		}
	}, lifetime());

	orderWidgets();
}

void HistoryWidget::scrollToCurrentVoiceMessage(FullMsgId fromId, FullMsgId toId) {
	if (getms() <= _lastUserScrolled + kScrollToVoiceAfterScrolledMs) {
		return;
	}
	if (!_list) {
		return;
	}

	auto from = App::histItemById(fromId);
	auto to = App::histItemById(toId);
	if (!from || !to) {
		return;
	}

	// If history has pending resize items, the scrollTopItem won't be updated.
	// And the scrollTop will be reset back to scrollTopItem + scrollTopOffset.
	handlePendingHistoryUpdate();

	auto toTop = _list->itemTop(to);
	if (toTop >= 0 && !isItemCompletelyHidden(from)) {
		auto scrollTop = _scroll->scrollTop();
		auto scrollBottom = scrollTop + _scroll->height();
		auto toBottom = toTop + to->height();
		if ((toTop < scrollTop && toBottom < scrollBottom) || (toTop > scrollTop && toBottom > scrollBottom)) {
			animatedScrollToItem(to->id);
		}
	}
}

void HistoryWidget::animatedScrollToItem(MsgId msgId) {
	Expects(_history != nullptr);
	if (hasPendingResizedItems()) {
		updateListSize();
	}

	auto to = App::histItemById(_channel, msgId);
	if (_list->itemTop(to) < 0) {
		return;
	}

	auto scrollTo = snap(itemTopForHighlight(to), 0, _scroll->scrollTopMax());
	animatedScrollToY(scrollTo, to);
}

void HistoryWidget::animatedScrollToY(int scrollTo, HistoryItem *attachTo) {
	Expects(_history != nullptr);
	if (hasPendingResizedItems()) {
		updateListSize();
	}

	// Attach our scroll animation to some item.
	auto itemTop = _list->itemTop(attachTo);
	auto scrollTop = _scroll->scrollTop();
	if (itemTop < 0 && !_history->isEmpty()) {
		attachTo = _history->blocks.back()->items.back();
		itemTop = _list->itemTop(attachTo);
	}
	if (itemTop < 0 || (scrollTop == scrollTo)) {
		synteticScrollToY(scrollTo);
		return;
	}

	_scrollToAnimation.finish();
	auto maxAnimatedDelta = _scroll->height();
	auto transition = anim::sineInOut;
	if (scrollTo > scrollTop + maxAnimatedDelta) {
		scrollTop = scrollTo - maxAnimatedDelta;
		synteticScrollToY(scrollTop);
		transition = anim::easeOutCubic;
	} else if (scrollTo + maxAnimatedDelta < scrollTop) {
		scrollTop = scrollTo + maxAnimatedDelta;
		synteticScrollToY(scrollTop);
		transition = anim::easeOutCubic;
	} else {
		// In local showHistory() we forget current scroll state,
		// so we need to restore it synchronously, otherwise we may
		// jump to the bottom of history in some updateHistoryGeometry() call.
		synteticScrollToY(scrollTop);
	}
	_scrollToAnimation.start([this, itemId = attachTo->fullId()] { scrollToAnimationCallback(itemId); }, scrollTop - itemTop, scrollTo - itemTop, st::slideDuration, anim::sineInOut);
}

void HistoryWidget::scrollToAnimationCallback(FullMsgId attachToId) {
	auto itemTop = _list->itemTop(App::histItemById(attachToId));
	if (itemTop < 0) {
		_scrollToAnimation.finish();
	} else {
		synteticScrollToY(qRound(_scrollToAnimation.current()) + itemTop);
	}
	if (!_scrollToAnimation.animating()) {
		preloadHistoryByScroll();
		checkReplyReturns();
	}
}

void HistoryWidget::enqueueMessageHighlight(not_null<HistoryItem*> item) {
	if (const auto group = item->getFullGroup()) {
		item = group->leader;
	}
	auto enqueueMessageId = [this](MsgId universalId) {
		if (_highlightQueue.empty() && !_highlightTimer.isActive()) {
			highlightMessage(universalId);
		} else if (_highlightedMessageId != universalId
			&& !base::contains(_highlightQueue, universalId)) {
			_highlightQueue.push_back(universalId);
			checkNextHighlight();
		}
	};
	if (item->history() == _history) {
		enqueueMessageId(item->id);
	} else if (item->history() == _migrated) {
		enqueueMessageId(-item->id);
	}
}

void HistoryWidget::highlightMessage(MsgId universalMessageId) {
	_highlightStart = getms();
	_highlightedMessageId = universalMessageId;
	_highlightTimer.callEach(AnimationTimerDelta);

	adjustHighlightedMessageToMigrated();
}

void HistoryWidget::adjustHighlightedMessageToMigrated() {
	if (_history
		&& _highlightTimer.isActive()
		&& _highlightedMessageId > 0
		&& _migrated
		&& !_migrated->isEmpty()
		&& _migrated->loadedAtBottom()
		&& _migrated->blocks.back()->items.back()->isGroupMigrate()
		&& _list->historyTop() != _list->historyDrawTop()) {
		auto highlighted = App::histItemById(
			_history->channelId(),
			_highlightedMessageId);
		if (highlighted && highlighted->isGroupMigrate()) {
			_highlightedMessageId = -_migrated->blocks.back()->items.back()->id;
		}
	}
}

void HistoryWidget::checkNextHighlight() {
	if (_highlightTimer.isActive()) {
		return;
	}
	auto nextHighlight = [this] {
		while (!_highlightQueue.empty()) {
			auto msgId = _highlightQueue.front();
			_highlightQueue.pop_front();
			auto item = getItemFromHistoryOrMigrated(msgId);
			if (item && !item->detached()) {
				return msgId;
			}
		}
		return 0;
	}();
	if (!nextHighlight) {
		return;
	}
	highlightMessage(nextHighlight);
}

void HistoryWidget::updateHighlightedMessage() {
	auto item = getItemFromHistoryOrMigrated(_highlightedMessageId);
	if (!item || item->detached()) {
		return stopMessageHighlight();
	}
	auto duration = st::activeFadeInDuration + st::activeFadeOutDuration;
	if (getms() - _highlightStart > duration) {
		return stopMessageHighlight();
	}

	Auth().data().requestItemRepaint(item);
}

TimeMs HistoryWidget::highlightStartTime(not_null<const HistoryItem*> item) const {
	auto isHighlighted = [this](not_null<const HistoryItem*> item) {
		if (item->id == _highlightedMessageId) {
			return (item->history() == _history);
		} else if (item->id == -_highlightedMessageId) {
			return (item->history() == _migrated);
		}
		return false;
	};
	return (isHighlighted(item) && _highlightTimer.isActive())
		? _highlightStart
		: 0;
}

void HistoryWidget::stopMessageHighlight() {
	_highlightTimer.cancel();
	_highlightedMessageId = 0;
	checkNextHighlight();
}

void HistoryWidget::clearHighlightMessages() {
	_highlightQueue.clear();
	stopMessageHighlight();
}

int HistoryWidget::itemTopForHighlight(not_null<HistoryItem*> item) const {
	if (const auto group = item->getFullGroup()) {
		item = group->leader;
	}
	auto itemTop = _list->itemTop(item);
	Assert(itemTop >= 0);

	auto heightLeft = (_scroll->height() - item->height());
	if (heightLeft <= 0) {
		return itemTop;
	}
	return qMax(itemTop - (heightLeft / 2), 0);
}

void HistoryWidget::start() {
	Auth().data().stickersUpdated()
		| rpl::start_with_next([this] {
			_tabbedSelector->refreshStickers();
			updateStickersByEmoji();
		}, lifetime());
	updateRecentStickers();
	Auth().data().markSavedGifsUpdated();
	subscribe(Auth().api().fullPeerUpdated(), [this](PeerData *peer) {
		fullPeerUpdated(peer);
	});
}

void HistoryWidget::onMentionInsert(UserData *user) {
	QString replacement, entityTag;
	if (user->username.isEmpty()) {
		replacement = user->firstName;
		if (replacement.isEmpty()) {
			replacement = App::peerName(user);
		}
		entityTag = qsl("mention://user.")
			+ QString::number(user->bareId())
			+ '.'
			+ QString::number(user->accessHash());
	} else {
		replacement = '@' + user->username;
	}
	_field->insertTag(replacement, entityTag);
}

void HistoryWidget::onHashtagOrBotCommandInsert(
		QString str,
		FieldAutocomplete::ChooseMethod method) {
	if (!_peer) {
		return;
	}

	// Send bot command at once, if it was not inserted by pressing Tab.
	if (str.at(0) == '/' && method != FieldAutocomplete::ChooseMethod::ByTab) {
		App::sendBotCommand(_peer, nullptr, str, replyToId());
		App::main()->finishForwarding(_history);
		setFieldText(_field->getTextWithTagsPart(_field->textCursor().position()));
	} else {
		_field->insertTag(str);
	}
}

void HistoryWidget::updateInlineBotQuery() {
	UserData *bot = nullptr;
	QString inlineBotUsername;
	QString query = _field->getInlineBotQuery(&bot, &inlineBotUsername);
	if (inlineBotUsername != _inlineBotUsername) {
		_inlineBotUsername = inlineBotUsername;
		if (_inlineBotResolveRequestId) {
//			Notify::inlineBotRequesting(false);
			MTP::cancel(_inlineBotResolveRequestId);
			_inlineBotResolveRequestId = 0;
		}
		if (bot == Ui::LookingUpInlineBot) {
			_inlineBot = Ui::LookingUpInlineBot;
//			Notify::inlineBotRequesting(true);
			_inlineBotResolveRequestId = MTP::send(MTPcontacts_ResolveUsername(MTP_string(_inlineBotUsername)), rpcDone(&HistoryWidget::inlineBotResolveDone), rpcFail(&HistoryWidget::inlineBotResolveFail, _inlineBotUsername));
			return;
		}
	} else if (bot == Ui::LookingUpInlineBot) {
		if (_inlineBot == Ui::LookingUpInlineBot) {
			return;
		}
		bot = _inlineBot;
	}

	applyInlineBotQuery(bot, query);
}

void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) {
	if (bot) {
		if (_inlineBot != bot) {
			_inlineBot = bot;
			inlineBotChanged();
		}
		if (!_inlineResults) {
			_inlineResults.create(this, controller());
			_inlineResults->setResultSelectedCallback([this](InlineBots::Result *result, UserData *bot) {
				onInlineResultSend(result, bot);
			});
			updateControlsGeometry();
			orderWidgets();
		}
		_inlineResults->queryInlineBot(_inlineBot, _peer, query);
		if (!_fieldAutocomplete->isHidden()) {
			_fieldAutocomplete->hideAnimated();
		}
	} else {
		clearInlineBot();
	}
}

void HistoryWidget::orderWidgets() {
	if (_reportSpamPanel) {
		_reportSpamPanel->raise();
	}
	_topShadow->raise();
	if (_membersDropdown) {
		_membersDropdown->raise();
	}
	if (_inlineResults) {
		_inlineResults->raise();
	}
	if (_tabbedPanel) {
		_tabbedPanel->raise();
	}
	_emojiSuggestions->raise();
	if (_tabbedSelectorToggleTooltip) {
		_tabbedSelectorToggleTooltip->raise();
	}
	_attachDragDocument->raise();
	_attachDragPhoto->raise();
}

void HistoryWidget::setReportSpamStatus(DBIPeerReportSpamStatus status) {
	if (_reportSpamStatus == status) {
		return;
	}
	_reportSpamStatus = status;
	if (_reportSpamStatus == dbiprsShowButton || _reportSpamStatus == dbiprsReportSent) {
		Assert(_peer != nullptr);
		_reportSpamPanel.create(this);
		connect(_reportSpamPanel, SIGNAL(reportClicked()), this, SLOT(onReportSpamClicked()));
		connect(_reportSpamPanel, SIGNAL(hideClicked()), this, SLOT(onReportSpamHide()));
		connect(_reportSpamPanel, SIGNAL(clearClicked()), this, SLOT(onReportSpamClear()));
		_reportSpamPanel->setReported(_reportSpamStatus == dbiprsReportSent, _peer);
		_reportSpamPanel->show();
		orderWidgets();
		updateControlsGeometry();
	} else {
		_reportSpamPanel.destroy();
	}
}

void HistoryWidget::updateStickersByEmoji() {
	int len = 0;
	if (!_editMsgId) {
		auto &text = _field->getTextWithTags().text;
		if (auto emoji = Ui::Emoji::Find(text, &len)) {
			if (text.size() > len) {
				len = 0;
			} else {
				_fieldAutocomplete->showStickers(emoji);
			}
		}
	}
	if (!len) {
		_fieldAutocomplete->showStickers(nullptr);
	}
}

void HistoryWidget::onTextChange() {
	updateInlineBotQuery();
	updateStickersByEmoji();

	if (_history) {
		if (!_inlineBot
			&& !_editMsgId
			&& (_textUpdateEvents & TextUpdateEvent::SendTyping)) {
			updateSendAction(_history, SendAction::Type::Typing);
		}
	}

	updateSendButtonType();
	if (showRecordButton()) {
		_previewCancelled = false;
	}
	if (updateCmdStartShown()) {
		updateControlsVisibility();
		updateControlsGeometry();
	}

	_saveCloudDraftTimer.stop();
	if (!_peer || !(_textUpdateEvents & TextUpdateEvent::SaveDraft)) return;

	_saveDraftText = true;
	onDraftSave(true);
}

void HistoryWidget::onDraftSaveDelayed() {
	if (!_peer || !(_textUpdateEvents & TextUpdateEvent::SaveDraft)) return;
	if (!_field->textCursor().anchor() && !_field->textCursor().position() && !_field->verticalScrollBar()->value()) {
		if (!Local::hasDraftCursors(_peer->id)) {
			return;
		}
	}
	onDraftSave(true);
}

void HistoryWidget::onDraftSave(bool delayed) {
	if (!_peer) return;
	if (delayed) {
		auto ms = getms();
		if (!_saveDraftStart) {
			_saveDraftStart = ms;
			return _saveDraftTimer.start(SaveDraftTimeout);
		} else if (ms - _saveDraftStart < SaveDraftAnywayTimeout) {
			return _saveDraftTimer.start(SaveDraftTimeout);
		}
	}
	writeDrafts(nullptr, nullptr);
}

void HistoryWidget::saveFieldToHistoryLocalDraft() {
	if (!_history) return;

	if (_editMsgId) {
		_history->setEditDraft(std::make_unique<Data::Draft>(_field, _editMsgId, _previewCancelled, _saveEditMsgRequestId));
	} else {
		if (_replyToId || !_field->isEmpty()) {
			_history->setLocalDraft(std::make_unique<Data::Draft>(_field, _replyToId, _previewCancelled));
		} else {
			_history->clearLocalDraft();
		}
		_history->clearEditDraft();
	}
}

void HistoryWidget::onCloudDraftSave() {
	if (App::main()) {
		App::main()->saveDraftToCloud();
	}
}

void HistoryWidget::writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft) {
	Data::Draft *historyLocalDraft = _history ? _history->localDraft() : nullptr;
	if (!localDraft && _editMsgId) localDraft = &historyLocalDraft;

	bool save = _peer && (_saveDraftStart > 0);
	_saveDraftStart = 0;
	_saveDraftTimer.stop();
	if (_saveDraftText) {
		if (save) {
			Local::MessageDraft storedLocalDraft, storedEditDraft;
			if (localDraft) {
				if (*localDraft) {
					storedLocalDraft = Local::MessageDraft((*localDraft)->msgId, (*localDraft)->textWithTags, (*localDraft)->previewCancelled);
				}
			} else {
				storedLocalDraft = Local::MessageDraft(_replyToId, _field->getTextWithTags(), _previewCancelled);
			}
			if (editDraft) {
				if (*editDraft) {
					storedEditDraft = Local::MessageDraft((*editDraft)->msgId, (*editDraft)->textWithTags, (*editDraft)->previewCancelled);
				}
			} else if (_editMsgId) {
				storedEditDraft = Local::MessageDraft(_editMsgId, _field->getTextWithTags(), _previewCancelled);
			}
			Local::writeDrafts(_peer->id, storedLocalDraft, storedEditDraft);
			if (_migrated) {
				Local::writeDrafts(_migrated->peer->id, Local::MessageDraft(), Local::MessageDraft());
			}
		}
		_saveDraftText = false;
	}
	if (save) {
		MessageCursor localCursor, editCursor;
		if (localDraft) {
			if (*localDraft) {
				localCursor = (*localDraft)->cursor;
			}
		} else {
			localCursor = MessageCursor(_field);
		}
		if (editDraft) {
			if (*editDraft) {
				editCursor = (*editDraft)->cursor;
			}
		} else if (_editMsgId) {
			editCursor = MessageCursor(_field);
		}
		Local::writeDraftCursors(_peer->id, localCursor, editCursor);
		if (_migrated) {
			Local::writeDraftCursors(_migrated->peer->id, MessageCursor(), MessageCursor());
		}
	}

	if (!_editMsgId && !_inlineBot) {
		_saveCloudDraftTimer.start(SaveCloudDraftIdleTimeout);
	}
}

void HistoryWidget::cancelSendAction(
		not_null<History*> history,
		SendAction::Type type) {
	auto i = _sendActionRequests.find(qMakePair(history, type));
	if (i != _sendActionRequests.cend()) {
		MTP::cancel(i.value());
		_sendActionRequests.erase(i);
	}
}

void HistoryWidget::cancelTypingAction() {
	if (_history) {
		cancelSendAction(_history, SendAction::Type::Typing);
	}
	_sendActionStopTimer.cancel();
}

void HistoryWidget::updateSendAction(
		not_null<History*> history,
		SendAction::Type type,
		int32 progress) {
	const auto peer = history->peer;
	if (peer->isSelf() || (peer->isChannel() && !peer->isMegagroup())) {
		return;
	}

	const auto doing = (progress >= 0);
	if (history->mySendActionUpdated(type, doing)) {
		cancelSendAction(history, type);
		if (doing) {
			using Type = SendAction::Type;
			MTPsendMessageAction action;
			switch (type) {
			case Type::Typing: action = MTP_sendMessageTypingAction(); break;
			case Type::RecordVideo: action = MTP_sendMessageRecordVideoAction(); break;
			case Type::UploadVideo: action = MTP_sendMessageUploadVideoAction(MTP_int(progress)); break;
			case Type::RecordVoice: action = MTP_sendMessageRecordAudioAction(); break;
			case Type::UploadVoice: action = MTP_sendMessageUploadAudioAction(MTP_int(progress)); break;
			case Type::RecordRound: action = MTP_sendMessageRecordRoundAction(); break;
			case Type::UploadRound: action = MTP_sendMessageUploadRoundAction(MTP_int(progress)); break;
			case Type::UploadPhoto: action = MTP_sendMessageUploadPhotoAction(MTP_int(progress)); break;
			case Type::UploadFile: action = MTP_sendMessageUploadDocumentAction(MTP_int(progress)); break;
			case Type::ChooseLocation: action = MTP_sendMessageGeoLocationAction(); break;
			case Type::ChooseContact: action = MTP_sendMessageChooseContactAction(); break;
			case Type::PlayGame: action = MTP_sendMessageGamePlayAction(); break;
			}
			const auto key = qMakePair(history, type);
			const auto requestId = MTP::send(
				MTPmessages_SetTyping(
					peer->input,
					action),
				rpcDone(&HistoryWidget::sendActionDone));
			_sendActionRequests.insert(key, requestId);
			if (type == Type::Typing) {
				_sendActionStopTimer.callOnce(kCancelTypingActionTimeout);
			}
		}
	}
}

void HistoryWidget::updateRecentStickers() {
	_tabbedSelector->refreshStickers();
}

void HistoryWidget::sendActionDone(const MTPBool &result, mtpRequestId req) {
	for (auto i = _sendActionRequests.begin(), e = _sendActionRequests.end(); i != e; ++i) {
		if (i.value() == req) {
			_sendActionRequests.erase(i);
			break;
		}
	}
}

void HistoryWidget::activate() {
	if (_history) {
		if (!_historyInited) {
			updateHistoryGeometry(true);
		} else if (hasPendingResizedItems()) {
			updateHistoryGeometry();
		}
	}
	if (App::wnd()) App::wnd()->setInnerFocus();
}

void HistoryWidget::setInnerFocus() {
	if (_scroll->isHidden()) {
		setFocus();
	} else if (_list) {
		if (_nonEmptySelection || (_list && _list->wasSelectedText()) || _recording || isBotStart() || isBlocked() || !_canSendMessages) {
			_list->setFocus();
		} else {
			_field->setFocus();
		}
	}
}

void HistoryWidget::onRecordError() {
	stopRecording(false);
}

void HistoryWidget::onRecordDone(QByteArray result, VoiceWaveform waveform, qint32 samples) {
	if (!canWriteMessage() || result.isEmpty()) return;

	App::wnd()->activateWindow();
	auto duration = samples / Media::Player::kDefaultFrequency;
	auto to = FileLoadTo(_peer->id, _peer->notifySilentPosts(), replyToId());
	auto caption = QString();
	_fileLoader.addTask(MakeShared<FileLoadTask>(result, duration, waveform, to, caption));
}

void HistoryWidget::onRecordUpdate(quint16 level, qint32 samples) {
	if (!_recording) {
		return;
	}

	a_recordingLevel.start(level);
	_a_recording.start();
	_recordingSamples = samples;
	if (samples < 0 || samples >= Media::Player::kDefaultFrequency * AudioVoiceMsgMaxLength) {
		stopRecording(_peer && samples > 0 && _inField);
	}
	updateField();
	if (_history) {
		updateSendAction(_history, SendAction::Type::RecordVoice);
	}
}

void HistoryWidget::notify_botCommandsChanged(UserData *user) {
	if (_peer && (_peer == user || !_peer->isUser())) {
		if (_fieldAutocomplete->clearFilteredBotCommands()) {
			onCheckFieldAutocomplete();
		}
	}
}

void HistoryWidget::notify_inlineBotRequesting(bool requesting) {
	_tabbedSelectorToggle->setLoading(requesting);
}

void HistoryWidget::notify_replyMarkupUpdated(const HistoryItem *item) {
	if (_keyboard->forMsgId() == item->fullId()) {
		updateBotKeyboard(item->history(), true);
	}
}

void HistoryWidget::notify_inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop) {
	if (_history == item->history() || _migrated == item->history()) {
		if (int move = _list->moveScrollFollowingInlineKeyboard(item, oldKeyboardTop, newKeyboardTop)) {
			_addToScroll = move;
		}
	}
}

bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo) {
	if (samePeerBot) {
		if (_history) {
			TextWithTags textWithTags = { '@' + samePeerBot->username + ' ' + query, TextWithTags::Tags() };
			MessageCursor cursor = { textWithTags.text.size(), textWithTags.text.size(), QFIXED_MAX };
			auto replyTo = _history->peer->isUser() ? 0 : samePeerReplyTo;
			_history->setLocalDraft(std::make_unique<Data::Draft>(textWithTags, replyTo, cursor, false));
			applyDraft();
			return true;
		}
	} else if (auto bot = _peer ? _peer->asUser() : nullptr) {
		PeerId toPeerId = bot->botInfo ? bot->botInfo->inlineReturnPeerId : 0;
		if (!toPeerId) {
			return false;
		}
		bot->botInfo->inlineReturnPeerId = 0;
		History *h = App::history(toPeerId);
		TextWithTags textWithTags = { '@' + bot->username + ' ' + query, TextWithTags::Tags() };
		MessageCursor cursor = { textWithTags.text.size(), textWithTags.text.size(), QFIXED_MAX };
		h->setLocalDraft(std::make_unique<Data::Draft>(textWithTags, 0, cursor, false));
		if (h == _history) {
			applyDraft();
		} else {
			Ui::showPeerHistory(toPeerId, ShowAtUnreadMsgId);
		}
		return true;
	}
	return false;
}

void HistoryWidget::notify_userIsBotChanged(UserData *user) {
	if (_peer && _peer == user) {
		_list->notifyIsBotChanged();
		_list->updateBotInfo();
		updateControlsVisibility();
		updateControlsGeometry();
	}
}

void HistoryWidget::notify_migrateUpdated(PeerData *peer) {
	if (_peer) {
		if (_peer == peer) {
			if (peer->migrateTo()) {
				showHistory(peer->migrateTo()->id, (_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId, true);
			} else if ((_migrated ? _migrated->peer : 0) != peer->migrateFrom()) {
				auto migrated = _history->migrateFrom();
				if (_migrated || (migrated && migrated->unreadCount() > 0)) {
					showHistory(peer->id, peer->migrateFrom() ? _showAtMsgId : ((_showAtMsgId < 0 && -_showAtMsgId < ServerMaxMsgId) ? ShowAtUnreadMsgId : _showAtMsgId), true);
				} else {
					_migrated = migrated;
					_list->notifyMigrateUpdated();
					updateHistoryGeometry();
				}
			}
		} else if (_migrated && _migrated->peer == peer && peer->migrateTo() != _peer) {
			showHistory(_peer->id, _showAtMsgId, true);
		}
	}
}

bool HistoryWidget::cmd_search() {
	if (!inFocusChain() || !_peer) return false;

	App::main()->searchInPeer(_peer);
	return true;
}

bool HistoryWidget::cmd_next_chat() {
	PeerData *p = 0;
	MsgId m = 0;
	App::main()->peerAfter(_peer, qMax(_showAtMsgId, 0), p, m);
	if (p) {
		Ui::showPeerHistory(p, m);
		return true;
	}
	return false;
}

bool HistoryWidget::cmd_previous_chat() {
	PeerData *p = 0;
	MsgId m = 0;
	App::main()->peerBefore(_peer, qMax(_showAtMsgId, 0), p, m);
	if (p) {
		Ui::showPeerHistory(p, m);
		return true;
	}
	return false;
}

void HistoryWidget::saveGif(DocumentData *doc) {
	if (doc->isGifv() && Auth().data().savedGifs().indexOf(doc) != 0) {
		MTPInputDocument mtpInput = doc->mtpInput();
		if (mtpInput.type() != mtpc_inputDocumentEmpty) {
			MTP::send(MTPmessages_SaveGif(mtpInput, MTP_bool(false)), rpcDone(&HistoryWidget::saveGifDone, doc));
		}
	}
}

void HistoryWidget::saveGifDone(DocumentData *doc, const MTPBool &result) {
	if (mtpIsTrue(result)) {
		App::addSavedGif(doc);
	}
}

void HistoryWidget::clearReplyReturns() {
	_replyReturns.clear();
	_replyReturn = 0;
}

void HistoryWidget::pushReplyReturn(HistoryItem *item) {
	if (!item) return;
	if (item->history() == _history) {
		_replyReturns.push_back(item->id);
	} else if (item->history() == _migrated) {
		_replyReturns.push_back(-item->id);
	} else {
		return;
	}
	_replyReturn = item;
	updateControlsVisibility();
}

QList<MsgId> HistoryWidget::replyReturns() {
	return _replyReturns;
}

void HistoryWidget::setReplyReturns(PeerId peer, const QList<MsgId> &replyReturns) {
	if (!_peer || _peer->id != peer) return;

	_replyReturns = replyReturns;
	if (_replyReturns.isEmpty()) {
		_replyReturn = 0;
	} else if (_replyReturns.back() < 0 && -_replyReturns.back() < ServerMaxMsgId) {
		_replyReturn = App::histItemById(0, -_replyReturns.back());
	} else {
		_replyReturn = App::histItemById(_channel, _replyReturns.back());
	}
	while (!_replyReturns.isEmpty() && !_replyReturn) {
		_replyReturns.pop_back();
		if (_replyReturns.isEmpty()) {
			_replyReturn = 0;
		} else if (_replyReturns.back() < 0 && -_replyReturns.back() < ServerMaxMsgId) {
			_replyReturn = App::histItemById(0, -_replyReturns.back());
		} else {
			_replyReturn = App::histItemById(_channel, _replyReturns.back());
		}
	}
}

void HistoryWidget::calcNextReplyReturn() {
	_replyReturn = 0;
	while (!_replyReturns.isEmpty() && !_replyReturn) {
		_replyReturns.pop_back();
		if (_replyReturns.isEmpty()) {
			_replyReturn = 0;
		} else if (_replyReturns.back() < 0 && -_replyReturns.back() < ServerMaxMsgId) {
			_replyReturn = App::histItemById(0, -_replyReturns.back());
		} else {
			_replyReturn = App::histItemById(_channel, _replyReturns.back());
		}
	}
	if (!_replyReturn) updateControlsVisibility();
}

void HistoryWidget::fastShowAtEnd(not_null<History*> history) {
	if (_history != history) {
		return;
	}

	clearAllLoadRequests();

	setMsgId(ShowAtUnreadMsgId);
	_historyInited = false;

	if (_history->isReadyFor(_showAtMsgId)) {
		historyLoaded();
	} else {
		firstLoadMessages();
		doneShow();
	}
}

void HistoryWidget::applyDraft(bool parseLinks, Ui::FlatTextarea::UndoHistoryAction undoHistoryAction) {
	auto draft = _history ? _history->draft() : nullptr;
	auto fieldAvailable = canWriteMessage();
	if (!draft || (!_history->editDraft() && !fieldAvailable)) {
		auto fieldWillBeHiddenAfterEdit = (!fieldAvailable && _editMsgId != 0);
		clearFieldText(0, undoHistoryAction);
		_field->setFocus();
		_replyEditMsg = nullptr;
		_editMsgId = _replyToId = 0;
		if (fieldWillBeHiddenAfterEdit) {
			updateControlsVisibility();
			updateControlsGeometry();
		}
		return;
	}

	_textUpdateEvents = 0;
	setFieldText(draft->textWithTags, 0, undoHistoryAction);
	_field->setFocus();
	draft->cursor.applyTo(_field);
	_textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping;
	_previewCancelled = draft->previewCancelled;
	_replyEditMsg = nullptr;
	if (auto editDraft = _history->editDraft()) {
		_editMsgId = editDraft->msgId;
		_replyToId = 0;
	} else {
		_editMsgId = 0;
		_replyToId = readyToForward() ? 0 : _history->localDraft()->msgId;
	}
	updateControlsVisibility();
	updateControlsGeometry();

	if (parseLinks) {
		onPreviewParse();
	}
	if (_editMsgId || _replyToId) {
		updateReplyEditTexts();
		if (!_replyEditMsg) {
			Auth().api().requestMessageData(
				_peer->asChannel(),
				_editMsgId ? _editMsgId : _replyToId,
				replyEditMessageDataCallback());
		}
	}
}

void HistoryWidget::applyCloudDraft(History *history) {
	if (_history == history && !_editMsgId) {
		applyDraft(true, Ui::FlatTextarea::AddToUndoHistory);

		updateControlsVisibility();
		updateControlsGeometry();
	}
}

void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool reload) {
	MsgId wasMsgId = _showAtMsgId;
	History *wasHistory = _history;

	bool startBot = (showAtMsgId == ShowAndStartBotMsgId);
	if (startBot) {
		showAtMsgId = ShowAtTheEndMsgId;
	}

	clearHighlightMessages();
	if (_history) {
		if (_peer->id == peerId && !reload) {
			updateForwarding();

			bool canShowNow = _history->isReadyFor(showAtMsgId);
			if (!canShowNow) {
				delayedShowAt(showAtMsgId);

				App::main()->dlgUpdated(wasHistory ? wasHistory->peer : nullptr, wasMsgId);
				emit historyShown(_history, _showAtMsgId);
			} else {
				_history->forgetScrollState();
				if (_migrated) {
					_migrated->forgetScrollState();
				}

				clearDelayedShowAt();
				if (_replyReturn) {
					if (_replyReturn->history() == _history && _replyReturn->id == showAtMsgId) {
						calcNextReplyReturn();
					} else if (_replyReturn->history() == _migrated && -_replyReturn->id == showAtMsgId) {
						calcNextReplyReturn();
					}
				}

				setMsgId(showAtMsgId);
				if (_historyInited) {
					countHistoryShowFrom();
					destroyUnreadBar();

					auto item = getItemFromHistoryOrMigrated(_showAtMsgId);
					animatedScrollToY(countInitialScrollTop(), item);
				} else {
					historyLoaded();
				}
			}

			_topBar->update();
			update();

			if (startBot && _peer->isUser() && _peer->asUser()->botInfo) {
				if (wasHistory) _peer->asUser()->botInfo->inlineReturnPeerId = wasHistory->peer->id;
				onBotStart();
				_history->clearLocalDraft();
				applyDraft();
				_send->finishAnimating();
			}
			return;
		}
		updateSendAction(_history, SendAction::Type::Typing, -1);
		cancelTypingAction();
	}

	if (!cAutoPlayGif()) {
		App::stopGifItems();
	}
	clearReplyReturns();

	clearAllLoadRequests();

	if (_history) {
		if (App::main()) App::main()->saveDraftToCloud();
		if (_migrated) {
			_migrated->clearLocalDraft(); // use migrated draft only once
			_migrated->clearEditDraft();
		}

		_history->showAtMsgId = _showAtMsgId;

		destroyUnreadBar();
		destroyPinnedBar();
		_membersDropdown.destroy();
		_scrollToAnimation.finish();
		_history = _migrated = nullptr;
		_peer = nullptr;
		_channel = NoChannel;
		_canSendMessages = false;
		_silent.destroy();
		updateBotKeyboard();
	}

	App::clearMousedItems();

	_addToScroll = 0;
	_saveEditMsgRequestId = 0;
	_replyEditMsg = nullptr;
	_editMsgId = _replyToId = 0;
	_previewData = nullptr;
	_previewCache.clear();
	_fieldBarCancel->hide();

	_membersDropdownShowTimer.stop();
	_scroll->takeWidget<HistoryInner>().destroyDelayed();
	_list = nullptr;

	clearInlineBot();

	_showAtMsgId = showAtMsgId;
	_historyInited = false;

	if (peerId) {
		_peer = App::peer(peerId);
		_channel = peerToChannel(_peer->id);
		_canSendMessages = _peer->canWrite();
		_tabbedSelector->setCurrentPeer(_peer);
	}
	_topBar->setHistoryPeer(_peer);
	updateTopBarSelection();

	if (_peer && _peer->isChannel()) {
		_peer->asChannel()->updateFull();
		_joinChannel->setText(lang(_peer->isMegagroup()
			? lng_profile_join_group
			: lng_profile_join_channel).toUpper());
	}

	_unblockRequest = _reportSpamRequest = 0;
	if (_reportSpamSettingRequestId > 0) {
		MTP::cancel(_reportSpamSettingRequestId);
	}
	_reportSpamSettingRequestId = ReportSpamRequestNeeded;

	noSelectingScroll();
	_nonEmptySelection = false;
	_topBar->showSelected(HistoryTopBarWidget::SelectedState {});

	App::hoveredItem(nullptr);
	App::pressedItem(nullptr);
	App::hoveredLinkItem(nullptr);
	App::pressedLinkItem(nullptr);
	App::contextItem(nullptr);
	App::mousedItem(nullptr);

	if (_peer) {
		App::forgetMedia();
		_serviceImageCacheSize = imageCacheSize();
		Auth().downloader().clearPriorities();

		_history = App::history(_peer);
		_migrated = _history->migrateFrom();

		if (_channel) {
			updateNotifySettings();
			if (_peer->notifySettingsUnknown()) {
				Auth().api().requestNotifySetting(_peer);
			}
			refreshSilentToggle();
		}

		if (_showAtMsgId == ShowAtUnreadMsgId) {
			if (_history->scrollTopItem) {
				_showAtMsgId = _history->showAtMsgId;
			}
		} else {
			_history->forgetScrollState();
			if (_migrated) {
				_migrated->forgetScrollState();
			}
		}

		_scroll->hide();
		_list = _scroll->setOwnedWidget(object_ptr<HistoryInner>(this, controller(), _scroll, _history));
		_list->show();

		_updateHistoryItems.stop();

		pinnedMsgVisibilityUpdated();
		if (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem) || _history->isReadyFor(_showAtMsgId)) {
			historyLoaded();
		} else {
			firstLoadMessages();
			doneShow();
		}

		handlePeerUpdate();

		Local::readDraftsWithCursors(_history);
		if (_migrated) {
			Local::readDraftsWithCursors(_migrated);
			_migrated->clearEditDraft();
			_history->takeLocalDraft(_migrated);
		}
		applyDraft(false);
		_send->finishAnimating();

		_tabbedSelector->showMegagroupSet(_peer->asMegagroup());

		updateControlsGeometry();
		if (!_previewCancelled) {
			onPreviewParse();
		}

		connect(_scroll, SIGNAL(geometryChanged()), _list, SLOT(onParentGeometryChanged()));

		if (startBot && _peer->isUser() && _peer->asUser()->botInfo) {
			if (wasHistory) _peer->asUser()->botInfo->inlineReturnPeerId = wasHistory->peer->id;
			onBotStart();
		}
		unreadCountChanged(_history); // set _historyDown badge.
	} else {
		clearFieldText();
		_tabbedSelector->showMegagroupSet(nullptr);
		doneShow();
	}
	updateForwarding();
	updateOverStates(mapFromGlobal(QCursor::pos()));

	if (App::wnd()) QTimer::singleShot(0, App::wnd(), SLOT(setInnerFocus()));

	App::main()->dlgUpdated(wasHistory ? wasHistory->peer : nullptr, wasMsgId);
	emit historyShown(_history, _showAtMsgId);

	controller()->historyPeer = _peer;
	if (_peer) {
		controller()->activePeer = _peer;
	}
	update();
}

void HistoryWidget::clearDelayedShowAt() {
	_delayedShowAtMsgId = -1;
	if (_delayedShowAtRequest) {
		MTP::cancel(_delayedShowAtRequest);
		_delayedShowAtRequest = 0;
	}
}

void HistoryWidget::clearAllLoadRequests() {
	clearDelayedShowAt();
	if (_firstLoadRequest) MTP::cancel(_firstLoadRequest);
	if (_preloadRequest) MTP::cancel(_preloadRequest);
	if (_preloadDownRequest) MTP::cancel(_preloadDownRequest);
	_preloadRequest = _preloadDownRequest = _firstLoadRequest = 0;
}

void HistoryWidget::updateFieldSubmitSettings() {
	auto settings = Ui::FlatTextarea::SubmitSettings::Enter;
	if (_isInlineBot) {
		settings = Ui::FlatTextarea::SubmitSettings::None;
	} else if (cCtrlEnter()) {
		settings = Ui::FlatTextarea::SubmitSettings::CtrlEnter;
	}
	_field->setSubmitSettings(settings);
}

void HistoryWidget::updateNotifySettings() {
	if (!_peer || !_peer->isChannel()) return;

	_muteUnmute->setText(lang(_history->mute()
		? lng_channel_unmute
		: lng_channel_mute).toUpper());
	if (!_peer->notifySettingsUnknown()) {
		if (_silent) {
			_silent->setChecked(_peer->notifySilentPosts());
		} else if (hasSilentToggle()) {
			refreshSilentToggle();
			updateControlsGeometry();
			updateControlsVisibility();
		}
	}
}

void HistoryWidget::refreshSilentToggle() {
	if (!_silent && hasSilentToggle()) {
		_silent.create(this, _peer->asChannel());
	} else if (_silent && !hasSilentToggle()) {
		_silent.destroy();
	}
}

bool HistoryWidget::contentOverlapped(const QRect &globalRect) {
	return (_attachDragDocument->overlaps(globalRect)
			|| _attachDragPhoto->overlaps(globalRect)
			|| _fieldAutocomplete->overlaps(globalRect)
			|| (_tabbedPanel && _tabbedPanel->overlaps(globalRect))
			|| (_inlineResults && _inlineResults->overlaps(globalRect)));
}

void HistoryWidget::updateReportSpamStatus() {
	if (!_peer || (_peer->isUser() && (_peer->id == Auth().userPeerId() || isNotificationsUser(_peer->id) || isServiceUser(_peer->id) || _peer->asUser()->botInfo))) {
		setReportSpamStatus(dbiprsHidden);
		return;
	} else if (!_firstLoadRequest && _history->isEmpty()) {
		setReportSpamStatus(dbiprsNoButton);
		if (cReportSpamStatuses().contains(_peer->id)) {
			cRefReportSpamStatuses().remove(_peer->id);
			Local::writeReportSpamStatuses();
		}
		return;
	} else {
		auto i = cReportSpamStatuses().constFind(_peer->id);
		if (i != cReportSpamStatuses().cend()) {
			if (i.value() == dbiprsNoButton) {
				setReportSpamStatus(dbiprsHidden);
				if (!_peer->isUser() || _peer->asUser()->contact < 1) {
					MTP::send(MTPmessages_HideReportSpam(_peer->input));
				}

				cRefReportSpamStatuses().insert(_peer->id, _reportSpamStatus);
				Local::writeReportSpamStatuses();
			} else {
				setReportSpamStatus(i.value());
				if (_reportSpamStatus == dbiprsShowButton) {
					requestReportSpamSetting();
				}
			}
			return;
		} else if (_peer->migrateFrom()) { // migrate report status
			i = cReportSpamStatuses().constFind(_peer->migrateFrom()->id);
			if (i != cReportSpamStatuses().cend()) {
				if (i.value() == dbiprsNoButton) {
					setReportSpamStatus(dbiprsHidden);
					if (!_peer->isUser() || _peer->asUser()->contact < 1) {
						MTP::send(MTPmessages_HideReportSpam(_peer->input));
					}
				} else {
					setReportSpamStatus(i.value());
					if (_reportSpamStatus == dbiprsShowButton) {
						requestReportSpamSetting();
					}
				}
				cRefReportSpamStatuses().insert(_peer->id, _reportSpamStatus);
				Local::writeReportSpamStatuses();
				return;
			}
		}
	}
	auto status = dbiprsRequesting;
	if (!Auth().data().contactsLoaded().value() || _firstLoadRequest) {
		status = dbiprsUnknown;
	} else if (_peer->isUser() && _peer->asUser()->contact > 0) {
		status = dbiprsHidden;
	} else {
		requestReportSpamSetting();
	}
	setReportSpamStatus(status);
	if (_reportSpamStatus == dbiprsHidden) {
		cRefReportSpamStatuses().insert(_peer->id, _reportSpamStatus);
		Local::writeReportSpamStatuses();
	}
}

void HistoryWidget::requestReportSpamSetting() {
	if (_reportSpamSettingRequestId >= 0 || !_peer) return;

	_reportSpamSettingRequestId = MTP::send(MTPmessages_GetPeerSettings(_peer->input), rpcDone(&HistoryWidget::reportSpamSettingDone), rpcFail(&HistoryWidget::reportSpamSettingFail));
}

void HistoryWidget::reportSpamSettingDone(const MTPPeerSettings &result, mtpRequestId req) {
	if (req != _reportSpamSettingRequestId) return;

	_reportSpamSettingRequestId = 0;
	if (result.type() == mtpc_peerSettings) {
		auto &d = result.c_peerSettings();
		auto status = d.is_report_spam() ? dbiprsShowButton : dbiprsHidden;
		if (status != _reportSpamStatus) {
			setReportSpamStatus(status);
			if (_reportSpamPanel) {
				_reportSpamPanel->setReported(false, _peer);
			}

			cRefReportSpamStatuses().insert(_peer->id, _reportSpamStatus);
			Local::writeReportSpamStatuses();

			updateControlsVisibility();
		}
	}
}

bool HistoryWidget::reportSpamSettingFail(const RPCError &error, mtpRequestId req) {
	if (MTP::isDefaultHandledError(error)) return false;

	if (req == _reportSpamSettingRequestId) {
		req = 0;
	}
	return true;
}

bool HistoryWidget::canWriteMessage() const {
	if (!_history || !_canSendMessages) return false;
	if (isBlocked() || isJoinChannel() || isMuteUnmute() || isBotStart()) return false;
	return true;
}

bool HistoryWidget::isRestrictedWrite() const {
	if (auto megagroup = _peer ? _peer->asMegagroup() : nullptr) {
		return megagroup->restricted(
			ChannelRestriction::f_send_messages);
	}
	return false;
}

void HistoryWidget::updateControlsVisibility() {
	if (!_a_show.animating()) {
		_topShadow->setVisible(_peer != nullptr);
		_topBar->setVisible(_peer != nullptr);
	}
	updateHistoryDownVisibility();
	updateUnreadMentionsVisibility();
	if (!_history || _a_show.animating()) {
		hideChildren();
		return;
	}

	if (_pinnedBar) {
		_pinnedBar->cancel->show();
		_pinnedBar->shadow->show();
	}
	if (_firstLoadRequest && !_scroll->isHidden()) {
		_scroll->hide();
	} else if (!_firstLoadRequest && _scroll->isHidden()) {
		_scroll->show();
	}
	if (_reportSpamPanel) {
		_reportSpamPanel->show();
	}
	if (!editingMessage() && (isBlocked() || isJoinChannel() || isMuteUnmute() || isBotStart())) {
		if (isBlocked()) {
			_joinChannel->hide();
			_muteUnmute->hide();
			_botStart->hide();
			if (_unblock->isHidden()) {
				_unblock->clearState();
				_unblock->show();
			}
		} else if (isJoinChannel()) {
			_unblock->hide();
			_muteUnmute->hide();
			_botStart->hide();
			if (_joinChannel->isHidden()) {
				_joinChannel->clearState();
				_joinChannel->show();
			}
		} else if (isMuteUnmute()) {
			_unblock->hide();
			_joinChannel->hide();
			_botStart->hide();
			if (_muteUnmute->isHidden()) {
				_muteUnmute->clearState();
				_muteUnmute->show();
			}
		} else if (isBotStart()) {
			_unblock->hide();
			_joinChannel->hide();
			_muteUnmute->hide();
			if (_botStart->isHidden()) {
				_botStart->clearState();
				_botStart->show();
			}
		}
		_kbShown = false;
		_fieldAutocomplete->hide();
		_send->hide();
		if (_silent) {
			_silent->hide();
		}
		_kbScroll->hide();
		_fieldBarCancel->hide();
		_attachToggle->hide();
		_tabbedSelectorToggle->hide();
		_botKeyboardShow->hide();
		_botKeyboardHide->hide();
		_botCommandStart->hide();
		if (_tabbedPanel) {
			_tabbedPanel->hide();
		}
		if (_inlineResults) {
			_inlineResults->hide();
		}
		if (!_field->isHidden()) {
			_field->hide();
			updateControlsGeometry();
			update();
		}
	} else if (editingMessage() || _canSendMessages) {
		onCheckFieldAutocomplete();
		_unblock->hide();
		_botStart->hide();
		_joinChannel->hide();
		_muteUnmute->hide();
		_send->show();
		updateSendButtonType();
		if (_recording) {
			_field->hide();
			_tabbedSelectorToggle->hide();
			_botKeyboardShow->hide();
			_botKeyboardHide->hide();
			_botCommandStart->hide();
			_attachToggle->hide();
			if (_silent) {
				_silent->hide();
			}
			if (_kbShown) {
				_kbScroll->show();
			} else {
				_kbScroll->hide();
			}
		} else {
			_field->show();
			if (_kbShown) {
				_kbScroll->show();
				_tabbedSelectorToggle->hide();
				_botKeyboardHide->show();
				_botKeyboardShow->hide();
				_botCommandStart->hide();
			} else if (_kbReplyTo) {
				_kbScroll->hide();
				_tabbedSelectorToggle->show();
				_botKeyboardHide->hide();
				_botKeyboardShow->hide();
				_botCommandStart->hide();
			} else {
				_kbScroll->hide();
				_tabbedSelectorToggle->show();
				_botKeyboardHide->hide();
				if (_keyboard->hasMarkup()) {
					_botKeyboardShow->show();
					_botCommandStart->hide();
				} else {
					_botKeyboardShow->hide();
					if (_cmdStartShown) {
						_botCommandStart->show();
					} else {
						_botCommandStart->hide();
					}
				}
			}
			_attachToggle->show();
			if (_silent) {
				_silent->show();
			}
			updateFieldPlaceholder();
		}
		if (_editMsgId || _replyToId || readyToForward() || (_previewData && _previewData->pendingTill >= 0) || _kbReplyTo) {
			if (_fieldBarCancel->isHidden()) {
				_fieldBarCancel->show();
				updateControlsGeometry();
				update();
			}
		} else {
			_fieldBarCancel->hide();
		}
	} else {
		_fieldAutocomplete->hide();
		_send->hide();
		_unblock->hide();
		_botStart->hide();
		_joinChannel->hide();
		_muteUnmute->hide();
		_attachToggle->hide();
		if (_silent) {
			_silent->hide();
		}
		_kbScroll->hide();
		_fieldBarCancel->hide();
		_attachToggle->hide();
		_tabbedSelectorToggle->hide();
		_botKeyboardShow->hide();
		_botKeyboardHide->hide();
		_botCommandStart->hide();
		if (_tabbedPanel) {
			_tabbedPanel->hide();
		}
		if (_inlineResults) {
			_inlineResults->hide();
		}
		_kbScroll->hide();
		if (!_field->isHidden()) {
			_field->hide();
			updateControlsGeometry();
			update();
		}
	}
	//checkTabbedSelectorToggleTooltip();
	updateMouseTracking();
}

void HistoryWidget::updateMouseTracking() {
	bool trackMouse = !_fieldBarCancel->isHidden() || _pinnedBar;
	setMouseTracking(trackMouse);
}

void HistoryWidget::destroyUnreadBar() {
	if (_history) _history->destroyUnreadBar();
	if (_migrated) _migrated->destroyUnreadBar();
}

void HistoryWidget::newUnreadMsg(History *history, HistoryItem *item) {
	if (_history == history) {
		if (_scroll->scrollTop() + 1 > _scroll->scrollTopMax()) {
			destroyUnreadBar();
		}
		if (App::wnd()->doWeReadServerHistory()) {
			if (item->mentionsMe() && item->isMediaUnread()) {
				App::main()->mediaMarkRead(item);
			}
			Auth().api().readServerHistoryForce(history);
			return;
		}
	}
	Auth().notifications().schedule(history, item);
	history->setUnreadCount(history->unreadCount() + 1);
}

void HistoryWidget::historyToDown(History *history) {
	history->forgetScrollState();
	if (auto migrated = App::historyLoaded(history->peer->migrateFrom())) {
		migrated->forgetScrollState();
	}
	if (history == _history) {
		synteticScrollToY(_scroll->scrollTopMax());
	}
}

void HistoryWidget::unreadCountChanged(History *history) {
	if (history == _history || history == _migrated) {
		updateHistoryDownVisibility();
		_historyDown->setUnreadCount(_history->unreadCount() + (_migrated ? _migrated->unreadCount() : 0));
	}
}

bool HistoryWidget::messagesFailed(const RPCError &error, mtpRequestId requestId) {
	if (MTP::isDefaultHandledError(error)) return false;

	if (error.type() == qstr("CHANNEL_PRIVATE") || error.type() == qstr("CHANNEL_PUBLIC_GROUP_NA") || error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
		auto was = _peer;
		controller()->showBackFromStack();
		Ui::show(Box<InformBox>(lang((was && was->isMegagroup()) ? lng_group_not_accessible : lng_channel_not_accessible)));
		return true;
	}

	LOG(("RPC Error: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description()));
	if (_preloadRequest == requestId) {
		_preloadRequest = 0;
	} else if (_preloadDownRequest == requestId) {
		_preloadDownRequest = 0;
	} else if (_firstLoadRequest == requestId) {
		_firstLoadRequest = 0;
		controller()->showBackFromStack();
	} else if (_delayedShowAtRequest == requestId) {
		_delayedShowAtRequest = 0;
	}
	return true;
}

void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages &messages, mtpRequestId requestId) {
	if (!_history) {
		_preloadRequest = _preloadDownRequest = _firstLoadRequest = _delayedShowAtRequest = 0;
		return;
	}

	bool toMigrated = (peer == _peer->migrateFrom());
	if (peer != _peer && !toMigrated) {
		_preloadRequest = _preloadDownRequest = _firstLoadRequest = _delayedShowAtRequest = 0;
		return;
	}

	auto count = 0;
	const QVector<MTPMessage> emptyList, *histList = &emptyList;
	switch (messages.type()) {
	case mtpc_messages_messages: {
		auto &d(messages.c_messages_messages());
		App::feedUsers(d.vusers);
		App::feedChats(d.vchats);
		histList = &d.vmessages.v;
		count = histList->size();
	} break;
	case mtpc_messages_messagesSlice: {
		auto &d(messages.c_messages_messagesSlice());
		App::feedUsers(d.vusers);
		App::feedChats(d.vchats);
		histList = &d.vmessages.v;
		count = d.vcount.v;
	} break;
	case mtpc_messages_channelMessages: {
		auto &d(messages.c_messages_channelMessages());
		if (peer && peer->isChannel()) {
			peer->asChannel()->ptsReceived(d.vpts.v);
		} else {
			LOG(("API Error: received messages.channelMessages when no channel was passed! (HistoryWidget::messagesReceived)"));
		}
		App::feedUsers(d.vusers);
		App::feedChats(d.vchats);
		histList = &d.vmessages.v;
		count = d.vcount.v;
	} break;
	case mtpc_messages_messagesNotModified: {
		LOG(("API Error: received messages.messagesNotModified! (HistoryWidget::messagesReceived)"));
	} break;
	}

	const auto ExtractFirstId = [&] {
		return histList->empty() ? -1 : idFromMessage(histList->front());
	};
	const auto ExtractLastId = [&] {
		return histList->empty() ? -1 : idFromMessage(histList->back());
	};
	const auto PeerString = [](PeerId peerId) {
		if (peerIsUser(peerId)) {
			return QString("User-%1").arg(peerToUser(peerId));
		} else if (peerIsChat(peerId)) {
			return QString("Chat-%1").arg(peerToChat(peerId));
		} else if (peerIsChannel(peerId)) {
			return QString("Channel-%1").arg(peerToChannel(peerId));
		}
		return QString("Bad-%1").arg(peerId);
	};

	if (_preloadRequest == requestId) {
		auto to = toMigrated ? _migrated : _history;
		if (cBetaVersion()) {
			CrashReports::SetAnnotation("old_debugstr", QString(
				"%1_%2_%3_%4:%5_%6 (%7)"
			).arg(PeerString(_debug_preloadDownPeer)
			).arg(_debug_preloadOffsetId
			).arg(_debug_preloadAddOffset
			).arg(_debug_preloadLoadCount
			).arg(ExtractFirstId()
			).arg(ExtractLastId()
			).arg(Auth().userId()
			));
		}

		addMessagesToFront(peer, *histList);

		if (cBetaVersion()) {
			CrashReports::ClearAnnotation("old_debugstr");
		}

		_preloadRequest = 0;
		preloadHistoryIfNeeded();
		if (_reportSpamStatus == dbiprsUnknown) {
			updateReportSpamStatus();
			if (_reportSpamStatus != dbiprsUnknown) updateControlsVisibility();
		}
	} else if (_preloadDownRequest == requestId) {
		auto to = toMigrated ? _migrated : _history;
		if (cBetaVersion()) {
			CrashReports::SetAnnotation("new_debugstr", QString(
				"%1_%2_%3_%4:%5_%6 (%7)"
			).arg(PeerString(_debug_preloadDownPeer)
			).arg(_debug_preloadDownOffsetId
			).arg(_debug_preloadDownAddOffset
			).arg(_debug_preloadDownLoadCount
			).arg(ExtractFirstId()
			).arg(ExtractLastId()
			).arg(Auth().userId()
			));
		}

		addMessagesToBack(peer, *histList);

		if (cBetaVersion()) {
			CrashReports::ClearAnnotation("new_debugstr");
		}

		_preloadDownRequest = 0;
		preloadHistoryIfNeeded();
		if (_history->loadedAtBottom() && App::wnd()) App::wnd()->checkHistoryActivation();
	} else if (_firstLoadRequest == requestId) {
		if (toMigrated) {
			_history->clear(true);
		} else if (_migrated) {
			_migrated->clear(true);
		}
		addMessagesToFront(peer, *histList);
		_firstLoadRequest = 0;
		if (_history->loadedAtTop()) {
			if (_history->unreadCount() > count) {
				_history->setUnreadCount(count);
			}
			if (_history->isEmpty() && count > 0) {
				firstLoadMessages();
				return;
			}
		}

		historyLoaded();
	} else if (_delayedShowAtRequest == requestId) {
		if (toMigrated) {
			_history->clear(true);
		} else if (_migrated) {
			_migrated->clear(true);
		}

		_delayedShowAtRequest = 0;
		_history->getReadyFor(_delayedShowAtMsgId);
		if (_history->isEmpty()) {
			if (_preloadRequest) MTP::cancel(_preloadRequest);
			if (_preloadDownRequest) MTP::cancel(_preloadDownRequest);
			if (_firstLoadRequest) MTP::cancel(_firstLoadRequest);
			_preloadRequest = _preloadDownRequest = 0;
			_firstLoadRequest = -1; // hack - don't updateListSize yet
			addMessagesToFront(peer, *histList);
			_firstLoadRequest = 0;
			if (_history->loadedAtTop()) {
				if (_history->unreadCount() > count) {
					_history->setUnreadCount(count);
				}
				if (_history->isEmpty() && count > 0) {
					firstLoadMessages();
					return;
				}
			}
		}
		if (_replyReturn) {
			if (_replyReturn->history() == _history && _replyReturn->id == _delayedShowAtMsgId) {
				calcNextReplyReturn();
			} else if (_replyReturn->history() == _migrated && -_replyReturn->id == _delayedShowAtMsgId) {
				calcNextReplyReturn();
			}
		}

		setMsgId(_delayedShowAtMsgId);

		_historyInited = false;
		historyLoaded();
	}
}

void HistoryWidget::historyLoaded() {
	countHistoryShowFrom();
	destroyUnreadBar();
	doneShow();
}

void HistoryWidget::windowShown() {
	updateControlsGeometry();
}

bool HistoryWidget::doWeReadServerHistory() const {
	if (!_history || !_list) return true;
	if (_firstLoadRequest || _a_show.animating()) return false;
	if (_history->loadedAtBottom()) {
		int scrollTop = _scroll->scrollTop();
		if (scrollTop + 1 > _scroll->scrollTopMax()) return true;

		auto showFrom = (_migrated && _migrated->showFrom) ? _migrated->showFrom : (_history ? _history->showFrom : nullptr);
		if (showFrom && !showFrom->detached()) {
			int scrollBottom = scrollTop + _scroll->height();
			if (scrollBottom > _list->itemTop(showFrom)) return true;
		}
	}
	if (historyHasNotFreezedUnreadBar(_history)) {
		return true;
	}
	if (historyHasNotFreezedUnreadBar(_migrated)) {
		return true;
	}
	return false;
}

bool HistoryWidget::doWeReadMentions() const {
	if (!_history || !_list) return true;
	if (_firstLoadRequest || _a_show.animating()) return false;
	return true;
}

bool HistoryWidget::historyHasNotFreezedUnreadBar(History *history) const {
	if (history && history->showFrom && !history->showFrom->detached() && history->unreadBar) {
		if (auto unreadBar = history->unreadBar->Get<HistoryMessageUnreadBar>()) {
			return !unreadBar->_freezed;
		}
	}
	return false;
}

void HistoryWidget::firstLoadMessages() {
	if (!_history || _firstLoadRequest) return;

	auto from = _peer;
	auto offsetId = 0;
	auto offset = 0;
	auto loadCount = kMessagesPerPage;
	if (_showAtMsgId == ShowAtUnreadMsgId) {
		if (_migrated && _migrated->unreadCount()) {
			_history->getReadyFor(_showAtMsgId);
			from = _migrated->peer;
			offset = -loadCount / 2;
			offsetId = _migrated->inboxReadBefore;
		} else if (_history->unreadCount()) {
			_history->getReadyFor(_showAtMsgId);
			offset = -loadCount / 2;
			offsetId = _history->inboxReadBefore;
		} else {
			_history->getReadyFor(ShowAtTheEndMsgId);
		}
	} else if (_showAtMsgId == ShowAtTheEndMsgId) {
		_history->getReadyFor(_showAtMsgId);
		loadCount = kMessagesPerPageFirst;
	} else if (_showAtMsgId > 0) {
		_history->getReadyFor(_showAtMsgId);
		offset = -loadCount / 2;
		offsetId = _showAtMsgId;
	} else if (_showAtMsgId < 0 && _history->isChannel()) {
		if (_showAtMsgId < 0 && -_showAtMsgId < ServerMaxMsgId && _migrated) {
			_history->getReadyFor(_showAtMsgId);
			from = _migrated->peer;
			offset = -loadCount / 2;
			offsetId = -_showAtMsgId;
		} else if (_showAtMsgId == SwitchAtTopMsgId) {
			_history->getReadyFor(_showAtMsgId);
		}
	}

	auto offsetDate = 0;
	auto maxId = 0;
	auto minId = 0;
	auto historyHash = 0;

	_firstLoadRequest = MTP::send(
		MTPmessages_GetHistory(
			from->input,
			MTP_int(offsetId),
			MTP_int(offsetDate),
			MTP_int(offset),
			MTP_int(loadCount),
			MTP_int(maxId),
			MTP_int(minId),
			MTP_int(historyHash)),
		rpcDone(&HistoryWidget::messagesReceived, from),
		rpcFail(&HistoryWidget::messagesFailed));
}

void HistoryWidget::loadMessages() {
	if (!_history || _preloadRequest) return;

	if (_history->isEmpty() && _migrated && _migrated->isEmpty()) {
		return firstLoadMessages();
	}

	auto loadMigrated = _migrated && (_history->isEmpty() || _history->loadedAtTop() || (!_migrated->isEmpty() && !_migrated->loadedAtBottom()));
	auto from = loadMigrated ? _migrated : _history;
	if (from->loadedAtTop()) {
		return;
	}

	auto offsetId = from->minMsgId();
	auto addOffset = 0;
	auto loadCount = offsetId
		? kMessagesPerPage
		: kMessagesPerPageFirst;
	auto offsetDate = 0;
	auto maxId = 0;
	auto minId = 0;
	auto historyHash = 0;

	_debug_preloadOffsetId = offsetId + 1;
	_debug_preloadAddOffset = addOffset;
	_debug_preloadLoadCount = loadCount;
	_debug_preloadPeer = from->peer->id;
	_preloadRequest = MTP::send(
		MTPmessages_GetHistory(
			from->peer->input,
			MTP_int(offsetId),
			MTP_int(offsetDate),
			MTP_int(addOffset),
			MTP_int(loadCount),
			MTP_int(maxId),
			MTP_int(minId),
			MTP_int(historyHash)),
		rpcDone(&HistoryWidget::messagesReceived, from->peer),
		rpcFail(&HistoryWidget::messagesFailed));
}

void HistoryWidget::loadMessagesDown() {
	if (!_history || _preloadDownRequest) return;

	if (_history->isEmpty() && _migrated && _migrated->isEmpty()) {
		return firstLoadMessages();
	}

	auto loadMigrated = _migrated && !(_migrated->isEmpty() || _migrated->loadedAtBottom() || (!_history->isEmpty() && !_history->loadedAtTop()));
	auto from = loadMigrated ? _migrated : _history;
	if (from->loadedAtBottom()) {
		return;
	}

	auto loadCount = kMessagesPerPage;
	auto addOffset = -loadCount;
	auto offsetId = from->maxMsgId();
	if (!offsetId) {
		if (loadMigrated || !_migrated) return;
		++offsetId;
		++addOffset;
	}
	auto offsetDate = 0;
	auto maxId = 0;
	auto minId = 0;
	auto historyHash = 0;

	_debug_preloadDownOffsetId = offsetId + 1;
	_debug_preloadDownAddOffset = addOffset;
	_debug_preloadDownLoadCount = loadCount;
	_debug_preloadDownPeer = from->peer->id;
	_preloadDownRequest = MTP::send(
		MTPmessages_GetHistory(
			from->peer->input,
			MTP_int(offsetId + 1),
			MTP_int(offsetDate),
			MTP_int(addOffset),
			MTP_int(loadCount),
			MTP_int(maxId),
			MTP_int(minId),
			MTP_int(historyHash)),
		rpcDone(&HistoryWidget::messagesReceived, from->peer),
		rpcFail(&HistoryWidget::messagesFailed));
}

void HistoryWidget::delayedShowAt(MsgId showAtMsgId) {
	if (!_history || (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId)) return;

	clearDelayedShowAt();
	_delayedShowAtMsgId = showAtMsgId;

	auto from = _peer;
	auto offsetId = 0;
	auto offset = 0;
	auto loadCount = kMessagesPerPage;
	if (_delayedShowAtMsgId == ShowAtUnreadMsgId) {
		if (_migrated && _migrated->unreadCount()) {
			from = _migrated->peer;
			offset = -loadCount / 2;
			offsetId = _migrated->inboxReadBefore;
		} else if (_history->unreadCount()) {
			offset = -loadCount / 2;
			offsetId = _history->inboxReadBefore;
		} else {
			loadCount = kMessagesPerPageFirst;
		}
	} else if (_delayedShowAtMsgId == ShowAtTheEndMsgId) {
		loadCount = kMessagesPerPageFirst;
	} else if (_delayedShowAtMsgId > 0) {
		offset = -loadCount / 2;
		offsetId = _delayedShowAtMsgId;
	} else if (_delayedShowAtMsgId < 0 && _history->isChannel()) {
		if (_delayedShowAtMsgId < 0 && -_delayedShowAtMsgId < ServerMaxMsgId && _migrated) {
			from = _migrated->peer;
			offset = -loadCount / 2;
			offsetId = -_delayedShowAtMsgId;
		}
	}
	auto offsetDate = 0;
	auto maxId = 0;
	auto minId = 0;
	auto historyHash = 0;

	_delayedShowAtRequest = MTP::send(
		MTPmessages_GetHistory(
			from->input,
			MTP_int(offsetId),
			MTP_int(offsetDate),
			MTP_int(offset),
			MTP_int(loadCount),
			MTP_int(maxId),
			MTP_int(minId),
			MTP_int(historyHash)),
		rpcDone(&HistoryWidget::messagesReceived, from),
		rpcFail(&HistoryWidget::messagesFailed));
}

void HistoryWidget::onScroll() {
	App::checkImageCacheSize();
	preloadHistoryIfNeeded();
	visibleAreaUpdated();
	if (!_synteticScrollEvent) {
		_lastUserScrolled = getms();
	}
}

bool HistoryWidget::isItemCompletelyHidden(HistoryItem *item) const {
	auto top = _list ? _list->itemTop(item) : -2;
	if (top < 0) {
		return true;
	}

	auto bottom = top + item->height();
	auto scrollTop = _scroll->scrollTop();
	auto scrollBottom = scrollTop + _scroll->height();
	return (top >= scrollBottom || bottom <= scrollTop);
}

void HistoryWidget::visibleAreaUpdated() {
	if (_list && !_scroll->isHidden()) {
		auto scrollTop = _scroll->scrollTop();
		auto scrollBottom = scrollTop + _scroll->height();
		_list->visibleAreaUpdated(scrollTop, scrollBottom);
		if (_history->loadedAtBottom() && (_history->unreadCount() > 0 || (_migrated && _migrated->unreadCount() > 0))) {
			auto showFrom = (_migrated && _migrated->showFrom) ? _migrated->showFrom : (_history ? _history->showFrom : nullptr);
			auto showFromVisible = (showFrom && !showFrom->detached() && scrollBottom > _list->itemTop(showFrom));
			auto atBottom = (scrollTop >= _scroll->scrollTopMax());
			if ((showFromVisible || atBottom) && App::wnd()->doWeReadServerHistory()) {
				Auth().api().readServerHistory(_history);
			}
		}
		controller()->floatPlayerAreaUpdated().notify(true);
	}
}

void HistoryWidget::preloadHistoryIfNeeded() {
	if (_firstLoadRequest || _scroll->isHidden() || !_peer) {
		return;
	}

	updateHistoryDownVisibility();
	if (!_scrollToAnimation.animating()) {
		preloadHistoryByScroll();
		checkReplyReturns();
	}

	auto scrollTop = _scroll->scrollTop();
	if (scrollTop != _lastScrollTop) {
		_lastScrolled = getms();
		_lastScrollTop = scrollTop;
	}
}

void HistoryWidget::preloadHistoryByScroll() {
	if (_firstLoadRequest || _scroll->isHidden() || !_peer) {
		return;
	}

	auto scrollTop = _scroll->scrollTop();
	auto scrollTopMax = _scroll->scrollTopMax();
	auto scrollHeight = _scroll->height();
	if (scrollTop + kPreloadHeightsCount * scrollHeight >= scrollTopMax) {
		loadMessagesDown();
	}
	if (scrollTop <= kPreloadHeightsCount * scrollHeight) {
		loadMessages();
	}
}

void HistoryWidget::checkReplyReturns() {
	if (_firstLoadRequest || _scroll->isHidden() || !_peer) {
		return;
	}
	auto scrollTop = _scroll->scrollTop();
	auto scrollTopMax = _scroll->scrollTopMax();
	auto scrollHeight = _scroll->height();
	while (_replyReturn) {
		auto below = (_replyReturn->detached() && _replyReturn->history() == _history && !_history->isEmpty() && _replyReturn->id < _history->blocks.back()->items.back()->id);
		if (!below) {
			below = (_replyReturn->detached() && _replyReturn->history() == _migrated && !_history->isEmpty());
		}
		if (!below) {
			below = (_replyReturn->detached() && _migrated && _replyReturn->history() == _migrated && !_migrated->isEmpty() && _replyReturn->id < _migrated->blocks.back()->items.back()->id);
		}
		if (!below && !_replyReturn->detached()) {
			below = (scrollTop >= scrollTopMax) || (_list->itemTop(_replyReturn) < scrollTop + scrollHeight / 2);
		}
		if (below) {
			calcNextReplyReturn();
		} else {
			break;
		}
	}
}

void HistoryWidget::onInlineBotCancel() {
	auto &textWithTags = _field->getTextWithTags();
	if (textWithTags.text.size() > _inlineBotUsername.size() + 2) {
		setFieldText({ '@' + _inlineBotUsername + ' ', TextWithTags::Tags() }, TextUpdateEvent::SaveDraft, Ui::FlatTextarea::AddToUndoHistory);
	} else {
		clearFieldText(TextUpdateEvent::SaveDraft, Ui::FlatTextarea::AddToUndoHistory);
	}
}

void HistoryWidget::onWindowVisibleChanged() {
	QTimer::singleShot(0, this, SLOT(preloadHistoryIfNeeded()));
}

void HistoryWidget::historyDownClicked() {
	if (_replyReturn && _replyReturn->history() == _history) {
		showHistory(_peer->id, _replyReturn->id);
	} else if (_replyReturn && _replyReturn->history() == _migrated) {
		showHistory(_peer->id, -_replyReturn->id);
	} else if (_peer) {
		showHistory(_peer->id, ShowAtUnreadMsgId);
	}
}

void HistoryWidget::showNextUnreadMention() {
	showHistory(_peer->id, _history->getMinLoadedUnreadMention());
}

void HistoryWidget::saveEditMsg() {
	if (_saveEditMsgRequestId) return;

	WebPageId webPageId = _previewCancelled ? CancelledWebPageId : ((_previewData && _previewData->pendingTill >= 0) ? _previewData->id : 0);

	auto &textWithTags = _field->getTextWithTags();
	auto prepareFlags = itemTextOptions(_history, App::self()).flags;
	auto sending = TextWithEntities();
	auto left = TextWithEntities { textWithTags.text, ConvertTextTagsToEntities(textWithTags.tags) };
	TextUtilities::PrepareForSending(left, prepareFlags);

	if (!TextUtilities::CutPart(sending, left, MaxMessageSize)) {
		_field->selectAll();
		_field->setFocus();
		return;
	} else if (!left.text.isEmpty()) {
		Ui::show(Box<InformBox>(lang(lng_edit_too_long)));
		return;
	}

	auto sendFlags = MTPmessages_EditMessage::Flag::f_message | 0;
	if (webPageId == CancelledWebPageId) {
		sendFlags |= MTPmessages_EditMessage::Flag::f_no_webpage;
	}
	auto localEntities = TextUtilities::EntitiesToMTP(sending.entities);
	auto sentEntities = TextUtilities::EntitiesToMTP(sending.entities, TextUtilities::ConvertOption::SkipLocal);
	if (!sentEntities.v.isEmpty()) {
		sendFlags |= MTPmessages_EditMessage::Flag::f_entities;
	}

	_saveEditMsgRequestId = MTP::send(
		MTPmessages_EditMessage(
			MTP_flags(sendFlags),
			_history->peer->input,
			MTP_int(_editMsgId),
			MTP_string(sending.text),
			MTPnullMarkup,
			sentEntities,
			MTP_inputGeoPointEmpty()),
		rpcDone(&HistoryWidget::saveEditMsgDone, _history),
		rpcFail(&HistoryWidget::saveEditMsgFail, _history));
}

void HistoryWidget::saveEditMsgDone(History *history, const MTPUpdates &updates, mtpRequestId req) {
	if (App::main()) {
		App::main()->sentUpdatesReceived(updates);
	}
	if (req == _saveEditMsgRequestId) {
		_saveEditMsgRequestId = 0;
		cancelEdit();
	}
	if (auto editDraft = history->editDraft()) {
		if (editDraft->saveRequestId == req) {
			history->clearEditDraft();
			if (App::main()) App::main()->writeDrafts(history);
		}
	}
}

bool HistoryWidget::saveEditMsgFail(History *history, const RPCError &error, mtpRequestId req) {
	if (MTP::isDefaultHandledError(error)) return false;
	if (req == _saveEditMsgRequestId) {
		_saveEditMsgRequestId = 0;
	}
	if (auto editDraft = history->editDraft()) {
		if (editDraft->saveRequestId == req) {
			editDraft->saveRequestId = 0;
		}
	}

	QString err = error.type();
	if (err == qstr("MESSAGE_ID_INVALID") || err == qstr("CHAT_ADMIN_REQUIRED") || err == qstr("MESSAGE_EDIT_TIME_EXPIRED")) {
		Ui::show(Box<InformBox>(lang(lng_edit_error)));
	} else if (err == qstr("MESSAGE_NOT_MODIFIED")) {
		cancelEdit();
	} else if (err == qstr("MESSAGE_EMPTY")) {
		_field->selectAll();
		_field->setFocus();
	} else {
		Ui::show(Box<InformBox>(lang(lng_edit_error)));
	}
	update();
	return true;
}

void HistoryWidget::hideSelectorControlsAnimated() {
	_fieldAutocomplete->hideAnimated();
	if (_tabbedPanel) {
		_tabbedPanel->hideAnimated();
	}
	if (_inlineResults) {
		_inlineResults->hideAnimated();
	}
}

void HistoryWidget::onSend(bool ctrlShiftEnter) {
	if (!_history) return;

	if (_editMsgId) {
		saveEditMsg();
		return;
	}

	WebPageId webPageId = _previewCancelled ? CancelledWebPageId : ((_previewData && _previewData->pendingTill >= 0) ? _previewData->id : 0);

	auto message = MainWidget::MessageToSend(_history);
	message.textWithTags = _field->getTextWithTags();
	message.replyTo = replyToId();
	message.webPageId = webPageId;
	App::main()->sendMessage(message);

	clearFieldText();
	_saveDraftText = true;
	_saveDraftStart = getms();
	onDraftSave();

	hideSelectorControlsAnimated();

	if (_previewData && _previewData->pendingTill) previewCancel();
	_field->setFocus();

	if (!_keyboard->hasMarkup() && _keyboard->forceReply() && !_kbReplyTo) {
		onKbToggle();
	}
}

void HistoryWidget::onUnblock() {
	if (_unblockRequest) return;
	if (!_peer || !_peer->isUser() || !_peer->asUser()->isBlocked()) {
		updateControlsVisibility();
		return;
	}

	_unblockRequest = MTP::send(MTPcontacts_Unblock(_peer->asUser()->inputUser), rpcDone(&HistoryWidget::unblockDone, _peer), rpcFail(&HistoryWidget::unblockFail));
}

void HistoryWidget::unblockDone(PeerData *peer, const MTPBool &result, mtpRequestId req) {
	if (!peer->isUser()) return;
	if (_unblockRequest == req) _unblockRequest = 0;
	peer->asUser()->setBlockStatus(UserData::BlockStatus::NotBlocked);
}

bool HistoryWidget::unblockFail(const RPCError &error, mtpRequestId req) {
	if (MTP::isDefaultHandledError(error)) return false;

	if (_unblockRequest == req) _unblockRequest = 0;
	return false;
}

void HistoryWidget::blockDone(PeerData *peer, const MTPBool &result) {
	if (!peer->isUser()) return;

	peer->asUser()->setBlockStatus(UserData::BlockStatus::Blocked);
}

void HistoryWidget::onBotStart() {
	if (!_peer || !_peer->isUser() || !_peer->asUser()->botInfo || !_canSendMessages) {
		updateControlsVisibility();
		return;
	}

	QString token = _peer->asUser()->botInfo->startToken;
	if (token.isEmpty()) {
		sendBotCommand(_peer, _peer->asUser(), qsl("/start"), 0);
	} else {
		uint64 randomId = rand_value<uint64>();
		MTP::send(MTPmessages_StartBot(_peer->asUser()->inputUser, MTP_inputPeerEmpty(), MTP_long(randomId), MTP_string(token)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::addParticipantFail, { _peer->asUser(), (PeerData*)nullptr }));

		_peer->asUser()->botInfo->startToken = QString();
		if (_keyboard->hasMarkup()) {
			if (_keyboard->singleUse() && _keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId) && _history->lastKeyboardUsed) {
				_history->lastKeyboardHiddenId = _history->lastKeyboardId;
			}
			if (!kbWasHidden()) _kbShown = _keyboard->hasMarkup();
		}
	}
	updateControlsVisibility();
	updateControlsGeometry();
}

void HistoryWidget::onJoinChannel() {
	if (_unblockRequest) return;
	if (!_peer || !_peer->isChannel() || !isJoinChannel()) {
		updateControlsVisibility();
		return;
	}

	_unblockRequest = MTP::send(MTPchannels_JoinChannel(_peer->asChannel()->inputChannel), rpcDone(&HistoryWidget::joinDone), rpcFail(&HistoryWidget::joinFail));
}

void HistoryWidget::joinDone(const MTPUpdates &result, mtpRequestId req) {
	if (_unblockRequest == req) _unblockRequest = 0;
	if (App::main()) App::main()->sentUpdatesReceived(result);
}

bool HistoryWidget::joinFail(const RPCError &error, mtpRequestId req) {
	if (MTP::isDefaultHandledError(error)) return false;

	if (_unblockRequest == req) _unblockRequest = 0;
	if (error.type() == qstr("CHANNEL_PRIVATE") || error.type() == qstr("CHANNEL_PUBLIC_GROUP_NA") || error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
		Ui::show(Box<InformBox>(lang((_peer && _peer->isMegagroup()) ? lng_group_not_accessible : lng_channel_not_accessible)));
		return true;
	} else if (error.type() == qstr("CHANNELS_TOO_MUCH")) {
		Ui::show(Box<InformBox>(lang(lng_join_channel_error)));
	}

	return false;
}

void HistoryWidget::onMuteUnmute() {
	const auto muteState = _history->mute()
		? Data::NotifySettings::MuteChange::Unmute
		: Data::NotifySettings::MuteChange::Mute;
	App::main()->updateNotifySettings(_peer, muteState);
}

void HistoryWidget::onBroadcastSilentChange() {
	updateFieldPlaceholder();
}

History *HistoryWidget::history() const {
	return _history;
}

PeerData *HistoryWidget::peer() const {
	return _peer;
}

void HistoryWidget::setMsgId(MsgId showAtMsgId) { // sometimes _showAtMsgId is set directly
	if (_showAtMsgId != showAtMsgId) {
		auto wasMsgId = _showAtMsgId;
		_showAtMsgId = showAtMsgId;
		App::main()->dlgUpdated(_history ? _history->peer : nullptr, wasMsgId);
		emit historyShown(_history, _showAtMsgId);
	}
}

MsgId HistoryWidget::msgId() const {
	return _showAtMsgId;
}

void HistoryWidget::showAnimated(
		Window::SlideDirection direction,
		const Window::SectionSlideParams &params) {
	_showDirection = direction;

	_a_show.finish();

	_cacheUnder = params.oldContentCache;
	show();
	_topBar->finishAnimating();
	historyDownAnimationFinish();
	unreadMentionsAnimationFinish();
	_topShadow->setVisible(params.withTopBarShadow ? false : true);
	_cacheOver = App::main()->grabForShowAnimation(params);

	hideChildren();
	if (params.withTopBarShadow) _topShadow->show();

	if (_showDirection == Window::SlideDirection::FromLeft) {
		std::swap(_cacheUnder, _cacheOver);
	}
	_a_show.start([this] { animationCallback(); }, 0., 1., st::slideDuration, Window::SlideAnimation::transition());
	if (_history) {
		_topBar->show();
		_topBar->setAnimationMode(true);
	}

	activate();
}

void HistoryWidget::animationCallback() {
	update();
	if (!_a_show.animating()) {
		historyDownAnimationFinish();
		unreadMentionsAnimationFinish();
		_cacheUnder = _cacheOver = QPixmap();
		doneShow();
	}
}

void HistoryWidget::doneShow() {
	_topBar->setAnimationMode(false);
	updateReportSpamStatus();
	updateBotKeyboard();
	updateControlsVisibility();
	if (!_historyInited) {
		updateHistoryGeometry(true);
	} else if (hasPendingResizedItems()) {
		updateHistoryGeometry();
	}
	preloadHistoryIfNeeded();
	if (App::wnd()) {
		App::wnd()->checkHistoryActivation();
		App::wnd()->setInnerFocus();
	}
}

void HistoryWidget::finishAnimating() {
	if (!_a_show.animating()) return;
	_a_show.finish();
	_topShadow->setVisible(_peer != nullptr);
	_topBar->setVisible(_peer != nullptr);
	historyDownAnimationFinish();
	unreadMentionsAnimationFinish();
}

void HistoryWidget::historyDownAnimationFinish() {
	_historyDownShown.finish();
	updateHistoryDownPosition();
}

void HistoryWidget::unreadMentionsAnimationFinish() {
	_unreadMentionsShown.finish();
	updateUnreadMentionsPosition();
}

void HistoryWidget::step_recording(float64 ms, bool timer) {
	float64 dt = ms / AudioVoiceMsgUpdateView;
	if (dt >= 1) {
		_a_recording.stop();
		a_recordingLevel.finish();
	} else {
		a_recordingLevel.update(dt, anim::linear);
	}
	if (timer) update(_attachToggle->geometry());
}

void HistoryWidget::chooseAttach() {
	if (!_peer || !_peer->canWrite()) return;
	if (auto megagroup = _peer->asMegagroup()) {
		if (megagroup->restricted(ChannelRestriction::f_send_media)) {
			Ui::show(Box<InformBox>(lang(lng_restricted_send_media)));
			return;
		}
	}

	auto filter = FileDialog::AllFilesFilter() + qsl(";;Image files (*") + cImgExtensions().join(qsl(" *")) + qsl(")");

	FileDialog::GetOpenPaths(lang(lng_choose_files), filter, base::lambda_guarded(this, [this](const FileDialog::OpenResult &result) {
		if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
			return;
		}

		if (!result.remoteContent.isEmpty()) {
			auto animated = false;
			auto image = App::readImage(result.remoteContent, nullptr, false, &animated);
			if (!image.isNull() && !animated) {
				confirmSendingFiles(image, result.remoteContent);
			} else {
				uploadFile(result.remoteContent, SendMediaType::File);
			}
		} else {
			auto lists = getSendingFilesLists(result.paths);
			if (lists.allFilesForCompress) {
				confirmSendingFiles(lists);
			} else {
				validateSendingFiles(lists, [this](const QStringList &files) {
					uploadFiles(files, SendMediaType::File);
					return true;
				});
			}
		}
	}));
}

void HistoryWidget::sendButtonClicked() {
	auto type = _send->type();
	if (type == Ui::SendButton::Type::Cancel) {
		onInlineBotCancel();
	} else if (type != Ui::SendButton::Type::Record) {
		onSend();
	}
}

void HistoryWidget::dragEnterEvent(QDragEnterEvent *e) {
	if (!_history || !_canSendMessages) return;

	_attachDrag = getDragState(e->mimeData());
	updateDragAreas();

	if (_attachDrag != DragState::None) {
		e->setDropAction(Qt::IgnoreAction);
		e->accept();
	}
}

void HistoryWidget::dragLeaveEvent(QDragLeaveEvent *e) {
	if (_attachDrag != DragState::None || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) {
		_attachDrag = DragState::None;
		updateDragAreas();
	}
}

void HistoryWidget::leaveEventHook(QEvent *e) {
	if (_attachDrag != DragState::None || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) {
		_attachDrag = DragState::None;
		updateDragAreas();
	}
	if (hasMouseTracking()) mouseMoveEvent(0);
}

void HistoryWidget::mouseMoveEvent(QMouseEvent *e) {
	auto pos = e ? e->pos() : mapFromGlobal(QCursor::pos());
	updateOverStates(pos);
}

void HistoryWidget::updateOverStates(QPoint pos) {
	auto inField = pos.y() >= (_scroll->y() + _scroll->height()) && pos.y() < height() && pos.x() >= 0 && pos.x() < width();
	auto inReplyEditForward = QRect(st::historyReplySkip, _field->y() - st::historySendPadding - st::historyReplyHeight, width() - st::historyReplySkip - _fieldBarCancel->width(), st::historyReplyHeight).contains(pos) && (_editMsgId || replyToId() || readyToForward());
	auto inPinnedMsg = QRect(0, _topBar->bottomNoMargins(), width(), st::historyReplyHeight).contains(pos) && _pinnedBar;
	auto inClickable = inReplyEditForward || inPinnedMsg;
	if (inField != _inField && _recording) {
		_inField = inField;
		_send->setRecordActive(_inField);
	}
	_inReplyEditForward = inReplyEditForward;
	_inPinnedMsg = inPinnedMsg;
	if (inClickable != _inClickable) {
		_inClickable = inClickable;
		setCursor(_inClickable ? style::cur_pointer : style::cur_default);
	}
}

void HistoryWidget::leaveToChildEvent(QEvent *e, QWidget *child) { // e -- from enterEvent() of child TWidget
	if (hasMouseTracking()) {
		updateOverStates(mapFromGlobal(QCursor::pos()));
	}
}

void HistoryWidget::recordStartCallback() {
	if (!Media::Capture::instance()->available()) {
		return;
	}
	if (auto megagroup = _peer ? _peer->asMegagroup() : nullptr) {
		if (megagroup->restricted(ChannelRestriction::f_send_media)) {
			Ui::show(Box<InformBox>(lang(lng_restricted_send_media)));
			return;
		}
	}

	emit Media::Capture::instance()->start();

	_recording = _inField = true;
	updateControlsVisibility();
	activate();

	updateField();

	_send->setRecordActive(true);
}

void HistoryWidget::recordStopCallback(bool active) {
	stopRecording(_peer && active);
}

void HistoryWidget::recordUpdateCallback(QPoint globalPos) {
	updateOverStates(mapFromGlobal(globalPos));
}

void HistoryWidget::mouseReleaseEvent(QMouseEvent *e) {
	if (_replyForwardPressed) {
		_replyForwardPressed = false;
		update(0, _field->y() - st::historySendPadding - st::historyReplyHeight, width(), st::historyReplyHeight);
	}
	if (_attachDrag != DragState::None || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) {
		_attachDrag = DragState::None;
		updateDragAreas();
	}
	if (_recording) {
		stopRecording(_peer && _inField);
	}
}

void HistoryWidget::stopRecording(bool send) {
	emit Media::Capture::instance()->stop(send);

	a_recordingLevel = anim::value();
	_a_recording.stop();

	_recording = false;
	_recordingSamples = 0;
	if (_history) {
		updateSendAction(_history, SendAction::Type::RecordVoice, -1);
	}

	updateControlsVisibility();
	activate();

	updateField();
	_send->setRecordActive(false);
}

void HistoryWidget::sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo) { // replyTo != 0 from ReplyKeyboardMarkup, == 0 from cmd links
	if (!_peer || _peer != peer) return;

	bool lastKeyboardUsed = (_keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)) && (_keyboard->forMsgId() == FullMsgId(_channel, replyTo));

	QString toSend = cmd;
	if (bot && (!bot->isUser() || !bot->asUser()->botInfo)) {
		bot = nullptr;
	}
	QString username = bot ? bot->asUser()->username : QString();
	int32 botStatus = _peer->isChat() ? _peer->asChat()->botStatus : (_peer->isMegagroup() ? _peer->asChannel()->mgInfo->botStatus : -1);
	if (!replyTo && toSend.indexOf('@') < 2 && !username.isEmpty() && (botStatus == 0 || botStatus == 2)) {
		toSend += '@' + username;
	}

	auto message = MainWidget::MessageToSend(_history);
	message.textWithTags = { toSend, TextWithTags::Tags() };
	message.replyTo = replyTo
		? ((!_peer->isUser()/* && (botStatus == 0 || botStatus == 2)*/)
			? replyTo
			: replyToId())
		: 0;
	App::main()->sendMessage(message);
	if (replyTo) {
		if (_replyToId == replyTo) {
			cancelReply();
			onCloudDraftSave();
		}
		if (_keyboard->singleUse() && _keyboard->hasMarkup() && lastKeyboardUsed) {
			if (_kbShown) onKbToggle(false);
			_history->lastKeyboardUsed = true;
		}
	}

	_field->setFocus();
}

void HistoryWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) {
	if (!_peer || _peer != peer) return;

	bool lastKeyboardUsed = (_keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)) && (_keyboard->forMsgId() == FullMsgId(_channel, replyTo));
	if (replyTo) {
		if (_replyToId == replyTo) {
			cancelReply();
			onCloudDraftSave();
		}
		if (_keyboard->singleUse() && _keyboard->hasMarkup() && lastKeyboardUsed) {
			if (_kbShown) onKbToggle(false);
			_history->lastKeyboardUsed = true;
		}
	}
}

void HistoryWidget::app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, not_null<const HistoryItem*> msg, int row, int col) {
	if (msg->id < 0 || _peer != msg->history()->peer) {
		return;
	}

	bool lastKeyboardUsed = (_keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)) && (_keyboard->forMsgId() == FullMsgId(_channel, msg->id));

	auto bot = msg->getMessageBot();

	using ButtonType = HistoryMessageReplyMarkup::Button::Type;
	BotCallbackInfo info = { bot, msg->fullId(), row, col, (button->type == ButtonType::Game) };
	auto flags = MTPmessages_GetBotCallbackAnswer::Flags(0);
	QByteArray sendData;
	if (info.game) {
		flags |= MTPmessages_GetBotCallbackAnswer::Flag::f_game;
	} else if (button->type == ButtonType::Callback) {
		flags |= MTPmessages_GetBotCallbackAnswer::Flag::f_data;
		sendData = button->data;
	}
	button->requestId = MTP::send(MTPmessages_GetBotCallbackAnswer(MTP_flags(flags), _peer->input, MTP_int(msg->id), MTP_bytes(sendData)), rpcDone(&HistoryWidget::botCallbackDone, info), rpcFail(&HistoryWidget::botCallbackFail, info));
	Auth().data().requestItemRepaint(msg);

	if (_replyToId == msg->id) {
		cancelReply();
	}
	if (_keyboard->singleUse() && _keyboard->hasMarkup() && lastKeyboardUsed) {
		if (_kbShown) onKbToggle(false);
		_history->lastKeyboardUsed = true;
	}
}

void HistoryWidget::botCallbackDone(BotCallbackInfo info, const MTPmessages_BotCallbackAnswer &answer, mtpRequestId req) {
	auto item = App::histItemById(info.msgId);
	if (item) {
		if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
			if (info.row < markup->rows.size() && info.col < markup->rows.at(info.row).size()) {
				if (markup->rows.at(info.row).at(info.col).requestId == req) {
					markup->rows.at(info.row).at(info.col).requestId = 0;
					Auth().data().requestItemRepaint(item);
				}
			}
		}
	}
	if (answer.type() == mtpc_messages_botCallbackAnswer) {
		auto &answerData = answer.c_messages_botCallbackAnswer();
		if (answerData.has_message()) {
			if (answerData.is_alert()) {
				Ui::show(Box<InformBox>(qs(answerData.vmessage)));
			} else {
				Ui::Toast::Show(qs(answerData.vmessage));
			}
		} else if (answerData.has_url()) {
			auto url = qs(answerData.vurl);
			if (info.game) {
				url = AppendShareGameScoreUrl(url, info.msgId);
				BotGameUrlClickHandler(info.bot, url).onClick(Qt::LeftButton);
				if (item) {
					updateSendAction(item->history(), SendAction::Type::PlayGame);
				}
			} else {
				UrlClickHandler(url).onClick(Qt::LeftButton);
			}
		}
	}
}

bool HistoryWidget::botCallbackFail(BotCallbackInfo info, const RPCError &error, mtpRequestId req) {
	// show error?
	if (auto item = App::histItemById(info.msgId)) {
		if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
			if (info.row < markup->rows.size() && info.col < markup->rows.at(info.row).size()) {
				if (markup->rows.at(info.row).at(info.col).requestId == req) {
					markup->rows.at(info.row).at(info.col).requestId = 0;
					Auth().data().requestItemRepaint(item);
				}
			}
		}
	}
	return true;
}

bool HistoryWidget::insertBotCommand(const QString &cmd) {
	if (!canWriteMessage()) return false;

	auto insertingInlineBot = !cmd.isEmpty() && (cmd.at(0) == '@');
	auto toInsert = cmd;
	if (!toInsert.isEmpty() && !insertingInlineBot) {
		auto bot = _peer->isUser() ? _peer : (App::hoveredLinkItem() ? App::hoveredLinkItem()->fromOriginal() : nullptr);
		if (bot && (!bot->isUser() || !bot->asUser()->botInfo)) {
			bot = nullptr;
		}
		auto username = bot ? bot->asUser()->username : QString();
		auto botStatus = _peer->isChat() ? _peer->asChat()->botStatus : (_peer->isMegagroup() ? _peer->asChannel()->mgInfo->botStatus : -1);
		if (toInsert.indexOf('@') < 0 && !username.isEmpty() && (botStatus == 0 || botStatus == 2)) {
			toInsert += '@' + username;
		}
	}
	toInsert += ' ';

	if (!insertingInlineBot) {
		auto &textWithTags = _field->getTextWithTags();
		TextWithTags textWithTagsToSet;
		QRegularExpressionMatch m = QRegularExpression(qsl("^/[A-Za-z_0-9]{0,64}(@[A-Za-z_0-9]{0,32})?(\\s|$)")).match(textWithTags.text);
		if (m.hasMatch()) {
			textWithTagsToSet = _field->getTextWithTagsPart(m.capturedLength());
		} else {
			textWithTagsToSet = textWithTags;
		}
		textWithTagsToSet.text = toInsert + textWithTagsToSet.text;
		for (auto &tag : textWithTagsToSet.tags) {
			tag.offset += toInsert.size();
		}
		_field->setTextWithTags(textWithTagsToSet);

		QTextCursor cur(_field->textCursor());
		cur.movePosition(QTextCursor::End);
		_field->setTextCursor(cur);
	} else {
		setFieldText({ toInsert, TextWithTags::Tags() }, TextUpdateEvent::SaveDraft, Ui::FlatTextarea::AddToUndoHistory);
		_field->setFocus();
		return true;
	}
	return false;
}

bool HistoryWidget::eventFilter(QObject *obj, QEvent *e) {
	if ((obj == _historyDown || obj == _unreadMentions) && e->type() == QEvent::Wheel) {
		return _scroll->viewportEvent(e);
	}
	return TWidget::eventFilter(obj, e);
}

bool HistoryWidget::wheelEventFromFloatPlayer(QEvent *e) {
	return _scroll->viewportEvent(e);
}

QRect HistoryWidget::rectForFloatPlayer() const {
	return mapToGlobal(_scroll->geometry());
}

DragState HistoryWidget::getDragState(const QMimeData *d) {
	if (!d
		|| d->hasFormat(qsl("application/x-td-forward-selected"))
		|| d->hasFormat(qsl("application/x-td-forward-pressed"))
		|| d->hasFormat(qsl("application/x-td-forward-pressed-link"))) return DragState::None;

	if (d->hasImage()) return DragState::Image;

	QString uriListFormat(qsl("text/uri-list"));
	if (!d->hasFormat(uriListFormat)) return DragState::None;

	QStringList imgExtensions(cImgExtensions()), files;

	const QList<QUrl> &urls(d->urls());
	if (urls.isEmpty()) return DragState::None;

	bool allAreSmallImages = true;
	for (QList<QUrl>::const_iterator i = urls.cbegin(), en = urls.cend(); i != en; ++i) {
		if (!i->isLocalFile()) return DragState::None;

		auto file = Platform::File::UrlToLocal(*i);

		QFileInfo info(file);
		if (info.isDir()) return DragState::None;

		quint64 s = info.size();
		if (s > App::kFileSizeLimit) {
			return DragState::None;
		}
		if (allAreSmallImages) {
			if (s > App::kImageSizeLimit) {
				allAreSmallImages = false;
			} else {
				bool foundImageExtension = false;
				for (QStringList::const_iterator j = imgExtensions.cbegin(), end = imgExtensions.cend(); j != end; ++j) {
					if (file.right(j->size()).toLower() == (*j).toLower()) {
						foundImageExtension = true;
						break;
					}
				}
				if (!foundImageExtension) {
					allAreSmallImages = false;
				}
			}
		}
	}
	return allAreSmallImages ? DragState::PhotoFiles : DragState::Files;
}

void HistoryWidget::updateDragAreas() {
	_field->setAcceptDrops(_attachDrag == DragState::None);
	updateControlsGeometry();

	switch (_attachDrag) {
	case DragState::None:
		_attachDragDocument->otherLeave();
		_attachDragPhoto->otherLeave();
	break;
	case DragState::Files:
		_attachDragDocument->setText(lang(lng_drag_files_here), lang(lng_drag_to_send_files));
		_attachDragDocument->otherEnter();
		_attachDragPhoto->hideFast();
	break;
	case DragState::PhotoFiles:
		_attachDragDocument->setText(lang(lng_drag_images_here), lang(lng_drag_to_send_no_compression));
		_attachDragPhoto->setText(lang(lng_drag_photos_here), lang(lng_drag_to_send_quick));
		_attachDragDocument->otherEnter();
		_attachDragPhoto->otherEnter();
	break;
	case DragState::Image:
		_attachDragPhoto->setText(lang(lng_drag_images_here), lang(lng_drag_to_send_quick));
		_attachDragDocument->hideFast();
		_attachDragPhoto->otherEnter();
	break;
	};
}

bool HistoryWidget::readyToForward() const {
	return _canSendMessages && !_toForward.empty();
}

bool HistoryWidget::hasSilentToggle() const {
	return _peer
		&& _peer->isChannel()
		&& !_peer->isMegagroup()
		&& _peer->canWrite()
		&& !_peer->notifySettingsUnknown();
}

void HistoryWidget::inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result) {
	_inlineBotResolveRequestId = 0;
//	Notify::inlineBotRequesting(false);
	UserData *resolvedBot = nullptr;
	if (result.type() == mtpc_contacts_resolvedPeer) {
		const auto &d(result.c_contacts_resolvedPeer());
		resolvedBot = App::feedUsers(d.vusers);
		if (resolvedBot) {
			if (!resolvedBot->botInfo || resolvedBot->botInfo->inlinePlaceholder.isEmpty()) {
				resolvedBot = nullptr;
			}
		}
		App::feedChats(d.vchats);
	}

	UserData *bot = nullptr;
	QString inlineBotUsername;
	auto query = _field->getInlineBotQuery(&bot, &inlineBotUsername);
	if (inlineBotUsername == _inlineBotUsername) {
		if (bot == Ui::LookingUpInlineBot) {
			bot = resolvedBot;
		}
	} else {
		bot = nullptr;
	}
	if (bot) {
		applyInlineBotQuery(bot, query);
	} else {
		clearInlineBot();
	}
}

bool HistoryWidget::inlineBotResolveFail(QString name, const RPCError &error) {
	if (MTP::isDefaultHandledError(error)) return false;

	_inlineBotResolveRequestId = 0;
//	Notify::inlineBotRequesting(false);
	if (name == _inlineBotUsername) {
		clearInlineBot();
	}
	return true;
}

bool HistoryWidget::isBotStart() const {
	if (!_peer || !_peer->isUser() || !_peer->asUser()->botInfo || !_canSendMessages) return false;
	return !_peer->asUser()->botInfo->startToken.isEmpty() || (_history->isEmpty() && !_history->lastMsg);
}

bool HistoryWidget::isBlocked() const {
	return _peer && _peer->isUser() && _peer->asUser()->isBlocked();
}

bool HistoryWidget::isJoinChannel() const {
	return _peer && _peer->isChannel() && !_peer->asChannel()->amIn();
}

bool HistoryWidget::isMuteUnmute() const {
	return _peer && _peer->isChannel() && _peer->asChannel()->isBroadcast() && !_peer->asChannel()->canPublish();
}

bool HistoryWidget::showRecordButton() const {
	return Media::Capture::instance()->available() && !_field->hasSendText() && !readyToForward() && !_editMsgId;
}

bool HistoryWidget::showInlineBotCancel() const {
	return _inlineBot && (_inlineBot != Ui::LookingUpInlineBot);
}

void HistoryWidget::updateSendButtonType() {
	auto type = [this] {
		using Type = Ui::SendButton::Type;
		if (_editMsgId) {
			return Type::Save;
		} else if (_isInlineBot) {
			return Type::Cancel;
		} else if (showRecordButton()) {
			return Type::Record;
		}
		return Type::Send;
	};
	_send->setType(type());
}

bool HistoryWidget::updateCmdStartShown() {
	bool cmdStartShown = false;
	if (_history && _peer && ((_peer->isChat() && _peer->asChat()->botStatus > 0) || (_peer->isMegagroup() && _peer->asChannel()->mgInfo->botStatus > 0) || (_peer->isUser() && _peer->asUser()->botInfo))) {
		if (!isBotStart() && !isBlocked() && !_keyboard->hasMarkup() && !_keyboard->forceReply()) {
			if (!_field->hasSendText()) {
				cmdStartShown = true;
			}
		}
	}
	if (_cmdStartShown != cmdStartShown) {
		_cmdStartShown = cmdStartShown;
		return true;
	}
	return false;
}

bool HistoryWidget::kbWasHidden() const {
	return _history && (_keyboard->forMsgId() == FullMsgId(_history->channelId(), _history->lastKeyboardHiddenId));
}

void HistoryWidget::dropEvent(QDropEvent *e) {
	_attachDrag = DragState::None;
	updateDragAreas();
	e->acceptProposedAction();
}

void HistoryWidget::onKbToggle(bool manual) {
	auto fieldEnabled = canWriteMessage() && !_a_show.animating();
	if (_kbShown || _kbReplyTo) {
		_botKeyboardHide->hide();
		if (_kbShown) {
			if (fieldEnabled) {
				_botKeyboardShow->show();
			}
			if (manual && _history) {
				_history->lastKeyboardHiddenId = _keyboard->forMsgId().msg;
			}

			_kbScroll->hide();
			_kbShown = false;

			_field->setMaxHeight(st::historyComposeFieldMaxHeight);

			_kbReplyTo = 0;
			if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_editMsgId && !_replyToId) {
				_fieldBarCancel->hide();
				updateMouseTracking();
			}
		} else {
			if (_history) {
				_history->clearLastKeyboard();
			} else {
				updateBotKeyboard();
			}
		}
	} else if (!_keyboard->hasMarkup() && _keyboard->forceReply()) {
		_botKeyboardHide->hide();
		_botKeyboardShow->hide();
		if (fieldEnabled) {
			_botCommandStart->show();
		}
		_kbScroll->hide();
		_kbShown = false;

		_field->setMaxHeight(st::historyComposeFieldMaxHeight);

		_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0;
		if (_kbReplyTo && !_editMsgId && !_replyToId && fieldEnabled) {
			updateReplyToName();
			_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_kbReplyTo->inReplyText()), _textDlgOptions);
			_fieldBarCancel->show();
			updateMouseTracking();
		}
		if (manual && _history) {
			_history->lastKeyboardHiddenId = 0;
		}
	} else if (fieldEnabled) {
		_botKeyboardHide->show();
		_botKeyboardShow->hide();
		_kbScroll->show();
		_kbShown = true;

		int32 maxh = qMin(_keyboard->height(), st::historyComposeFieldMaxHeight - (st::historyComposeFieldMaxHeight / 2));
		_field->setMaxHeight(st::historyComposeFieldMaxHeight - maxh);

		_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0;
		if (_kbReplyTo && !_editMsgId && !_replyToId) {
			updateReplyToName();
			_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_kbReplyTo->inReplyText()), _textDlgOptions);
			_fieldBarCancel->show();
			updateMouseTracking();
		}
		if (manual && _history) {
			_history->lastKeyboardHiddenId = 0;
		}
	}
	updateControlsGeometry();
	if (_botKeyboardHide->isHidden() && canWriteMessage() && !_a_show.animating()) {
		_tabbedSelectorToggle->show();
	} else {
		_tabbedSelectorToggle->hide();
	}
	updateField();
}

void HistoryWidget::onCmdStart() {
	setFieldText({ qsl("/"), TextWithTags::Tags() }, 0, Ui::FlatTextarea::AddToUndoHistory);
}

void HistoryWidget::setMembersShowAreaActive(bool active) {
	if (!active) {
		_membersDropdownShowTimer.stop();
	}
	if (active && _peer && (_peer->isChat() || _peer->isMegagroup())) {
		if (_membersDropdown) {
			_membersDropdown->otherEnter();
		} else if (!_membersDropdownShowTimer.isActive()) {
			_membersDropdownShowTimer.start(kShowMembersDropdownTimeoutMs);
		}
	} else if (_membersDropdown) {
		_membersDropdown->otherLeave();
	}
}

void HistoryWidget::onMembersDropdownShow() {
	if (!_membersDropdown) {
		_membersDropdown.create(this, st::membersInnerDropdown);
		_membersDropdown->setOwnedWidget(object_ptr<Profile::GroupMembersWidget>(this, _peer, Profile::GroupMembersWidget::TitleVisibility::Hidden, st::membersInnerItem));
		_membersDropdown->resizeToWidth(st::membersInnerWidth);

		_membersDropdown->setMaxHeight(countMembersDropdownHeightMax());
		_membersDropdown->moveToLeft(0, _topBar->height());
		_membersDropdown->setHiddenCallback([this] { _membersDropdown.destroyDelayed(); });
	}
	_membersDropdown->otherEnter();
}

void HistoryWidget::onModerateKeyActivate(int index, bool *outHandled) {
	*outHandled = _keyboard->isHidden() ? false : _keyboard->moderateKeyActivate(index);
}

void HistoryWidget::pushTabbedSelectorToThirdSection(
		const Window::SectionShow &params) {
	if (!_history || !_tabbedPanel) {
		return;
	} else if (!_canSendMessages) {
		Auth().data().setTabbedReplacedWithInfo(true);
		controller()->showPeerInfo(_peer, params.withThirdColumn());
		return;
	}
	Auth().data().setTabbedReplacedWithInfo(false);
	_tabbedSelectorToggle->setColorOverrides(
		&st::historyAttachEmojiActive,
		&st::historyRecordVoiceFgActive,
		&st::historyRecordVoiceRippleBgActive);
	auto destroyingPanel = std::move(_tabbedPanel);
	auto memento = ChatHelpers::TabbedMemento(
		destroyingPanel->takeSelector(),
		base::lambda_guarded(this, [this](
				object_ptr<TabbedSelector> selector) {
			returnTabbedSelector(std::move(selector));
		}));
	controller()->resizeForThirdSection();
	controller()->showSection(std::move(memento), params.withThirdColumn());
	destroyingPanel.destroy();
}

void HistoryWidget::pushInfoToThirdSection(
		const Window::SectionShow &params) {
	if (!_peer) {
		return;
	}
	controller()->showSection(
		Info::Memento::Default(_peer),
		params.withThirdColumn());
}

void HistoryWidget::toggleTabbedSelectorMode() {
	if (_tabbedPanel) {
		if (controller()->canShowThirdSection()
			&& !Adaptive::OneColumn()) {
			Auth().data().setTabbedSelectorSectionEnabled(true);
			Auth().saveDataDelayed();
			pushTabbedSelectorToThirdSection(
				Window::SectionShow::Way::ClearStack);
		} else {
			_tabbedPanel->toggleAnimated();
		}
	} else {
		controller()->closeThirdSection();
	}
}

void HistoryWidget::returnTabbedSelector(
		object_ptr<TabbedSelector> selector) {
	_tabbedPanel.create(
		this,
		controller(),
		std::move(selector));
	_tabbedPanel->hide();
	_tabbedSelectorToggle->installEventFilter(_tabbedPanel);
	_tabbedSelectorToggle->setColorOverrides(nullptr, nullptr, nullptr);
	_tabbedSelectorToggleTooltipShown = false;
	moveFieldControls();
}

void HistoryWidget::recountChatWidth() {
	auto layout = (width() < st::adaptiveChatWideWidth)
		? Adaptive::ChatLayout::Normal
		: Adaptive::ChatLayout::Wide;
	if (layout != Global::AdaptiveChatLayout()) {
		Global::SetAdaptiveChatLayout(layout);
		Adaptive::Changed().notify(true);
	}
}

void HistoryWidget::moveFieldControls() {
	auto keyboardHeight = 0;
	auto bottom = height();
	auto maxKeyboardHeight = st::historyComposeFieldMaxHeight - _field->height();
	_keyboard->resizeToWidth(width(), maxKeyboardHeight);
	if (_kbShown) {
		keyboardHeight = qMin(_keyboard->height(), maxKeyboardHeight);
		bottom -= keyboardHeight;
		_kbScroll->setGeometryToLeft(0, bottom, width(), keyboardHeight);
	}

// _attachToggle --------- _inlineResults -------------------------------------- _tabbedPanel --------- _fieldBarCancel
// (_attachDocument|_attachPhoto) _field (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) [_broadcast] _send
// (_botStart|_unblock|_joinChannel|_muteUnmute)

	auto buttonsBottom = bottom - _attachToggle->height();
	auto left = 0;
	_attachToggle->moveToLeft(left, buttonsBottom); left += _attachToggle->width();
	_field->moveToLeft(left, bottom - _field->height() - st::historySendPadding);
	auto right = st::historySendRight;
	_send->moveToRight(right, buttonsBottom); right += _send->width();
	_tabbedSelectorToggle->moveToRight(right, buttonsBottom);
	updateTabbedSelectorToggleTooltipGeometry();
	_botKeyboardHide->moveToRight(right, buttonsBottom); right += _botKeyboardHide->width();
	_botKeyboardShow->moveToRight(right, buttonsBottom);
	_botCommandStart->moveToRight(right, buttonsBottom);
	if (_silent) {
		_silent->moveToRight(right, buttonsBottom);
	}

	_fieldBarCancel->moveToRight(0, _field->y() - st::historySendPadding - _fieldBarCancel->height());
	if (_inlineResults) {
		_inlineResults->moveBottom(_field->y() - st::historySendPadding);
	}
	if (_tabbedPanel) {
		_tabbedPanel->moveBottom(buttonsBottom);
	}

	auto fullWidthButtonRect = myrtlrect(
		0,
		bottom - _botStart->height(),
		width(),
		_botStart->height());
	_botStart->setGeometry(fullWidthButtonRect);
	_unblock->setGeometry(fullWidthButtonRect);
	_joinChannel->setGeometry(fullWidthButtonRect);
	_muteUnmute->setGeometry(fullWidthButtonRect);
}

void HistoryWidget::updateTabbedSelectorToggleTooltipGeometry() {
	if (_tabbedSelectorToggleTooltip) {
		auto toggle = _tabbedSelectorToggle->geometry();
		auto margin = st::historyAttachEmojiTooltipDelta;
		auto margins = QMargins(margin, margin, margin, margin);
		_tabbedSelectorToggleTooltip->pointAt(toggle.marginsRemoved(margins));
	}
}

void HistoryWidget::updateFieldSize() {
	auto kbShowShown = _history && !_kbShown && _keyboard->hasMarkup();
	auto fieldWidth = width() - _attachToggle->width() - st::historySendRight;
	fieldWidth -= _send->width();
	fieldWidth -= _tabbedSelectorToggle->width();
	if (kbShowShown) fieldWidth -= _botKeyboardShow->width();
	if (_cmdStartShown) fieldWidth -= _botCommandStart->width();
	if (_silent) fieldWidth -= _silent->width();

	if (_field->width() != fieldWidth) {
		_field->resize(fieldWidth, _field->height());
	} else {
		moveFieldControls();
	}
}

void HistoryWidget::clearInlineBot() {
	if (_inlineBot) {
		_inlineBot = nullptr;
		inlineBotChanged();
		_field->finishPlaceholder();
	}
	if (_inlineResults) {
		_inlineResults->clearInlineBot();
	}
	onCheckFieldAutocomplete();
}

void HistoryWidget::inlineBotChanged() {
	bool isInlineBot = showInlineBotCancel();
	if (_isInlineBot != isInlineBot) {
		_isInlineBot = isInlineBot;
		updateFieldPlaceholder();
		updateFieldSubmitSettings();
		updateControlsVisibility();
	}
}

void HistoryWidget::onFieldResize() {
	moveFieldControls();
	updateHistoryGeometry();
	updateField();
}

void HistoryWidget::onFieldFocused() {
	if (_list) _list->clearSelectedItems(true);
}

void HistoryWidget::onCheckFieldAutocomplete() {
	if (!_history || _a_show.animating()) return;

	auto start = false;
	auto isInlineBot = _inlineBot && (_inlineBot != Ui::LookingUpInlineBot);
	auto query = isInlineBot ? QString() : _field->getMentionHashtagBotCommandPart(start);
	if (!query.isEmpty()) {
		if (query.at(0) == '#' && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) Local::readRecentHashtagsAndBots();
		if (query.at(0) == '@' && cRecentInlineBots().isEmpty()) Local::readRecentHashtagsAndBots();
		if (query.at(0) == '/' && _peer->isUser() && !_peer->asUser()->botInfo) return;
	}
	_fieldAutocomplete->showFiltered(_peer, query, start);
}

void HistoryWidget::updateFieldPlaceholder() {
	if (_editMsgId) {
		_field->setPlaceholder(langFactory(lng_edit_message_text));
	} else {
		if (_inlineBot && _inlineBot != Ui::LookingUpInlineBot) {
			auto text = _inlineBot->botInfo->inlinePlaceholder.mid(1);
			_field->setPlaceholder([text] { return text; }, _inlineBot->username.size() + 2);
		} else {
			const auto peer = _history ? _history->peer : nullptr;
			_field->setPlaceholder(langFactory(
				(peer && peer->isChannel() && !peer->isMegagroup())
				? (peer->notifySilentPosts()
					? lng_broadcast_silent_ph
					: lng_broadcast_ph)
				: lng_message_ph));
		}
	}
	updateSendButtonType();
}

template <typename SendCallback>
bool HistoryWidget::showSendFilesBox(object_ptr<SendFilesBox> box, const QString &insertTextOnCancel, const QString *addedComment, SendCallback callback) {
	App::wnd()->activateWindow();

	auto withComment = (addedComment != nullptr);
	box->setConfirmedCallback(base::lambda_guarded(this, [this, withComment, sendCallback = std::move(callback)](const QStringList &files, const QImage &image, std::unique_ptr<FileLoadTask::MediaInformation> information, bool compressed, const QString &caption, bool ctrlShiftEnter) {
		if (!canWriteMessage()) return;

		const auto replyTo = replyToId();
		if (withComment) {
			// This call will clear replyToId().
			onSend(ctrlShiftEnter);
		}
		sendCallback(
			files,
			image,
			std::move(information),
			compressed,
			caption,
			replyTo);
	}));

	if (withComment) {
		auto was = _field->getTextWithTags();
		setFieldText({ *addedComment, TextWithTags::Tags() });
		box->setCancelledCallback(base::lambda_guarded(this, [this, was] {
			setFieldText(was);
		}));
	} else if (!insertTextOnCancel.isEmpty()) {
		box->setCancelledCallback(base::lambda_guarded(this, [this, insertTextOnCancel] {
			_field->textCursor().insertText(insertTextOnCancel);
		}));
	}

	Ui::show(std::move(box));
	return true;
}

template <typename Callback>
bool HistoryWidget::validateSendingFiles(const SendingFilesLists &lists, Callback callback) {
	if (!canWriteMessage()) return false;

	App::wnd()->activateWindow();
	if (!lists.nonLocalUrls.isEmpty()) {
		Ui::show(Box<InformBox>(lng_send_image_empty(lt_name, lists.nonLocalUrls.front().toDisplayString())));
	} else if (!lists.emptyFiles.isEmpty()) {
		Ui::show(Box<InformBox>(lng_send_image_empty(lt_name, lists.emptyFiles.front())));
	} else if (!lists.tooLargeFiles.isEmpty()) {
		Ui::show(Box<InformBox>(lng_send_image_too_large(lt_name, lists.tooLargeFiles.front())));
	} else if (!lists.filesToSend.isEmpty()) {
		return callback(lists.filesToSend);
	}
	return false;
}

bool HistoryWidget::confirmSendingFiles(const QList<QUrl> &files, CompressConfirm compressed, const QString *addedComment) {
	return confirmSendingFiles(getSendingFilesLists(files), compressed, addedComment);
}

bool HistoryWidget::confirmSendingFiles(const QStringList &files, CompressConfirm compressed, const QString *addedComment) {
	return confirmSendingFiles(getSendingFilesLists(files), compressed, addedComment);
}

bool HistoryWidget::confirmSendingFiles(const SendingFilesLists &lists, CompressConfirm compressed, const QString *addedComment) {
	if (auto megagroup = _peer ? _peer->asMegagroup() : nullptr) {
		if (megagroup->restricted(ChannelRestriction::f_send_media)) {
			Ui::show(Box<InformBox>(lang(lng_restricted_send_media)));
			return false;
		}
	}
	return validateSendingFiles(lists, [this, &lists, compressed, addedComment](const QStringList &files) {
		auto insertTextOnCancel = QString();
		auto sendCallback = [this](const QStringList &files, const QImage &image, std::unique_ptr<FileLoadTask::MediaInformation> information, bool compressed, const QString &caption, MsgId replyTo) {
			auto type = compressed ? SendMediaType::Photo : SendMediaType::File;
			uploadFilesAfterConfirmation(files, QByteArray(), image, std::move(information), type, caption);
		};
		auto boxCompressConfirm = compressed;
		if (files.size() > 1 && !lists.allFilesForCompress) {
			boxCompressConfirm = CompressConfirm::None;
		}
		auto box = Box<SendFilesBox>(files, boxCompressConfirm);
		return showSendFilesBox(std::move(box), insertTextOnCancel, addedComment, std::move(sendCallback));
	});
}

bool HistoryWidget::confirmSendingFiles(const QImage &image, const QByteArray &content, CompressConfirm compressed, const QString &insertTextOnCancel) {
	if (!canWriteMessage() || image.isNull()) return false;

	App::wnd()->activateWindow();
	auto sendCallback = [this, content](const QStringList &files, const QImage &image, std::unique_ptr<FileLoadTask::MediaInformation> information, bool compressed, const QString &caption, MsgId replyTo) {
		auto type = compressed ? SendMediaType::Photo : SendMediaType::File;
		uploadFilesAfterConfirmation(files, content, image, std::move(information), type, caption);
	};
	auto box = Box<SendFilesBox>(image, compressed);
	return showSendFilesBox(std::move(box), insertTextOnCancel, nullptr, std::move(sendCallback));
}

bool HistoryWidget::confirmSendingFiles(const QMimeData *data, CompressConfirm compressed, const QString &insertTextOnCancel) {
	if (!canWriteMessage()) {
		return false;
	}

	auto urls = data->urls();
	if (!urls.isEmpty()) {
		for_const (auto &url, urls) {
			if (url.isLocalFile()) {
				confirmSendingFiles(urls, compressed);
				return true;
			}
		}
	}
	if (data->hasImage()) {
		auto image = qvariant_cast<QImage>(data->imageData());
		if (!image.isNull()) {
			confirmSendingFiles(image, QByteArray(), compressed, insertTextOnCancel);
			return true;
		}
	}
	return false;
}

bool HistoryWidget::confirmShareContact(
		const QString &phone,
		const QString &fname,
		const QString &lname,
		const QString *addedComment) {
	if (!canWriteMessage()) return false;

	auto box = Box<SendFilesBox>(phone, fname, lname);
	auto sendCallback = [=](
			const QStringList &files,
			const QImage &image,
			std::unique_ptr<FileLoadTask::MediaInformation> information,
			bool compressed,
			const QString &caption,
			MsgId replyTo) {
		auto options = ApiWrap::SendOptions(_history);
		options.replyTo = replyTo;
		Auth().api().shareContact(phone, fname, lname, options);
	};
	auto insertTextOnCancel = QString();
	return showSendFilesBox(
		std::move(box),
		insertTextOnCancel,
		addedComment,
		std::move(sendCallback));
}

HistoryWidget::SendingFilesLists HistoryWidget::getSendingFilesLists(const QList<QUrl> &files) {
	auto result = SendingFilesLists();
	for_const (auto &url, files) {
		if (!url.isLocalFile()) {
			result.nonLocalUrls.push_back(url);
		} else {
			auto filepath = Platform::File::UrlToLocal(url);
			getSendingLocalFileInfo(result, filepath);
		}
	}
	return result;
}

HistoryWidget::SendingFilesLists HistoryWidget::getSendingFilesLists(const QStringList &files) {
	auto result = SendingFilesLists();
	for_const (auto &filepath, files) {
		getSendingLocalFileInfo(result, filepath);
	}
	return result;
}

void HistoryWidget::getSendingLocalFileInfo(SendingFilesLists &result, const QString &filepath) {
	auto hasExtensionForCompress = [](const QString &filepath) {
		for_const (auto extension, cExtensionsForCompress()) {
			if (filepath.right(extension.size()).compare(extension, Qt::CaseInsensitive) == 0) {
				return true;
			}
		}
		return false;
	};
	auto fileinfo = QFileInfo(filepath);
	if (fileinfo.isDir()) {
		result.directories.push_back(filepath);
	} else {
		auto filesize = fileinfo.size();
		if (filesize <= 0) {
			result.emptyFiles.push_back(filepath);
		} else if (filesize > App::kFileSizeLimit) {
			result.tooLargeFiles.push_back(filepath);
		} else {
			result.filesToSend.push_back(filepath);
			if (result.allFilesForCompress) {
				if (filesize > App::kImageSizeLimit || !hasExtensionForCompress(filepath)) {
					result.allFilesForCompress = false;
				}
			}
		}
	}
}

void HistoryWidget::uploadFiles(const QStringList &files, SendMediaType type) {
	if (!canWriteMessage()) return;

	auto caption = QString();
	uploadFilesAfterConfirmation(files, QByteArray(), QImage(), nullptr, type, caption);
}

void HistoryWidget::uploadFilesAfterConfirmation(
		const QStringList &files,
		const QByteArray &content,
		const QImage &image,
		std::unique_ptr<FileLoadTask::MediaInformation> information,
		SendMediaType type,
		QString caption) {
	Assert(canWriteMessage());

	auto to = FileLoadTo(_peer->id, _peer->notifySilentPosts(), replyToId());
	if (files.size() > 1 && !caption.isEmpty()) {
		auto message = MainWidget::MessageToSend(_history);
		message.textWithTags = { caption, TextWithTags::Tags() };
		message.replyTo = to.replyTo;
		message.clearDraft = false;
		App::main()->sendMessage(message);
		caption = QString();
	}
	auto tasks = TasksList();
	tasks.reserve(files.size());
	for_const (auto &filepath, files) {
		if (filepath.isEmpty() && (!image.isNull() || !content.isNull())) {
			tasks.push_back(MakeShared<FileLoadTask>(content, image, type, to, caption));
		} else {
			tasks.push_back(MakeShared<FileLoadTask>(filepath, std::move(information), type, to, caption));
		}
	}
	_fileLoader.addTasks(tasks);
}

void HistoryWidget::uploadFile(const QByteArray &fileContent, SendMediaType type) {
	if (!canWriteMessage()) return;

	auto to = FileLoadTo(_peer->id, _peer->notifySilentPosts(), replyToId());
	auto caption = QString();
	_fileLoader.addTask(MakeShared<FileLoadTask>(fileContent, QImage(), type, to, caption));
}

void HistoryWidget::sendFileConfirmed(const FileLoadResultPtr &file) {
	bool lastKeyboardUsed = lastForceReplyReplied(FullMsgId(peerToChannel(file->to.peer), file->to.replyTo));

	FullMsgId newId(peerToChannel(file->to.peer), clientMsgId());

	connect(&Auth().uploader(), SIGNAL(photoReady(const FullMsgId&,bool,const MTPInputFile&)), this, SLOT(onPhotoUploaded(const FullMsgId&,bool,const MTPInputFile&)), Qt::UniqueConnection);
	connect(&Auth().uploader(), SIGNAL(documentReady(const FullMsgId&,bool,const MTPInputFile&)), this, SLOT(onDocumentUploaded(const FullMsgId&,bool,const MTPInputFile&)), Qt::UniqueConnection);
	connect(&Auth().uploader(), SIGNAL(thumbDocumentReady(const FullMsgId&,bool,const MTPInputFile&,const MTPInputFile&)), this, SLOT(onThumbDocumentUploaded(const FullMsgId&,bool,const MTPInputFile&, const MTPInputFile&)), Qt::UniqueConnection);
	connect(&Auth().uploader(), SIGNAL(photoProgress(const FullMsgId&)), this, SLOT(onPhotoProgress(const FullMsgId&)), Qt::UniqueConnection);
	connect(&Auth().uploader(), SIGNAL(documentProgress(const FullMsgId&)), this, SLOT(onDocumentProgress(const FullMsgId&)), Qt::UniqueConnection);
	connect(&Auth().uploader(), SIGNAL(photoFailed(const FullMsgId&)), this, SLOT(onPhotoFailed(const FullMsgId&)), Qt::UniqueConnection);
	connect(&Auth().uploader(), SIGNAL(documentFailed(const FullMsgId&)), this, SLOT(onDocumentFailed(const FullMsgId&)), Qt::UniqueConnection);

	Auth().uploader().upload(newId, file);

	const auto history = App::history(file->to.peer);
	const auto peer = history->peer;

	auto options = ApiWrap::SendOptions(history);
	options.clearDraft = false;
	options.replyTo = file->to.replyTo;
	options.generateLocal = true;
	Auth().api().sendAction(options);

	auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
	if (file->to.replyTo) flags |= MTPDmessage::Flag::f_reply_to_msg_id;
	bool channelPost = peer->isChannel() && !peer->isMegagroup();
	bool silentPost = channelPost && file->to.silent;
	if (channelPost) {
		flags |= MTPDmessage::Flag::f_views;
		flags |= MTPDmessage::Flag::f_post;
	}
	if (!channelPost) {
		flags |= MTPDmessage::Flag::f_from_id;
	} else if (peer->asChannel()->addsSignature()) {
		flags |= MTPDmessage::Flag::f_post_author;
	}
	if (silentPost) {
		flags |= MTPDmessage::Flag::f_silent;
	}
	auto messageFromId = channelPost ? 0 : Auth().userId();
	auto messagePostAuthor = channelPost ? (Auth().user()->firstName + ' ' + Auth().user()->lastName) : QString();
	if (file->type == SendMediaType::Photo) {
		auto photoFlags = MTPDmessageMediaPhoto::Flag::f_photo | 0;
		if (!file->caption.isEmpty()) {
			photoFlags |= MTPDmessageMediaPhoto::Flag::f_caption;
		}
		auto photo = MTP_messageMediaPhoto(
			MTP_flags(photoFlags),
			file->photo,
			MTP_string(file->caption),
			MTPint());
		history->addNewMessage(
			MTP_message(
				MTP_flags(flags),
				MTP_int(newId.msg),
				MTP_int(messageFromId),
				peerToMTP(file->to.peer),
				MTPnullFwdHeader,
				MTPint(),
				MTP_int(file->to.replyTo),
				MTP_int(unixtime()),
				MTP_string(""),
				photo,
				MTPnullMarkup,
				MTPnullEntities,
				MTP_int(1),
				MTPint(),
				MTP_string(messagePostAuthor),
				MTPlong()),
			NewMessageUnread);
	} else if (file->type == SendMediaType::File) {
		auto documentFlags = MTPDmessageMediaDocument::Flag::f_document | 0;
		if (!file->caption.isEmpty()) {
			documentFlags |= MTPDmessageMediaDocument::Flag::f_caption;
		}
		auto document = MTP_messageMediaDocument(
			MTP_flags(documentFlags),
			file->document,
			MTP_string(file->caption),
			MTPint());
		history->addNewMessage(
			MTP_message(
				MTP_flags(flags),
				MTP_int(newId.msg),
				MTP_int(messageFromId),
				peerToMTP(file->to.peer),
				MTPnullFwdHeader,
				MTPint(),
				MTP_int(file->to.replyTo),
				MTP_int(unixtime()),
				MTP_string(""),
				document,
				MTPnullMarkup,
				MTPnullEntities,
				MTP_int(1),
				MTPint(),
				MTP_string(messagePostAuthor),
				MTPlong()),
			NewMessageUnread);
	} else if (file->type == SendMediaType::Audio) {
		if (!peer->isChannel()) {
			flags |= MTPDmessage::Flag::f_media_unread;
		}
		auto documentFlags = MTPDmessageMediaDocument::Flag::f_document | 0;
		if (!file->caption.isEmpty()) {
			documentFlags |= MTPDmessageMediaDocument::Flag::f_caption;
		}
		auto document = MTP_messageMediaDocument(
			MTP_flags(documentFlags),
			file->document,
			MTP_string(file->caption),
			MTPint());
		history->addNewMessage(
			MTP_message(
				MTP_flags(flags),
				MTP_int(newId.msg),
				MTP_int(messageFromId),
				peerToMTP(file->to.peer),
				MTPnullFwdHeader,
				MTPint(),
				MTP_int(file->to.replyTo),
				MTP_int(unixtime()),
				MTP_string(""),
				document,
				MTPnullMarkup,
				MTPnullEntities,
				MTP_int(1),
				MTPint(),
				MTP_string(messagePostAuthor),
				MTPlong()),
			NewMessageUnread);
	}

	if (_peer && file->to.peer == _peer->id) {
		App::main()->historyToDown(_history);
	}
	App::main()->dialogsToUp();
	peerMessagesUpdated(file->to.peer);
}

void HistoryWidget::onPhotoUploaded(
		const FullMsgId &newId,
		bool silent,
		const MTPInputFile &file) {
	if (auto item = App::histItemById(newId)) {
		uint64 randomId = rand_value<uint64>();
		App::historyRegRandom(randomId, newId);
		History *hist = item->history();
		MsgId replyTo = item->replyToId();
		auto sendFlags = MTPmessages_SendMedia::Flags(0);
		if (replyTo) {
			sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
		}

		bool channelPost = hist->peer->isChannel() && !hist->peer->isMegagroup();
		bool silentPost = channelPost && silent;
		if (silentPost) {
			sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
		}
		auto caption = item->getMedia() ? item->getMedia()->getCaption() : TextWithEntities();
		auto media = MTP_inputMediaUploadedPhoto(
			MTP_flags(0),
			file,
			MTP_string(caption.text),
			MTPVector<MTPInputDocument>(),
			MTP_int(0));
		hist->sendRequestId = MTP::send(
			MTPmessages_SendMedia(
				MTP_flags(sendFlags),
				item->history()->peer->input,
				MTP_int(replyTo),
				media,
				MTP_long(randomId),
				MTPnullMarkup),
			App::main()->rpcDone(&MainWidget::sentUpdatesReceived),
			App::main()->rpcFail(&MainWidget::sendMessageFail),
			0,
			0,
			hist->sendRequestId);
	}
}

void HistoryWidget::onDocumentUploaded(
		const FullMsgId &newId,
		bool silent,
		const MTPInputFile &file) {
	if (auto item = dynamic_cast<HistoryMessage*>(App::histItemById(newId))) {
		auto media = item->getMedia();
		if (auto document = media ? media->getDocument() : nullptr) {
			auto randomId = rand_value<uint64>();
			App::historyRegRandom(randomId, newId);
			auto hist = item->history();
			auto replyTo = item->replyToId();
			auto sendFlags = MTPmessages_SendMedia::Flags(0);
			if (replyTo) {
				sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
			}

			bool channelPost = hist->peer->isChannel() && !hist->peer->isMegagroup();
			bool silentPost = channelPost && silent;
			if (silentPost) {
				sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
			}
			auto caption = item->getMedia() ? item->getMedia()->getCaption() : TextWithEntities();
			auto media = MTP_inputMediaUploadedDocument(
				MTP_flags(0),
				file,
				MTPInputFile(),
				MTP_string(document->mimeString()),
				composeDocumentAttributes(document),
				MTP_string(caption.text),
				MTPVector<MTPInputDocument>(),
				MTP_int(0));
			hist->sendRequestId = MTP::send(
				MTPmessages_SendMedia(
					MTP_flags(sendFlags),
					item->history()->peer->input,
					MTP_int(replyTo),
					media,
					MTP_long(randomId),
					MTPnullMarkup),
				App::main()->rpcDone(&MainWidget::sentUpdatesReceived),
				App::main()->rpcFail(&MainWidget::sendMessageFail),
				0,
				0,
				hist->sendRequestId);
		}
	}
}

void HistoryWidget::onThumbDocumentUploaded(
		const FullMsgId &newId,
		bool silent,
		const MTPInputFile &file,
		const MTPInputFile &thumb) {
	if (auto item = dynamic_cast<HistoryMessage*>(App::histItemById(newId))) {
		auto media = item->getMedia();
		if (auto document = media ? media->getDocument() : nullptr) {
			auto randomId = rand_value<uint64>();
			App::historyRegRandom(randomId, newId);
			auto hist = item->history();
			auto replyTo = item->replyToId();
			auto sendFlags = MTPmessages_SendMedia::Flags(0);
			if (replyTo) {
				sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
			}

			bool channelPost = hist->peer->isChannel() && !hist->peer->isMegagroup();
			bool silentPost = channelPost && silent;
			if (silentPost) {
				sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
			}
			auto caption = media ? media->getCaption() : TextWithEntities();
			auto media = MTP_inputMediaUploadedDocument(
				MTP_flags(MTPDinputMediaUploadedDocument::Flag::f_thumb),
				file,
				thumb,
				MTP_string(document->mimeString()),
				composeDocumentAttributes(document),
				MTP_string(caption.text),
				MTPVector<MTPInputDocument>(),
				MTP_int(0));
			hist->sendRequestId = MTP::send(
				MTPmessages_SendMedia(
					MTP_flags(sendFlags),
					item->history()->peer->input,
					MTP_int(replyTo),
					media,
					MTP_long(randomId),
					MTPnullMarkup),
				App::main()->rpcDone(&MainWidget::sentUpdatesReceived),
				App::main()->rpcFail(&MainWidget::sendMessageFail),
				0,
				0,
				hist->sendRequestId);
		}
	}
}

void HistoryWidget::onPhotoProgress(const FullMsgId &newId) {
	if (const auto item = App::histItemById(newId)) {
		const auto photo = item->getMedia()
			? item->getMedia()->getPhoto()
			: nullptr;
		updateSendAction(item->history(), SendAction::Type::UploadPhoto, 0);
		Auth().data().requestItemRepaint(item);
	}
}

void HistoryWidget::onDocumentProgress(const FullMsgId &newId) {
	if (const auto item = App::histItemById(newId)) {
		const auto media = item->getMedia();
		const auto document = media ? media->getDocument() : nullptr;
		const auto sendAction = (document && document->isVoiceMessage())
			? SendAction::Type::UploadVoice
			: SendAction::Type::UploadFile;
		updateSendAction(
			item->history(),
			sendAction,
			document ? document->uploadOffset : 0);
		Auth().data().requestItemRepaint(item);
	}
}

void HistoryWidget::onPhotoFailed(const FullMsgId &newId) {
	if (const auto item = App::histItemById(newId)) {
		updateSendAction(item->history(), SendAction::Type::UploadPhoto, -1);
		Auth().data().requestItemRepaint(item);
	}
}

void HistoryWidget::onDocumentFailed(const FullMsgId &newId) {
	if (const auto item = App::histItemById(newId)) {
		const auto media = item->getMedia();
		const auto document = media ? media->getDocument() : nullptr;
		const auto sendAction = (document && document->isVoiceMessage())
			? SendAction::Type::UploadVoice
			: SendAction::Type::UploadFile;
		updateSendAction(item->history(), sendAction, -1);
		Auth().data().requestItemRepaint(item);
	}
}

void HistoryWidget::onReportSpamClicked() {
	auto text = lang(_peer->isUser() ? lng_report_spam_sure : ((_peer->isChat() || _peer->isMegagroup()) ? lng_report_spam_sure_group : lng_report_spam_sure_channel));
	Ui::show(Box<ConfirmBox>(text, lang(lng_report_spam_ok), st::attentionBoxButton, base::lambda_guarded(this, [this, peer = _peer] {
		if (_reportSpamRequest) return;

		Ui::hideLayer();
		if (auto user = peer->asUser()) {
			MTP::send(MTPcontacts_Block(user->inputUser), rpcDone(&HistoryWidget::blockDone, peer), RPCFailHandlerPtr(), 0, 5);
		}
		_reportSpamRequest = MTP::send(MTPmessages_ReportSpam(peer->input), rpcDone(&HistoryWidget::reportSpamDone, peer), rpcFail(&HistoryWidget::reportSpamFail));
	})));
}

void HistoryWidget::reportSpamDone(PeerData *peer, const MTPBool &result, mtpRequestId req) {
	Expects(peer != nullptr);
	if (req == _reportSpamRequest) {
		_reportSpamRequest = 0;
	}
	cRefReportSpamStatuses().insert(peer->id, dbiprsReportSent);
	Local::writeReportSpamStatuses();
	if (_peer == peer) {
		setReportSpamStatus(dbiprsReportSent);
		if (_reportSpamPanel) {
			_reportSpamPanel->setReported(_reportSpamStatus == dbiprsReportSent, peer);
		}
	}
}

bool HistoryWidget::reportSpamFail(const RPCError &error, mtpRequestId req) {
	if (MTP::isDefaultHandledError(error)) return false;

	if (req == _reportSpamRequest) {
		_reportSpamRequest = 0;
	}
	return false;
}

void HistoryWidget::onReportSpamHide() {
	if (_peer) {
		cRefReportSpamStatuses().insert(_peer->id, dbiprsHidden);
		Local::writeReportSpamStatuses();

		MTP::send(MTPmessages_HideReportSpam(_peer->input));
	}
	setReportSpamStatus(dbiprsHidden);
	updateControlsVisibility();
}

void HistoryWidget::onReportSpamClear() {
	Expects(_peer != nullptr);
	InvokeQueued(App::main(), [peer = _peer] {
		if (peer->isUser()) {
			App::main()->deleteConversation(peer);
		} else if (auto chat = peer->asChat()) {
			App::main()->deleteAndExit(chat);
		} else if (auto channel = peer->asChannel()) {
			if (channel->migrateFrom()) {
				App::main()->deleteConversation(channel->migrateFrom());
			}
			Auth().api().leaveChannel(channel);
		}
	});

	// Invalidates _peer.
	controller()->showBackFromStack();
}

void HistoryWidget::peerMessagesUpdated(PeerId peer) {
	if (_peer && _list && peer == _peer->id) {
		updateHistoryGeometry();
		updateBotKeyboard();
		if (!_scroll->isHidden()) {
			bool unblock = isBlocked(), botStart = isBotStart(), joinChannel = isJoinChannel(), muteUnmute = isMuteUnmute();
			bool upd = (_unblock->isHidden() == unblock);
			if (!upd && !unblock) upd = (_botStart->isHidden() == botStart);
			if (!upd && !unblock && !botStart) upd = (_joinChannel->isHidden() == joinChannel);
			if (!upd && !unblock && !botStart && !joinChannel) upd = (_muteUnmute->isHidden() == muteUnmute);
			if (upd) {
				updateControlsVisibility();
				updateControlsGeometry();
			}
		}
	}
}

void HistoryWidget::peerMessagesUpdated() {
	if (_list) peerMessagesUpdated(_peer->id);
}

void HistoryWidget::grapWithoutTopBarShadow() {
	grabStart();
	_topShadow->hide();
}

void HistoryWidget::grabFinish() {
	_inGrab = false;
	updateControlsGeometry();
	_topShadow->show();
}

void HistoryWidget::repaintHistoryItem(
		not_null<const HistoryItem*> item) {
	// It is possible that repaintHistoryItem() will be called from
	// _scroll->setOwnedWidget() because it calls onScroll() that
	// sendSynteticMouseEvent() and it could lead to some Info layout
	// calling Auth().data().requestItemRepaint(), while we still are
	// in progrss of showing the history. Just ignore them for now :/
	if (!_list) {
		return;
	}

	auto itemHistory = item->history();
	if (itemHistory == _history || itemHistory == _migrated) {
		auto ms = getms();
		if (_lastScrolled + kSkipRepaintWhileScrollMs <= ms) {
			_list->repaintItem(item);
		} else {
			_updateHistoryItems.start(
				_lastScrolled + kSkipRepaintWhileScrollMs - ms);
		}
	}
}

void HistoryWidget::onUpdateHistoryItems() {
	if (!_list) return;

	auto ms = getms();
	if (_lastScrolled + kSkipRepaintWhileScrollMs <= ms) {
		_list->update();
	} else {
		_updateHistoryItems.start(_lastScrolled + kSkipRepaintWhileScrollMs - ms);
	}
}

PeerData *HistoryWidget::ui_getPeerForMouseAction() {
	return _peer;
}

void HistoryWidget::handlePendingHistoryUpdate() {
	if (hasPendingResizedItems() || _updateHistoryGeometryRequired) {
		if (_list) {
			updateHistoryGeometry();
			_list->update();
		} else {
			_updateHistoryGeometryRequired = false;
		}
	}
}

void HistoryWidget::resizeEvent(QResizeEvent *e) {
	//updateTabbedSelectorSectionShown();
	recountChatWidth();
	updateControlsGeometry();
}

void HistoryWidget::updateControlsGeometry() {
	_topBar->setGeometryToLeft(0, 0, width(), st::topBarHeight);

	moveFieldControls();

	auto scrollAreaTop = _topBar->bottomNoMargins();
	if (_pinnedBar) {
		_pinnedBar->cancel->moveToLeft(width() - _pinnedBar->cancel->width(), scrollAreaTop);
		scrollAreaTop += st::historyReplyHeight;
		_pinnedBar->shadow->setGeometryToLeft(0, scrollAreaTop, width(), st::lineWidth);
	}
	if (_scroll->y() != scrollAreaTop) {
		_scroll->moveToLeft(0, scrollAreaTop);
		_fieldAutocomplete->setBoundings(_scroll->geometry());
	}
	if (_reportSpamPanel) {
		_reportSpamPanel->setGeometryToLeft(0, _scroll->y(), width(), _reportSpamPanel->height());
	}

	updateHistoryGeometry(false, false, { ScrollChangeAdd, App::main() ? App::main()->contentScrollAddToY() : 0 });

	updateFieldSize();

	updateHistoryDownPosition();

	if (_membersDropdown) {
		_membersDropdown->setMaxHeight(countMembersDropdownHeightMax());
	}

	switch (_attachDrag) {
	case DragState::Files:
		_attachDragDocument->resize(width() - st::dragMargin.left() - st::dragMargin.right(), height() - st::dragMargin.top() - st::dragMargin.bottom());
		_attachDragDocument->move(st::dragMargin.left(), st::dragMargin.top());
	break;
	case DragState::PhotoFiles:
		_attachDragDocument->resize(width() - st::dragMargin.left() - st::dragMargin.right(), (height() - st::dragMargin.top() - st::dragMargin.bottom()) / 2);
		_attachDragDocument->move(st::dragMargin.left(), st::dragMargin.top());
		_attachDragPhoto->resize(_attachDragDocument->width(), _attachDragDocument->height());
		_attachDragPhoto->move(st::dragMargin.left(), height() - _attachDragPhoto->height() - st::dragMargin.bottom());
	break;
	case DragState::Image:
		_attachDragPhoto->resize(width() - st::dragMargin.left() - st::dragMargin.right(), height() - st::dragMargin.top() - st::dragMargin.bottom());
		_attachDragPhoto->move(st::dragMargin.left(), st::dragMargin.top());
	break;
	}

	auto topShadowLeft = (Adaptive::OneColumn() || _inGrab) ? 0 : st::lineWidth;
	auto topShadowRight = (Adaptive::ThreeColumn() && !_inGrab && _peer) ? st::lineWidth : 0;
	_topShadow->setGeometryToLeft(
		topShadowLeft,
		_topBar->bottomNoMargins(),
		width() - topShadowLeft - topShadowRight,
		st::lineWidth);
}

void HistoryWidget::itemRemoved(not_null<const HistoryItem*> item) {
	if (item == _replyEditMsg) {
		if (_editMsgId) {
			cancelEdit();
		} else {
			cancelReply();
		}
	}
	if (item == _replyReturn) {
		calcNextReplyReturn();
	}
	if (_pinnedBar && item->id == _pinnedBar->msgId) {
		pinnedMsgVisibilityUpdated();
	}
	if (_kbReplyTo && item == _kbReplyTo) {
		onKbToggle();
		_kbReplyTo = 0;
	}
	auto found = ranges::find(_toForward, item);
	if (found != _toForward.end()) {
		_toForward.erase(found);
		updateForwardingTexts();
		if (_toForward.empty()) {
			updateControlsVisibility();
			updateControlsGeometry();
		}
	}
}

void HistoryWidget::itemEdited(HistoryItem *item) {
	if (item == _replyEditMsg) {
		updateReplyEditTexts(true);
	}
	if (_pinnedBar && item->id == _pinnedBar->msgId) {
		updatePinnedBar(true);
	}
}

void HistoryWidget::updateScrollColors() {
	_scroll->updateBars();
}

MsgId HistoryWidget::replyToId() const {
	return _replyToId ? _replyToId : (_kbReplyTo ? _kbReplyTo->id : 0);
}

int HistoryWidget::countInitialScrollTop() {
	auto result = ScrollMax;
	if (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem)) {
		result = _list->historyScrollTop();
	} else if (_showAtMsgId && (_showAtMsgId > 0 || -_showAtMsgId < ServerMaxMsgId)) {
		auto item = getItemFromHistoryOrMigrated(_showAtMsgId);
		auto itemTop = _list->itemTop(item);
		if (itemTop < 0) {
			setMsgId(0);
			return countInitialScrollTop();
		} else {
			result = itemTopForHighlight(item);
			enqueueMessageHighlight(item);
		}
	} else if (_history->unreadBar || (_migrated && _migrated->unreadBar)) {
		result = unreadBarTop();
	} else {
		return countAutomaticScrollTop();
	}
	return qMin(result, _scroll->scrollTopMax());
}

int HistoryWidget::countAutomaticScrollTop() {
	auto result = ScrollMax;
	if (_migrated && _migrated->showFrom) {
		result = _list->itemTop(_migrated->showFrom);
		if (result < _scroll->scrollTopMax() + HistoryMessageUnreadBar::height() - HistoryMessageUnreadBar::marginTop()) {
			_migrated->addUnreadBar();
			if (hasPendingResizedItems()) {
				updateListSize();
			}
			if (_migrated->unreadBar) {
				setMsgId(ShowAtUnreadMsgId);
				result = countInitialScrollTop();
				App::wnd()->checkHistoryActivation();
				return result;
			}
		}
	} else if (_history->showFrom) {
		result = _list->itemTop(_history->showFrom);
		if (result < _scroll->scrollTopMax() + HistoryMessageUnreadBar::height() - HistoryMessageUnreadBar::marginTop()) {
			_history->addUnreadBar();
			if (hasPendingResizedItems()) {
				updateListSize();
			}
			if (_history->unreadBar) {
				setMsgId(ShowAtUnreadMsgId);
				result = countInitialScrollTop();
				App::wnd()->checkHistoryActivation();
				return result;
			}
		}
	}
	return qMin(result, _scroll->scrollTopMax());
}

void HistoryWidget::updateHistoryGeometry(bool initial, bool loadedDown, const ScrollChange &change) {
	if (!_history || (initial && _historyInited) || (!initial && !_historyInited)) return;
	if (_firstLoadRequest || _a_show.animating()) {
		return; // scrollTopMax etc are not working after recountHistoryGeometry()
	}

	auto newScrollHeight = height() - _topBar->height();
	if (!editingMessage() && (isBlocked() || isBotStart() || isJoinChannel() || isMuteUnmute())) {
		newScrollHeight -= _unblock->height();
	} else {
		if (editingMessage() || _canSendMessages) {
			newScrollHeight -= (_field->height() + 2 * st::historySendPadding);
		} else if (isRestrictedWrite()) {
			newScrollHeight -= _unblock->height();
		}
		if (_editMsgId || replyToId() || readyToForward() || (_previewData && _previewData->pendingTill >= 0)) {
			newScrollHeight -= st::historyReplyHeight;
		}
		if (_kbShown) {
			newScrollHeight -= _kbScroll->height();
		}
	}
	if (_pinnedBar) {
		newScrollHeight -= st::historyReplyHeight;
	}
	auto wasScrollTop = _scroll->scrollTop();
	auto wasScrollTopMax = _scroll->scrollTopMax();
	auto wasAtBottom = wasScrollTop + 1 > wasScrollTopMax;
	auto needResize = (_scroll->width() != width()) || (_scroll->height() != newScrollHeight);
	if (needResize) {
		_scroll->resize(width(), newScrollHeight);
		// on initial updateListSize we didn't put the _scroll->scrollTop correctly yet
		// so visibleAreaUpdated() call will erase it with the new (undefined) value
		if (!initial) {
			visibleAreaUpdated();
		}

		_fieldAutocomplete->setBoundings(_scroll->geometry());
		if (!_historyDownShown.animating()) {
			// _historyDown is a child widget of _scroll, not me.
			_historyDown->moveToRight(st::historyToDownPosition.x(), _scroll->height() - _historyDown->height() - st::historyToDownPosition.y());
			if (!_unreadMentionsShown.animating()) {
				// _unreadMentions is a child widget of _scroll, not me.
				auto additionalSkip = _historyDownIsShown ? (_historyDown->height() + st::historyUnreadMentionsSkip) : 0;
				_unreadMentions->moveToRight(st::historyToDownPosition.x(), _scroll->height() - additionalSkip - st::historyToDownPosition.y());
			}
		}

		controller()->floatPlayerAreaUpdated().notify(true);
	}

	updateListSize();
	_updateHistoryGeometryRequired = false;

	if ((!initial && !wasAtBottom) || (loadedDown && (!_history->showFrom || _history->unreadBar || _history->loadedAtBottom()) && (!_migrated || !_migrated->showFrom || _migrated->unreadBar || _history->loadedAtBottom()))) {
		auto toY = qMin(_list->historyScrollTop(), _scroll->scrollTopMax());
		if (change.type == ScrollChangeAdd) {
			toY += change.value;
		} else if (change.type == ScrollChangeNoJumpToBottom) {
			toY = wasScrollTop;
		} else if (_addToScroll) {
			toY += _addToScroll;
			_addToScroll = 0;
		}
		toY = snap(toY, 0, _scroll->scrollTopMax());
		if (_scroll->scrollTop() == toY) {
			visibleAreaUpdated();
		} else {
			synteticScrollToY(toY);
		}
		return;
	}

	if (initial) {
		_historyInited = true;
		_scrollToAnimation.finish();
	}
	auto newScrollTop = initial ? countInitialScrollTop() : countAutomaticScrollTop();
	if (_scroll->scrollTop() == newScrollTop) {
		visibleAreaUpdated();
	} else {
		synteticScrollToY(newScrollTop);
	}
}

void HistoryWidget::updateListSize() {
	_list->recountHistoryGeometry();
	auto washidden = _scroll->isHidden();
	if (washidden) {
		_scroll->show();
	}
	_list->updateSize();
	if (washidden) {
		_scroll->hide();
	}
	_updateHistoryGeometryRequired = true;
}

int HistoryWidget::unreadBarTop() const {
	auto getUnreadBar = [this]() -> HistoryItem* {
		if (_migrated && _migrated->unreadBar) {
			return _migrated->unreadBar;
		}
		if (_history->unreadBar) {
			return _history->unreadBar;
		}
		return nullptr;
	};
	if (HistoryItem *bar = getUnreadBar()) {
		int result = _list->itemTop(bar) + HistoryMessageUnreadBar::marginTop();
		if (bar->Has<HistoryMessageDate>()) {
			result += bar->Get<HistoryMessageDate>()->height();
		}
		return result;
	}
	return -1;
}

void HistoryWidget::addMessagesToFront(PeerData *peer, const QVector<MTPMessage> &messages) {
	_list->messagesReceived(peer, messages);
	if (!_firstLoadRequest) {
		updateHistoryGeometry();
		adjustHighlightedMessageToMigrated();
		updateBotKeyboard();
	}
}

void HistoryWidget::addMessagesToBack(PeerData *peer, const QVector<MTPMessage> &messages) {
	_list->messagesReceivedDown(peer, messages);
	if (!_firstLoadRequest) {
		updateHistoryGeometry(false, true, { ScrollChangeNoJumpToBottom, 0 });
	}
}

void HistoryWidget::countHistoryShowFrom() {
	if (_migrated && _showAtMsgId == ShowAtUnreadMsgId && _migrated->unreadCount()) {
		_migrated->updateShowFrom();
	}
	if ((_migrated && _migrated->showFrom) || _showAtMsgId != ShowAtUnreadMsgId || !_history->unreadCount()) {
		_history->showFrom = nullptr;
		return;
	}
	_history->updateShowFrom();
}

void HistoryWidget::updateBotKeyboard(History *h, bool force) {
	if (h && h != _history && h != _migrated) {
		return;
	}

	bool changed = false;
	bool wasVisible = _kbShown || _kbReplyTo;
	if ((_replyToId && !_replyEditMsg) || _editMsgId || !_history) {
		changed = _keyboard->updateMarkup(nullptr, force);
	} else if (_replyToId && _replyEditMsg) {
		changed = _keyboard->updateMarkup(_replyEditMsg, force);
	} else {
		HistoryItem *keyboardItem = _history->lastKeyboardId ? App::histItemById(_channel, _history->lastKeyboardId) : nullptr;
		changed = _keyboard->updateMarkup(keyboardItem, force);
	}
	updateCmdStartShown();
	if (!changed) return;

	bool hasMarkup = _keyboard->hasMarkup(), forceReply = _keyboard->forceReply() && (!_replyToId || !_replyEditMsg);
	if (hasMarkup || forceReply) {
		if (_keyboard->singleUse() && _keyboard->hasMarkup() && _keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId) && _history->lastKeyboardUsed) {
			_history->lastKeyboardHiddenId = _history->lastKeyboardId;
		}
		if (!isBotStart() && !isBlocked() && _canSendMessages && (wasVisible || (_replyToId && _replyEditMsg) || (!_field->hasSendText() && !kbWasHidden()))) {
			if (!_a_show.animating()) {
				if (hasMarkup) {
					_kbScroll->show();
					_tabbedSelectorToggle->hide();
					_botKeyboardHide->show();
				} else {
					_kbScroll->hide();
					_tabbedSelectorToggle->show();
					_botKeyboardHide->hide();
				}
				_botKeyboardShow->hide();
				_botCommandStart->hide();
			}
			int32 maxh = hasMarkup ? qMin(_keyboard->height(), st::historyComposeFieldMaxHeight - (st::historyComposeFieldMaxHeight / 2)) : 0;
			_field->setMaxHeight(st::historyComposeFieldMaxHeight - maxh);
			_kbShown = hasMarkup;
			_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0;
			if (_kbReplyTo && !_replyToId) {
				updateReplyToName();
				_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_kbReplyTo->inReplyText()), _textDlgOptions);
				_fieldBarCancel->show();
				updateMouseTracking();
			}
		} else {
			if (!_a_show.animating()) {
				_kbScroll->hide();
				_tabbedSelectorToggle->show();
				_botKeyboardHide->hide();
				_botKeyboardShow->show();
				_botCommandStart->hide();
			}
			_field->setMaxHeight(st::historyComposeFieldMaxHeight);
			_kbShown = false;
			_kbReplyTo = 0;
			if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyToId) {
				_fieldBarCancel->hide();
				updateMouseTracking();
			}
		}
	} else {
		if (!_scroll->isHidden()) {
			_kbScroll->hide();
			_tabbedSelectorToggle->show();
			_botKeyboardHide->hide();
			_botKeyboardShow->hide();
			_botCommandStart->show();
		}
		_field->setMaxHeight(st::historyComposeFieldMaxHeight);
		_kbShown = false;
		_kbReplyTo = 0;
		if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_replyToId && !_editMsgId) {
			_fieldBarCancel->hide();
			updateMouseTracking();
		}
	}
	updateControlsGeometry();
	update();
}

void HistoryWidget::updateHistoryDownPosition() {
	// _historyDown is a child widget of _scroll, not me.
	auto top = anim::interpolate(0, _historyDown->height() + st::historyToDownPosition.y(), _historyDownShown.current(_historyDownIsShown ? 1. : 0.));
	_historyDown->moveToRight(st::historyToDownPosition.x(), _scroll->height() - top);
	auto shouldBeHidden = !_historyDownIsShown && !_historyDownShown.animating();
	if (shouldBeHidden != _historyDown->isHidden()) {
		_historyDown->setVisible(!shouldBeHidden);
	}
	updateUnreadMentionsPosition();
}

void HistoryWidget::updateHistoryDownVisibility() {
	if (_a_show.animating()) return;

	auto haveUnreadBelowBottom = [this](History *history) {
		if (!_list || !history || history->unreadCount() <= 0) {
			return false;
		}
		if (!history->showFrom || history->showFrom->detached()) {
			return false;
		}
		return (_list->itemTop(history->showFrom) >= _scroll->scrollTop() + _scroll->height());
	};
	auto historyDownIsVisible = [this, &haveUnreadBelowBottom] {
		if (!_history || _firstLoadRequest) {
			return false;
		}
		if (!_history->loadedAtBottom() || _replyReturn) {
			return true;
		}
		if (_scroll->scrollTop() + st::historyToDownShownAfter < _scroll->scrollTopMax()) {
			return true;
		}
		if (haveUnreadBelowBottom(_history) || haveUnreadBelowBottom(_migrated)) {
			return true;
		}
		return false;
	};
	auto historyDownIsShown = historyDownIsVisible();
	if (_historyDownIsShown != historyDownIsShown) {
		_historyDownIsShown = historyDownIsShown;
		_historyDownShown.start([this] { updateHistoryDownPosition(); }, _historyDownIsShown ? 0. : 1., _historyDownIsShown ? 1. : 0., st::historyToDownDuration);
	}
}

void HistoryWidget::updateUnreadMentionsPosition() {
	// _unreadMentions is a child widget of _scroll, not me.
	auto right = anim::interpolate(-_unreadMentions->width(), st::historyToDownPosition.x(), _unreadMentionsShown.current(_unreadMentionsIsShown ? 1. : 0.));
	auto shift = anim::interpolate(0, _historyDown->height() + st::historyUnreadMentionsSkip, _historyDownShown.current(_historyDownIsShown ? 1. : 0.));
	auto top = _scroll->height() - _unreadMentions->height() - st::historyToDownPosition.y() - shift;
	_unreadMentions->moveToRight(right, top);
	auto shouldBeHidden = !_unreadMentionsIsShown && !_unreadMentionsShown.animating();
	if (shouldBeHidden != _unreadMentions->isHidden()) {
		_unreadMentions->setVisible(!shouldBeHidden);
	}
}

void HistoryWidget::updateUnreadMentionsVisibility() {
	if (_a_show.animating()) return;

	auto showUnreadMentions = _peer && (_peer->isChat() || _peer->isMegagroup());
	if (showUnreadMentions) {
		Auth().api().preloadEnoughUnreadMentions(_history);
	}
	auto unreadMentionsIsVisible = [this, showUnreadMentions] {
		if (!showUnreadMentions || _firstLoadRequest) {
			return false;
		}
		return (_history->getUnreadMentionsLoadedCount() > 0);
	};
	auto unreadMentionsIsShown = unreadMentionsIsVisible();
	if (unreadMentionsIsShown) {
		_unreadMentions->setUnreadCount(_history->getUnreadMentionsCount());
	}
	if (_unreadMentionsIsShown != unreadMentionsIsShown) {
		_unreadMentionsIsShown = unreadMentionsIsShown;
		_unreadMentionsShown.start([this] { updateUnreadMentionsPosition(); }, _unreadMentionsIsShown ? 0. : 1., _unreadMentionsIsShown ? 1. : 0., st::historyToDownDuration);
	}
}

void HistoryWidget::mousePressEvent(QMouseEvent *e) {
	_replyForwardPressed = QRect(0, _field->y() - st::historySendPadding - st::historyReplyHeight, st::historyReplySkip, st::historyReplyHeight).contains(e->pos());
	if (_replyForwardPressed && !_fieldBarCancel->isHidden()) {
		updateField();
	} else if (_inReplyEditForward) {
		if (readyToForward()) {
			const auto items = std::move(_toForward);
			App::main()->cancelForwarding(_history);
			Window::ShowForwardMessagesBox(ranges::view::all(
				items
			) | ranges::view::transform([](not_null<HistoryItem*> item) {
				return item->fullId();
			}) | ranges::to_vector);
		} else {
			Ui::showPeerHistory(_peer, _editMsgId ? _editMsgId : replyToId());
		}
	} else if (_inPinnedMsg) {
		Assert(_pinnedBar != nullptr);
		Ui::showPeerHistory(_peer, _pinnedBar->msgId);
	}
}

void HistoryWidget::keyPressEvent(QKeyEvent *e) {
	if (!_history) return;

	if (e->key() == Qt::Key_Escape) {
		e->ignore();
	} else if (e->key() == Qt::Key_Back) {
		controller()->showBackFromStack();
		emit cancelled();
	} else if (e->key() == Qt::Key_PageDown) {
		_scroll->keyPressEvent(e);
	} else if (e->key() == Qt::Key_PageUp) {
		_scroll->keyPressEvent(e);
	} else if (e->key() == Qt::Key_Down) {
		if (!(e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier))) {
			_scroll->keyPressEvent(e);
		}
	} else if (e->key() == Qt::Key_Up) {
		if (!(e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier))) {
			if (_history && _history->lastSentMsg && _history->lastSentMsg->canEdit(::date(unixtime()))) {
				if (_field->isEmpty() && !_editMsgId && !_replyToId) {
					App::contextItem(_history->lastSentMsg);
					onEditMessage();
					return;
				}
			}
			_scroll->keyPressEvent(e);
		}
	} else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
		onListEnterPressed();
	} else {
		e->ignore();
	}
}

void HistoryWidget::onFieldTabbed() {
	if (!_fieldAutocomplete->isHidden()) {
		_fieldAutocomplete->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab);
	}
}

bool HistoryWidget::onStickerSend(DocumentData *sticker) {
	if (auto megagroup = _peer ? _peer->asMegagroup() : nullptr) {
		if (megagroup->restricted(ChannelRestriction::f_send_stickers)) {
			Ui::show(
				Box<InformBox>(lang(lng_restricted_send_stickers)),
				LayerOption::KeepOther);
			return false;
		}
	}
	return sendExistingDocument(sticker, QString());
}

void HistoryWidget::onPhotoSend(PhotoData *photo) {
	if (auto megagroup = _peer ? _peer->asMegagroup() : nullptr) {
		if (megagroup->restricted(ChannelRestriction::f_send_media)) {
			Ui::show(
				Box<InformBox>(lang(lng_restricted_send_media)),
				LayerOption::KeepOther);
			return;
		}
	}
	sendExistingPhoto(photo, QString());
}

void HistoryWidget::onInlineResultSend(
		InlineBots::Result *result,
		UserData *bot) {
	if (!_peer || !_peer->canWrite() || !result) {
		return;
	}

	auto errorText = result->getErrorOnSend(_history);
	if (!errorText.isEmpty()) {
		Ui::show(Box<InformBox>(errorText));
		return;
	}

	auto options = ApiWrap::SendOptions(_history);
	options.clearDraft = true;
	options.replyTo = replyToId();
	options.generateLocal = true;
	Auth().api().sendAction(options);

	uint64 randomId = rand_value<uint64>();
	FullMsgId newId(_channel, clientMsgId());

	auto flags = NewMessageFlags(_peer) | MTPDmessage::Flag::f_media;
	auto sendFlags = MTPmessages_SendInlineBotResult::Flag::f_clear_draft | 0;
	if (options.replyTo) {
		flags |= MTPDmessage::Flag::f_reply_to_msg_id;
		sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_reply_to_msg_id;
	}
	bool channelPost = _peer->isChannel() && !_peer->isMegagroup();
	bool silentPost = channelPost && _peer->notifySilentPosts();
	if (channelPost) {
		flags |= MTPDmessage::Flag::f_views;
		flags |= MTPDmessage::Flag::f_post;
	}
	if (!channelPost) {
		flags |= MTPDmessage::Flag::f_from_id;
	} else if (_peer->asChannel()->addsSignature()) {
		flags |= MTPDmessage::Flag::f_post_author;
	}
	if (silentPost) {
		sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_silent;
	}
	if (bot) {
		flags |= MTPDmessage::Flag::f_via_bot_id;
	}

	auto messageFromId = channelPost ? 0 : Auth().userId();
	auto messagePostAuthor = channelPost ? (Auth().user()->firstName + ' ' + Auth().user()->lastName) : QString();
	MTPint messageDate = MTP_int(unixtime());
	UserId messageViaBotId = bot ? peerToUser(bot->id) : 0;
	MsgId messageId = newId.msg;

	result->addToHistory(
		_history,
		flags,
		messageId,
		messageFromId,
		messageDate,
		messageViaBotId,
		options.replyTo,
		messagePostAuthor);

	_history->sendRequestId = MTP::send(
		MTPmessages_SendInlineBotResult(
			MTP_flags(sendFlags),
			_peer->input,
			MTP_int(options.replyTo),
			MTP_long(randomId),
			MTP_long(result->getQueryId()),
			MTP_string(result->getId())),
		App::main()->rpcDone(&MainWidget::sentUpdatesReceived),
		App::main()->rpcFail(&MainWidget::sendMessageFail),
		0,
		0,
		_history->sendRequestId);
	App::main()->finishForwarding(_history);

	App::historyRegRandom(randomId, newId);

	clearFieldText();
	_saveDraftText = true;
	_saveDraftStart = getms();
	onDraftSave();

	RecentInlineBots &bots(cRefRecentInlineBots());
	int32 index = bots.indexOf(bot);
	if (index) {
		if (index > 0) {
			bots.removeAt(index);
		} else if (bots.size() >= RecentInlineBotsLimit) {
			bots.resize(RecentInlineBotsLimit - 1);
		}
		bots.push_front(bot);
		Local::writeRecentHashtagsAndBots();
	}

	hideSelectorControlsAnimated();

	_field->setFocus();
}

HistoryWidget::PinnedBar::PinnedBar(MsgId msgId, HistoryWidget *parent)
: msgId(msgId)
, cancel(parent, st::historyReplyCancel)
, shadow(parent) {
}

HistoryWidget::PinnedBar::~PinnedBar() {
	cancel.destroyDelayed();
	shadow.destroyDelayed();
}

void HistoryWidget::updatePinnedBar(bool force) {
	update();
	if (!_pinnedBar) {
		return;
	}
	if (!force) {
		if (_pinnedBar->msg) {
			return;
		}
	}

	Assert(_history != nullptr);
	if (!_pinnedBar->msg) {
		_pinnedBar->msg = App::histItemById(_history->channelId(), _pinnedBar->msgId);
	}
	if (_pinnedBar->msg) {
		_pinnedBar->text.setText(st::messageTextStyle, TextUtilities::Clean(_pinnedBar->msg->notificationText()), _textDlgOptions);
		update();
	} else if (force) {
		if (auto channel = _peer ? _peer->asChannel() : nullptr) {
			channel->clearPinnedMessage();
		}
		destroyPinnedBar();
		updateControlsGeometry();
	}
}

bool HistoryWidget::pinnedMsgVisibilityUpdated() {
	auto result = false;
	auto pinnedId = [&] {
		if (auto channel = _peer ? _peer->asChannel() : nullptr) {
			return channel->pinnedMessageId();
		}
		return 0;
	}();
	if (pinnedId && !_peer->asChannel()->canPinMessages()) {
		auto it = Global::HiddenPinnedMessages().constFind(_peer->id);
		if (it != Global::HiddenPinnedMessages().cend()) {
			if (it.value() == pinnedId) {
				pinnedId = 0;
			} else {
				Global::RefHiddenPinnedMessages().remove(_peer->id);
				Local::writeUserSettings();
			}
		}
	}
	if (pinnedId) {
		if (!_pinnedBar) {
			_pinnedBar = std::make_unique<PinnedBar>(pinnedId, this);
			if (_a_show.animating()) {
				_pinnedBar->cancel->hide();
				_pinnedBar->shadow->hide();
			} else {
				_pinnedBar->cancel->show();
				_pinnedBar->shadow->show();
			}
			connect(_pinnedBar->cancel, SIGNAL(clicked()), this, SLOT(onPinnedHide()));
			orderWidgets();

			updatePinnedBar();
			result = true;

			if (_scroll->scrollTop() != unreadBarTop()) {
				synteticScrollToY(_scroll->scrollTop() + st::historyReplyHeight);
			}
		} else if (_pinnedBar->msgId != pinnedId) {
			_pinnedBar->msgId = pinnedId;
			_pinnedBar->msg = nullptr;
			_pinnedBar->text.clear();
			updatePinnedBar();
		}
		if (!_pinnedBar->msg) {
			Auth().api().requestMessageData(
				_peer->asChannel(),
				_pinnedBar->msgId,
				replyEditMessageDataCallback());
		}
	} else if (_pinnedBar) {
		destroyPinnedBar();
		result = true;
		if (_scroll->scrollTop() != unreadBarTop()) {
			synteticScrollToY(_scroll->scrollTop() - st::historyReplyHeight);
		}
		updateControlsGeometry();
	}
	return result;
}

void HistoryWidget::destroyPinnedBar() {
	_pinnedBar.reset();
	_inPinnedMsg = false;
}

bool HistoryWidget::sendExistingDocument(
		DocumentData *doc,
		const QString &caption) {
	if (!_peer || !_peer->canWrite() || !doc) {
		return false;
	}

	MTPInputDocument mtpInput = doc->mtpInput();
	if (mtpInput.type() == mtpc_inputDocumentEmpty) {
		return false;
	}

	auto options = ApiWrap::SendOptions(_history);
	options.clearDraft = false;
	options.replyTo = replyToId();
	options.generateLocal = true;
	Auth().api().sendAction(options);

	uint64 randomId = rand_value<uint64>();
	FullMsgId newId(_channel, clientMsgId());

	auto flags = NewMessageFlags(_peer) | MTPDmessage::Flag::f_media;
	auto sendFlags = MTPmessages_SendMedia::Flags(0);
	if (options.replyTo) {
		flags |= MTPDmessage::Flag::f_reply_to_msg_id;
		sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
	}
	bool channelPost = _peer->isChannel() && !_peer->isMegagroup();
	bool silentPost = channelPost && _peer->notifySilentPosts();
	if (channelPost) {
		flags |= MTPDmessage::Flag::f_views;
		flags |= MTPDmessage::Flag::f_post;
	}
	if (!channelPost) {
		flags |= MTPDmessage::Flag::f_from_id;
	} else if (_peer->asChannel()->addsSignature()) {
		flags |= MTPDmessage::Flag::f_post_author;
	}
	if (silentPost) {
		sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
	}
	auto messageFromId = channelPost ? 0 : Auth().userId();
	auto messagePostAuthor = channelPost ? (Auth().user()->firstName + ' ' + Auth().user()->lastName) : QString();
	_history->addNewDocument(
		newId.msg,
		flags,
		0,
		options.replyTo,
		date(MTP_int(unixtime())),
		messageFromId,
		messagePostAuthor,
		doc,
		caption,
		MTPnullMarkup);

	_history->sendRequestId = MTP::send(
		MTPmessages_SendMedia(
			MTP_flags(sendFlags),
			_peer->input,
			MTP_int(options.replyTo),
			MTP_inputMediaDocument(
				MTP_flags(0),
				mtpInput,
				MTP_string(caption),
				MTPint()),
			MTP_long(randomId),
			MTPnullMarkup),
		App::main()->rpcDone(&MainWidget::sentUpdatesReceived),
		App::main()->rpcFail(&MainWidget::sendMessageFail),
		0,
		0,
		_history->sendRequestId);
	App::main()->finishForwarding(_history);

	if (doc->sticker()) App::main()->incrementSticker(doc);

	App::historyRegRandom(randomId, newId);

	if (_fieldAutocomplete->stickersShown()) {
		clearFieldText();
		//_saveDraftText = true;
		//_saveDraftStart = getms();
		//onDraftSave();
		onCloudDraftSave(); // won't be needed if SendInlineBotResult will clear the cloud draft
	}

	hideSelectorControlsAnimated();

	_field->setFocus();
	return true;
}

void HistoryWidget::sendExistingPhoto(
		PhotoData *photo,
		const QString &caption) {
	if (!_peer || !_peer->canWrite() || !photo) {
		return;
	}

	auto options = ApiWrap::SendOptions(_history);
	options.clearDraft = false;
	options.replyTo = replyToId();
	options.generateLocal = true;
	Auth().api().sendAction(options);

	uint64 randomId = rand_value<uint64>();
	FullMsgId newId(_channel, clientMsgId());

	auto flags = NewMessageFlags(_peer) | MTPDmessage::Flag::f_media;
	auto sendFlags = MTPmessages_SendMedia::Flags(0);
	if (options.replyTo) {
		flags |= MTPDmessage::Flag::f_reply_to_msg_id;
		sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
	}
	bool channelPost = _peer->isChannel() && !_peer->isMegagroup();
	bool silentPost = channelPost && _peer->notifySilentPosts();
	if (channelPost) {
		flags |= MTPDmessage::Flag::f_views;
		flags |= MTPDmessage::Flag::f_post;
	}
	if (!channelPost) {
		flags |= MTPDmessage::Flag::f_from_id;
	} else if (_peer->asChannel()->addsSignature()) {
		flags |= MTPDmessage::Flag::f_post_author;
	}
	if (silentPost) {
		sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
	}
	auto messageFromId = channelPost ? 0 : Auth().userId();
	auto messagePostAuthor = channelPost ? (Auth().user()->firstName + ' ' + Auth().user()->lastName) : QString();
	_history->addNewPhoto(
		newId.msg,
		flags,
		0,
		options.replyTo,
		date(MTP_int(unixtime())),
		messageFromId,
		messagePostAuthor,
		photo,
		caption,
		MTPnullMarkup);

	_history->sendRequestId = MTP::send(
		MTPmessages_SendMedia(
			MTP_flags(sendFlags),
			_peer->input,
			MTP_int(options.replyTo),
			MTP_inputMediaPhoto(
				MTP_flags(0),
				MTP_inputPhoto(MTP_long(photo->id), MTP_long(photo->access)),
				MTP_string(caption),
				MTPint()),
			MTP_long(randomId),
			MTPnullMarkup),
		App::main()->rpcDone(&MainWidget::sentUpdatesReceived),
		App::main()->rpcFail(&MainWidget::sendMessageFail),
		0,
		0,
		_history->sendRequestId);
	App::main()->finishForwarding(_history);

	App::historyRegRandom(randomId, newId);

	hideSelectorControlsAnimated();

	_field->setFocus();
}

void HistoryWidget::setFieldText(const TextWithTags &textWithTags, TextUpdateEvents events, Ui::FlatTextarea::UndoHistoryAction undoHistoryAction) {
	_textUpdateEvents = events;
	_field->setTextWithTags(textWithTags, undoHistoryAction);
	_field->moveCursor(QTextCursor::End);
	_textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping;

	_previewCancelled = false;
	_previewData = nullptr;
	if (_previewRequest) {
		MTP::cancel(_previewRequest);
		_previewRequest = 0;
	}
	_previewLinks.clear();
}

void HistoryWidget::onReplyToMessage() {
	auto to = App::contextItem();
	if (!to || to->id <= 0 || !_canSendMessages) return;

	if (to->history() == _migrated) {
		if (to->isGroupMigrate() && !_history->isEmpty() && _history->blocks.front()->items.front()->isGroupMigrate() && _history != _migrated) {
			App::contextItem(_history->blocks.front()->items.front());
			onReplyToMessage();
			App::contextItem(to);
		} else {
			if (to->id < 0 || to->serviceMsg()) {
				Ui::show(Box<InformBox>(lang(lng_reply_cant)));
			} else {
				Ui::show(Box<ConfirmBox>(lang(lng_reply_cant_forward), lang(lng_selected_forward), base::lambda_guarded(this, [this] {
					auto item = App::contextItem();
					if (!item || item->id < 0 || item->serviceMsg()) return;

					App::main()->setForwardDraft(
						_peer->id,
						{ 1, item->fullId() });
				})));
			}
		}
		return;
	}

	App::main()->cancelForwarding(_history);

	if (_editMsgId) {
		if (auto localDraft = _history->localDraft()) {
			localDraft->msgId = to->id;
		} else {
			_history->setLocalDraft(std::make_unique<Data::Draft>(TextWithTags(), to->id, MessageCursor(), false));
		}
	} else {
		_replyEditMsg = to;
		_replyToId = to->id;
		_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_replyEditMsg->inReplyText()), _textDlgOptions);

		updateBotKeyboard();

		if (!_field->isHidden()) _fieldBarCancel->show();
		updateMouseTracking();
		updateReplyToName();
		updateControlsGeometry();
		updateField();
	}

	_saveDraftText = true;
	_saveDraftStart = getms();
	onDraftSave();

	_field->setFocus();
}

void HistoryWidget::onEditMessage() {
	auto to = App::contextItem();
	if (!to) return;

	if (auto media = to->getMedia()) {
		if (media->canEditCaption()) {
			Ui::show(Box<EditCaptionBox>(media, to->fullId()));
			return;
		}
	}

	if (_recording) {
		// Just fix some strange inconsistency.
		_send->clearState();
	}
	if (!_editMsgId) {
		if (_replyToId || !_field->isEmpty()) {
			_history->setLocalDraft(std::make_unique<Data::Draft>(_field, _replyToId, _previewCancelled));
		} else {
			_history->clearLocalDraft();
		}
	}

	auto original = to->originalText();
	auto editData = TextWithTags { TextUtilities::ApplyEntities(original), ConvertEntitiesToTextTags(original.entities) };
	auto cursor = MessageCursor { editData.text.size(), editData.text.size(), QFIXED_MAX };
	_history->setEditDraft(std::make_unique<Data::Draft>(editData, to->id, cursor, false));
	applyDraft(false);

	_previewData = nullptr;
	if (auto media = to->getMedia()) {
		if (media->type() == MediaTypeWebPage) {
			_previewData = static_cast<HistoryWebPage*>(media)->webpage();
			updatePreview();
		}
	}
	if (!_previewData) {
		onPreviewParse();
	}

	updateBotKeyboard();

	if (!_field->isHidden()) _fieldBarCancel->show();
	updateFieldPlaceholder();
	updateMouseTracking();
	updateReplyToName();
	updateControlsGeometry();
	updateField();

	_saveDraftText = true;
	_saveDraftStart = getms();
	onDraftSave();

	_field->setFocus();
}

void HistoryWidget::onPinMessage() {
	auto to = App::contextItem();
	if (!to || !to->canPin()) return;

	Ui::show(Box<PinMessageBox>(
		to->history()->peer->asChannel(),
		to->id));
}

void HistoryWidget::onUnpinMessage() {
	if (!_peer || !_peer->isChannel()) return;

	Ui::show(Box<ConfirmBox>(lang(lng_pinned_unpin_sure), lang(lng_pinned_unpin), base::lambda_guarded(this, [this] {
		auto channel = _peer ? _peer->asChannel() : nullptr;
		if (!channel) return;

		channel->clearPinnedMessage();

		Ui::hideLayer();
		MTP::send(
			MTPchannels_UpdatePinnedMessage(
				MTP_flags(0),
				channel->inputChannel,
				MTP_int(0)),
			rpcDone(&HistoryWidget::unpinDone));
	})));
}

void HistoryWidget::unpinDone(const MTPUpdates &updates) {
	if (App::main()) {
		App::main()->sentUpdatesReceived(updates);
	}
}

void HistoryWidget::onPinnedHide() {
	auto channel = _peer ? _peer->asChannel() : nullptr;
	auto pinnedId = channel->pinnedMessageId();
	if (!pinnedId) {
		if (pinnedMsgVisibilityUpdated()) {
			updateControlsGeometry();
			update();
		}
		return;
	}

	if (channel->canPinMessages()) {
		onUnpinMessage();
	} else {
		Global::RefHiddenPinnedMessages().insert(_peer->id, pinnedId);
		Local::writeUserSettings();
		if (pinnedMsgVisibilityUpdated()) {
			updateControlsGeometry();
			update();
		}
	}
}

void HistoryWidget::onCopyPostLink() {
	auto item = App::contextItem();
	if (!item || !item->hasDirectLink()) return;

	QApplication::clipboard()->setText(item->directLink());
}

bool HistoryWidget::lastForceReplyReplied(const FullMsgId &replyTo) const {
	if (replyTo.channel != _channel) {
		return false;
	}
	return _keyboard->forceReply()
		&& _keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)
		&& _keyboard->forMsgId().msg == replyTo.msg;
}

bool HistoryWidget::lastForceReplyReplied() const {
	return _keyboard->forceReply()
		&& _keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)
		&& _keyboard->forMsgId().msg == replyToId();
}

bool HistoryWidget::cancelReply(bool lastKeyboardUsed) {
	bool wasReply = false;
	if (_replyToId) {
		wasReply = true;

		_replyEditMsg = nullptr;
		_replyToId = 0;
		mouseMoveEvent(0);
		if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !_kbReplyTo) {
			_fieldBarCancel->hide();
			updateMouseTracking();
		}

		updateBotKeyboard();

		updateControlsGeometry();
		update();
	} else if (auto localDraft = (_history ? _history->localDraft() : nullptr)) {
		if (localDraft->msgId) {
			if (localDraft->textWithTags.text.isEmpty()) {
				_history->clearLocalDraft();
			} else {
				localDraft->msgId = 0;
			}
		}
	}
	if (wasReply) {
		_saveDraftText = true;
		_saveDraftStart = getms();
		onDraftSave();
	}
	if (!_editMsgId
		&& _keyboard->singleUse()
		&& _keyboard->forceReply()
		&& lastKeyboardUsed) {
		if (_kbReplyTo) {
			onKbToggle(false);
		}
	}
	return wasReply;
}

void HistoryWidget::cancelReplyAfterMediaSend(bool lastKeyboardUsed) {
	if (cancelReply(lastKeyboardUsed)) {
		onCloudDraftSave();
	}
}

int HistoryWidget::countMembersDropdownHeightMax() const {
	int result = height() - st::membersInnerDropdown.padding.top() - st::membersInnerDropdown.padding.bottom();
	result -= _tabbedSelectorToggle->height();
	accumulate_min(result, st::membersInnerHeightMax);
	return result;
}

void HistoryWidget::cancelEdit() {
	if (!_editMsgId) return;

	_replyEditMsg = nullptr;
	_editMsgId = 0;
	_history->clearEditDraft();
	applyDraft();

	if (_saveEditMsgRequestId) {
		MTP::cancel(_saveEditMsgRequestId);
		_saveEditMsgRequestId = 0;
	}

	_saveDraftText = true;
	_saveDraftStart = getms();
	onDraftSave();

	mouseMoveEvent(nullptr);
	if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !replyToId()) {
		_fieldBarCancel->hide();
		updateMouseTracking();
	}

	auto old = _textUpdateEvents;
	_textUpdateEvents = 0;
	onTextChange();
	_textUpdateEvents = old;

	if (!canWriteMessage()) {
		updateControlsVisibility();
	}
	updateBotKeyboard();
	updateFieldPlaceholder();

	updateControlsGeometry();
	update();
}

void HistoryWidget::onFieldBarCancel() {
	Ui::hideLayer();
	_replyForwardPressed = false;
	if (_previewData && _previewData->pendingTill >= 0) {
		_previewCancelled = true;
		previewCancel();

		_saveDraftText = true;
		_saveDraftStart = getms();
		onDraftSave();
	} else if (_editMsgId) {
		cancelEdit();
	} else if (readyToForward()) {
		App::main()->cancelForwarding(_history);
	} else if (_replyToId) {
		cancelReply();
	} else if (_kbReplyTo) {
		onKbToggle();
	}
}

void HistoryWidget::previewCancel() {
	MTP::cancel(base::take(_previewRequest));
	_previewData = nullptr;
	_previewLinks.clear();
	updatePreview();
	if (!_editMsgId && !_replyToId && !readyToForward() && !_kbReplyTo) {
		_fieldBarCancel->hide();
		updateMouseTracking();
	}
}

void HistoryWidget::onPreviewParse() {
	if (_previewCancelled) return;
	_field->parseLinks();
}

void HistoryWidget::onPreviewCheck() {
	auto previewRestricted = [this] {
		if (auto megagroup = _peer ? _peer->asMegagroup() : nullptr) {
			if (megagroup->restricted(ChannelRestriction::f_embed_links)) {
				return true;
			}
		}
		return false;
	};
	if (_previewCancelled || previewRestricted()) {
		MTP::cancel(base::take(_previewRequest));
		_previewData = nullptr;
		_previewLinks.clear();
		update();
		return;
	}
	auto linksList = _field->linksList();
	auto newLinks = linksList.join(' ');
	if (newLinks != _previewLinks) {
		MTP::cancel(base::take(_previewRequest));
		_previewLinks = newLinks;
		if (_previewLinks.isEmpty()) {
			if (_previewData && _previewData->pendingTill >= 0) previewCancel();
		} else {
			PreviewCache::const_iterator i = _previewCache.constFind(_previewLinks);
			if (i == _previewCache.cend()) {
				_previewRequest = MTP::send(MTPmessages_GetWebPagePreview(MTP_string(_previewLinks)), rpcDone(&HistoryWidget::gotPreview, _previewLinks));
			} else if (i.value()) {
				_previewData = App::webPage(i.value());
				updatePreview();
			} else {
				if (_previewData && _previewData->pendingTill >= 0) previewCancel();
			}
		}
	}
}

void HistoryWidget::onPreviewTimeout() {
	if (_previewData && _previewData->pendingTill > 0 && !_previewLinks.isEmpty()) {
		_previewRequest = MTP::send(MTPmessages_GetWebPagePreview(MTP_string(_previewLinks)), rpcDone(&HistoryWidget::gotPreview, _previewLinks));
	}
}

void HistoryWidget::gotPreview(QString links, const MTPMessageMedia &result, mtpRequestId req) {
	if (req == _previewRequest) {
		_previewRequest = 0;
	}
	if (result.type() == mtpc_messageMediaWebPage) {
		auto data = App::feedWebPage(result.c_messageMediaWebPage().vwebpage);
		_previewCache.insert(links, data->id);
		if (data->pendingTill > 0 && data->pendingTill <= unixtime()) {
			data->pendingTill = -1;
		}
		if (links == _previewLinks && !_previewCancelled) {
			_previewData = (data->id && data->pendingTill >= 0) ? data : 0;
			updatePreview();
		}
		if (App::main()) App::main()->webPagesOrGamesUpdate();
	} else if (result.type() == mtpc_messageMediaEmpty) {
		_previewCache.insert(links, 0);
		if (links == _previewLinks && !_previewCancelled) {
			_previewData = 0;
			updatePreview();
		}
	}
}

void HistoryWidget::updatePreview() {
	_previewTimer.stop();
	if (_previewData && _previewData->pendingTill >= 0) {
		_fieldBarCancel->show();
		updateMouseTracking();
		if (_previewData->pendingTill) {
			_previewTitle.setText(st::msgNameStyle, lang(lng_preview_loading), _textNameOptions);
#ifndef OS_MAC_OLD
			auto linkText = _previewLinks.splitRef(' ').at(0).toString();
#else // OS_MAC_OLD
			auto linkText = _previewLinks.split(' ').at(0);
#endif // OS_MAC_OLD
			_previewDescription.setText(st::messageTextStyle, TextUtilities::Clean(linkText), _textDlgOptions);

			int32 t = (_previewData->pendingTill - unixtime()) * 1000;
			if (t <= 0) t = 1;
			_previewTimer.start(t);
		} else {
			QString title, desc;
			if (_previewData->siteName.isEmpty()) {
				if (_previewData->title.isEmpty()) {
					if (_previewData->description.text.isEmpty()) {
						title = _previewData->author;
						desc = ((_previewData->document && !_previewData->document->filename().isEmpty()) ? _previewData->document->filename() : _previewData->url);
					} else {
						title = _previewData->description.text;
						desc = _previewData->author.isEmpty() ? ((_previewData->document && !_previewData->document->filename().isEmpty()) ? _previewData->document->filename() : _previewData->url) : _previewData->author;
					}
				} else {
					title = _previewData->title;
					desc = _previewData->description.text.isEmpty() ? (_previewData->author.isEmpty() ? ((_previewData->document && !_previewData->document->filename().isEmpty()) ? _previewData->document->filename() : _previewData->url) : _previewData->author) : _previewData->description.text;
				}
			} else {
				title = _previewData->siteName;
				desc = _previewData->title.isEmpty() ? (_previewData->description.text.isEmpty() ? (_previewData->author.isEmpty() ? ((_previewData->document && !_previewData->document->filename().isEmpty()) ? _previewData->document->filename() : _previewData->url) : _previewData->author) : _previewData->description.text) : _previewData->title;
			}
			if (title.isEmpty()) {
				if (_previewData->document) {
					title = lang(lng_attach_file);
				} else if (_previewData->photo) {
					title = lang(lng_attach_photo);
				}
			}
			_previewTitle.setText(st::msgNameStyle, title, _textNameOptions);
			_previewDescription.setText(st::messageTextStyle, TextUtilities::Clean(desc), _textDlgOptions);
		}
	} else if (!readyToForward() && !replyToId() && !_editMsgId) {
		_fieldBarCancel->hide();
		updateMouseTracking();
	}
	updateControlsGeometry();
	update();
}

void HistoryWidget::onCancel() {
	if (_isInlineBot) {
		onInlineBotCancel();
	} else if (_editMsgId) {
		auto original = _replyEditMsg ? _replyEditMsg->originalText() : TextWithEntities();
		auto editData = TextWithTags { TextUtilities::ApplyEntities(original), ConvertEntitiesToTextTags(original.entities) };
		if (_replyEditMsg && editData != _field->getTextWithTags()) {
			Ui::show(Box<ConfirmBox>(
				lang(lng_cancel_edit_post_sure),
				lang(lng_cancel_edit_post_yes),
				lang(lng_cancel_edit_post_no),
				base::lambda_guarded(this, [this] {
				onFieldBarCancel();
			})));
		} else {
			onFieldBarCancel();
		}
	} else if (!_fieldAutocomplete->isHidden()) {
		_fieldAutocomplete->hideAnimated();
	} else  {
		controller()->showBackFromStack();
		emit cancelled();
	}
}

void HistoryWidget::fullPeerUpdated(PeerData *peer) {
	auto refresh = false;
	if (_list && peer == _peer) {
		auto newCanSendMessages = _peer->canWrite();
		if (newCanSendMessages != _canSendMessages) {
			_canSendMessages = newCanSendMessages;
			if (!_canSendMessages) {
				cancelReply();
			}
			refreshSilentToggle();
			refresh = true;
		}
		onCheckFieldAutocomplete();
		updateReportSpamStatus();
		_list->updateBotInfo();

		handlePeerUpdate();
	}
	if (updateCmdStartShown()) {
		refresh = true;
	} else if (!_scroll->isHidden() && _unblock->isHidden() == isBlocked()) {
		refresh = true;
	}
	if (refresh) {
		updateControlsVisibility();
		updateControlsGeometry();
	}
}

void HistoryWidget::handlePeerUpdate() {
	bool resize = false;
	updateHistoryGeometry();
	if (_peer->isChannel()) updateReportSpamStatus();
	if (_peer->isChat() && _peer->asChat()->noParticipantInfo()) {
		Auth().api().requestFullPeer(_peer);
	} else if (_peer->isUser() && (_peer->asUser()->blockStatus() == UserData::BlockStatus::Unknown || _peer->asUser()->callsStatus() == UserData::CallsStatus::Unknown)) {
		Auth().api().requestFullPeer(_peer);
	} else if (auto channel = _peer->asMegagroup()) {
		if (!channel->mgInfo->botStatus) {
			Auth().api().requestBots(channel);
		}
		if (channel->mgInfo->admins.empty()) {
			Auth().api().requestAdmins(channel);
		}
	}
	if (!_a_show.animating()) {
		if (_unblock->isHidden() == isBlocked() || (!isBlocked() && _joinChannel->isHidden() == isJoinChannel())) {
			resize = true;
		}
		bool newCanSendMessages = _peer->canWrite();
		if (newCanSendMessages != _canSendMessages) {
			_canSendMessages = newCanSendMessages;
			if (!_canSendMessages) {
				cancelReply();
			}
			refreshSilentToggle();
			resize = true;
		}
		updateControlsVisibility();
		if (resize) {
			updateControlsGeometry();
		}
	}
}

void HistoryWidget::onForwardSelected() {
	if (!_list) return;
	auto weak = make_weak(this);
	Window::ShowForwardMessagesBox(getSelectedItems(), [=] {
		if (weak) {
			weak->onClearSelected();
		}
	});
}

void HistoryWidget::confirmDeleteSelectedItems() {
	if (!_list) return;

	auto selected = _list->getSelectedItems();
	if (selected.empty()) return;

	App::main()->deleteLayer(int(selected.size()));
}

void HistoryWidget::deleteSelectedItems(bool forEveryone) {
	Ui::hideLayer();
	if (!_list) return;

	const auto items = _list->getSelectedItems();
	const auto selected = ranges::view::all(
		items
	) | ranges::view::transform([](const FullMsgId &fullId) {
		return App::histItemById(fullId);
	}) | ranges::view::filter([](HistoryItem *item) {
		return item != nullptr;
	}) | ranges::to_vector;

	if (selected.empty()) return;

	QMap<PeerData*, QVector<MTPint>> idsByPeer;
	for (const auto item : selected) {
		idsByPeer[item->history()->peer].push_back(MTP_int(item->id));
	}

	onClearSelected();
	for (const auto item : selected) {
		item->destroy();
	}

	for (auto i = idsByPeer.cbegin(), e = idsByPeer.cend(); i != e; ++i) {
		App::main()->deleteMessages(i.key(), i.value(), forEveryone);
	}
}

void HistoryWidget::onListEscapePressed() {
	if (_nonEmptySelection && _list) {
		onClearSelected();
	} else {
		onCancel();
	}
}

void HistoryWidget::onListEnterPressed() {
	if (!_botStart->isHidden()) {
		onBotStart();
	}
}

void HistoryWidget::onClearSelected() {
	if (_list) _list->clearSelectedItems();
}

HistoryItem *HistoryWidget::getItemFromHistoryOrMigrated(MsgId genericMsgId) const {
	if (genericMsgId < 0 && -genericMsgId < ServerMaxMsgId && _migrated) {
		return App::histItemById(_migrated->channelId(), -genericMsgId);
	}
	return App::histItemById(_channel, genericMsgId);
}

MessageIdsList HistoryWidget::getSelectedItems() const {
	return _list ? _list->getSelectedItems() : MessageIdsList();
}

void HistoryWidget::updateTopBarSelection() {
	if (!_list) {
		_topBar->showSelected(HistoryTopBarWidget::SelectedState {});
		return;
	}

	auto selectedState = _list->getSelectionState();
	_nonEmptySelection = (selectedState.count > 0) || selectedState.textSelected;
	_topBar->showSelected(selectedState);
	updateControlsVisibility();
	updateHistoryGeometry();
	if (!Ui::isLayerShown() && !App::passcoded()) {
		if (_nonEmptySelection || (_list && _list->wasSelectedText()) || _recording || isBotStart() || isBlocked() || !_canSendMessages) {
			_list->setFocus();
		} else {
			_field->setFocus();
		}
	}
	_topBar->update();
	update();
}

void HistoryWidget::messageDataReceived(ChannelData *channel, MsgId msgId) {
	if (!_peer || _peer->asChannel() != channel || !msgId) return;
	if (_editMsgId == msgId || _replyToId == msgId) {
		updateReplyEditTexts(true);
	}
	if (_pinnedBar && _pinnedBar->msgId == msgId) {
		updatePinnedBar(true);
	}
}

void HistoryWidget::updateReplyEditTexts(bool force) {
	if (!force) {
		if (_replyEditMsg || (!_editMsgId && !_replyToId)) {
			return;
		}
	}
	if (!_replyEditMsg) {
		_replyEditMsg = App::histItemById(_channel, _editMsgId ? _editMsgId : _replyToId);
	}
	if (_replyEditMsg) {
		_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_replyEditMsg->inReplyText()), _textDlgOptions);

		updateBotKeyboard();

		if (!_field->isHidden() || _recording) {
			_fieldBarCancel->show();
			updateMouseTracking();
		}
		updateReplyToName();
		updateField();
	} else if (force) {
		if (_editMsgId) {
			cancelEdit();
		} else {
			cancelReply();
		}
	}
}

void HistoryWidget::updateForwarding() {
	if (_history) {
		_toForward = _history->validateForwardDraft();
		updateForwardingTexts();
	} else {
		_toForward.clear();
	}
	updateControlsVisibility();
	updateControlsGeometry();
}

void HistoryWidget::updateForwardingTexts() {
	int32 version = 0;
	QString from, text;
	if (const auto count = int(_toForward.size())) {
		QMap<PeerData*, bool> fromUsersMap;
		QVector<PeerData*> fromUsers;
		fromUsers.reserve(_toForward.size());
		for (const auto item : _toForward) {
			const auto from = item->senderOriginal();
			if (!fromUsersMap.contains(from)) {
				fromUsersMap.insert(from, true);
				fromUsers.push_back(from);
			}
			version += from->nameVersion;
		}
		if (fromUsers.size() > 2) {
			from = lng_forwarding_from(lt_count, fromUsers.size() - 1, lt_user, fromUsers.at(0)->shortName());
		} else if (fromUsers.size() < 2) {
			from = fromUsers.at(0)->name;
		} else {
			from = lng_forwarding_from_two(lt_user, fromUsers.at(0)->shortName(), lt_second_user, fromUsers.at(1)->shortName());
		}

		if (count < 2) {
			text = _toForward.front()->inReplyText();
		} else {
			text = lng_forward_messages(lt_count, count);
		}
	}
	_toForwardFrom.setText(st::msgNameStyle, from, _textNameOptions);
	_toForwardText.setText(st::messageTextStyle, TextUtilities::Clean(text), _textDlgOptions);
	_toForwardNameVersion = version;
}

void HistoryWidget::checkForwardingInfo() {
	if (!_toForward.empty()) {
		auto version = 0;
		for (const auto item : _toForward) {
			version += item->senderOriginal()->nameVersion;
		}
		if (version != _toForwardNameVersion) {
			updateForwardingTexts();
		}
	}
}

void HistoryWidget::updateReplyToName() {
	if (_editMsgId) return;
	if (!_replyEditMsg && (_replyToId || !_kbReplyTo)) return;
	_replyToName.setText(st::msgNameStyle, App::peerName((_replyEditMsg ? _replyEditMsg : _kbReplyTo)->author()), _textNameOptions);
	_replyToNameVersion = (_replyEditMsg ? _replyEditMsg : _kbReplyTo)->author()->nameVersion;
}

void HistoryWidget::updateField() {
	auto fieldAreaTop = _scroll->y() + _scroll->height();
	rtlupdate(0, fieldAreaTop, width(), height() - fieldAreaTop);
}

void HistoryWidget::drawField(Painter &p, const QRect &rect) {
	auto backy = _field->y() - st::historySendPadding;
	auto backh = _field->height() + 2 * st::historySendPadding;
	auto hasForward = readyToForward();
	auto drawMsgText = (_editMsgId || _replyToId) ? _replyEditMsg : _kbReplyTo;
	if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) {
		if (!_editMsgId && drawMsgText && drawMsgText->author()->nameVersion > _replyToNameVersion) {
			updateReplyToName();
		}
		backy -= st::historyReplyHeight;
		backh += st::historyReplyHeight;
	} else if (hasForward) {
		checkForwardingInfo();
		backy -= st::historyReplyHeight;
		backh += st::historyReplyHeight;
	} else if (_previewData && _previewData->pendingTill >= 0) {
		backy -= st::historyReplyHeight;
		backh += st::historyReplyHeight;
	}
	auto drawWebPagePreview = (_previewData && _previewData->pendingTill >= 0) && !_replyForwardPressed;
	p.fillRect(myrtlrect(0, backy, width(), backh), st::historyReplyBg);
	if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) {
		auto replyLeft = st::historyReplySkip;
		(_editMsgId ? st::historyEditIcon : st::historyReplyIcon).paint(p, st::historyReplyIconPosition + QPoint(0, backy), width());
		if (!drawWebPagePreview) {
			if (drawMsgText) {
				if (drawMsgText->getMedia() && drawMsgText->getMedia()->hasReplyPreview()) {
					auto replyPreview = drawMsgText->getMedia()->replyPreview();
					if (!replyPreview->isNull()) {
						auto to = QRect(replyLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height());
						p.drawPixmap(to.x(), to.y(), replyPreview->pixSingle(replyPreview->width() / cIntRetinaFactor(), replyPreview->height() / cIntRetinaFactor(), to.width(), to.height(), ImageRoundRadius::Small));
					}
					replyLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x();
				}
				p.setPen(st::historyReplyNameFg);
				if (_editMsgId) {
					paintEditHeader(p, rect, replyLeft, backy);
				} else {
					_replyToName.drawElided(p, replyLeft, backy + st::msgReplyPadding.top(), width() - replyLeft - _fieldBarCancel->width() - st::msgReplyPadding.right());
				}
				p.setPen(((drawMsgText->toHistoryMessage() && drawMsgText->toHistoryMessage()->emptyText()) || drawMsgText->serviceMsg()) ? st::historyComposeAreaFgService : st::historyComposeAreaFg);
				_replyEditMsgText.drawElided(p, replyLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - replyLeft - _fieldBarCancel->width() - st::msgReplyPadding.right());
			} else {
				p.setFont(st::msgDateFont);
				p.setPen(st::historyComposeAreaFgService);
				p.drawText(replyLeft, backy + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->elided(lang(lng_profile_loading), width() - replyLeft - _fieldBarCancel->width() - st::msgReplyPadding.right()));
			}
		}
	} else if (hasForward) {
		auto forwardLeft = st::historyReplySkip;
		st::historyForwardIcon.paint(p, st::historyReplyIconPosition + QPoint(0, backy), width());
		if (!drawWebPagePreview) {
			const auto firstItem = _toForward.front();
			const auto firstMedia = firstItem->getMedia();
			const auto serviceColor = (_toForward.size() > 1)
				|| (firstMedia != nullptr)
				|| firstItem->serviceMsg();
			const auto preview = (_toForward.size() < 2 && firstMedia && firstMedia->hasReplyPreview())
				? firstMedia->replyPreview()
				: ImagePtr();
			if (!preview->isNull()) {
				auto to = QRect(forwardLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height());
				if (preview->width() == preview->height()) {
					p.drawPixmap(to.x(), to.y(), preview->pix());
				} else {
					auto from = (preview->width() > preview->height()) ? QRect((preview->width() - preview->height()) / 2, 0, preview->height(), preview->height()) : QRect(0, (preview->height() - preview->width()) / 2, preview->width(), preview->width());
					p.drawPixmap(to, preview->pix(), from);
				}
				forwardLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x();
			}
			p.setPen(st::historyReplyNameFg);
			_toForwardFrom.drawElided(p, forwardLeft, backy + st::msgReplyPadding.top(), width() - forwardLeft - _fieldBarCancel->width() - st::msgReplyPadding.right());
			p.setPen(serviceColor ? st::historyComposeAreaFgService : st::historyComposeAreaFg);
			_toForwardText.drawElided(p, forwardLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - forwardLeft - _fieldBarCancel->width() - st::msgReplyPadding.right());
		}
	}
	if (drawWebPagePreview) {
		auto previewLeft = st::historyReplySkip + st::webPageLeft;
		p.fillRect(st::historyReplySkip, backy + st::msgReplyPadding.top(), st::webPageBar, st::msgReplyBarSize.height(), st::msgInReplyBarColor);
		if ((_previewData->photo && !_previewData->photo->thumb->isNull()) || (_previewData->document && !_previewData->document->thumb->isNull())) {
			auto replyPreview = _previewData->photo ? _previewData->photo->makeReplyPreview() : _previewData->document->makeReplyPreview();
			if (!replyPreview->isNull()) {
				auto to = QRect(previewLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height());
				if (replyPreview->width() == replyPreview->height()) {
					p.drawPixmap(to.x(), to.y(), replyPreview->pix());
				} else {
					auto from = (replyPreview->width() > replyPreview->height()) ? QRect((replyPreview->width() - replyPreview->height()) / 2, 0, replyPreview->height(), replyPreview->height()) : QRect(0, (replyPreview->height() - replyPreview->width()) / 2, replyPreview->width(), replyPreview->width());
					p.drawPixmap(to, replyPreview->pix(), from);
				}
			}
			previewLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x();
		}
		p.setPen(st::historyReplyNameFg);
		_previewTitle.drawElided(p, previewLeft, backy + st::msgReplyPadding.top(), width() - previewLeft - _fieldBarCancel->width() - st::msgReplyPadding.right());
		p.setPen(st::historyComposeAreaFg);
		_previewDescription.drawElided(p, previewLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - previewLeft - _fieldBarCancel->width() - st::msgReplyPadding.right());
	}
}

void HistoryWidget::drawRestrictedWrite(Painter &p) {
	auto rect = myrtlrect(0, height() - _unblock->height(), width(), _unblock->height());
	p.fillRect(rect, st::historyReplyBg);

	p.setFont(st::normalFont);
	p.setPen(st::windowSubTextFg);
	p.drawText(rect.marginsRemoved(QMargins(st::historySendPadding, 0, st::historySendPadding, 0)), lang(lng_restricted_send_message), style::al_center);
}

void HistoryWidget::paintEditHeader(Painter &p, const QRect &rect, int left, int top) const {
	if (!rect.intersects(myrtlrect(left, top, width() - left, st::normalFont->height))) {
		return;
	}

	p.setFont(st::msgServiceNameFont);
	p.drawTextLeft(left, top + st::msgReplyPadding.top(), width(), lang(lng_edit_message));

	if (!_replyEditMsg || _replyEditMsg->history()->peer->isSelf()) return;

	QString editTimeLeftText;
	int updateIn = -1;
	auto tmp = ::date(unixtime());
	auto timeSinceMessage = _replyEditMsg->date.msecsTo(QDateTime::currentDateTime());
	auto editTimeLeft = (Global::EditTimeLimit() * 1000LL) - timeSinceMessage;
	if (editTimeLeft < 2) {
		editTimeLeftText = qsl("0:00");
	} else if (editTimeLeft > kDisplayEditTimeWarningMs) {
		updateIn = static_cast<int>(qMin(editTimeLeft - kDisplayEditTimeWarningMs, qint64(kFullDayInMs)));
	} else {
		updateIn = static_cast<int>(editTimeLeft % 1000);
		if (!updateIn) {
			updateIn = 1000;
		}
		++updateIn;

		editTimeLeft = (editTimeLeft - 1) / 1000; // seconds
		editTimeLeftText = qsl("%1:%2").arg(editTimeLeft / 60).arg(editTimeLeft % 60, 2, 10, QChar('0'));
	}

	// Restart timer only if we are sure that we've painted the whole timer.
	if (rect.contains(myrtlrect(left, top, width() - left, st::normalFont->height)) && updateIn > 0) {
		_updateEditTimeLeftDisplay.start(updateIn);
	}

	if (!editTimeLeftText.isEmpty()) {
		p.setFont(st::normalFont);
		p.setPen(st::historyComposeAreaFgService);
		p.drawText(left + st::msgServiceNameFont->width(lang(lng_edit_message)) + st::normalFont->spacew, top + st::msgReplyPadding.top() + st::msgServiceNameFont->ascent, editTimeLeftText);
	}
}

void HistoryWidget::drawRecording(Painter &p, float64 recordActive) {
	p.setPen(Qt::NoPen);
	p.setBrush(st::historyRecordSignalColor);

	auto delta = qMin(a_recordingLevel.current() / 0x4000, 1.);
	auto d = 2 * qRound(st::historyRecordSignalMin + (delta * (st::historyRecordSignalMax - st::historyRecordSignalMin)));
	{
		PainterHighQualityEnabler hq(p);
		p.drawEllipse(_attachToggle->x() + (_tabbedSelectorToggle->width() - d) / 2, _attachToggle->y() + (_attachToggle->height() - d) / 2, d, d);
	}

	auto duration = formatDurationText(_recordingSamples / Media::Player::kDefaultFrequency);
	p.setFont(st::historyRecordFont);

	p.setPen(st::historyRecordDurationFg);
	p.drawText(_attachToggle->x() + _tabbedSelectorToggle->width(), _attachToggle->y() + st::historyRecordTextTop + st::historyRecordFont->ascent, duration);

	int32 left = _attachToggle->x() + _tabbedSelectorToggle->width() + st::historyRecordFont->width(duration) + ((_send->width() - st::historyRecordVoice.width()) / 2);
	int32 right = width() - _send->width();

	p.setPen(anim::pen(st::historyRecordCancel, st::historyRecordCancelActive, 1. - recordActive));
	p.drawText(left + (right - left - _recordCancelWidth) / 2, _attachToggle->y() + st::historyRecordTextTop + st::historyRecordFont->ascent, lang(lng_record_cancel));
}

void HistoryWidget::drawPinnedBar(Painter &p) {
	Expects(_pinnedBar != nullptr);

	auto top = _topBar->bottomNoMargins();
	Text *from = 0, *text = 0;
	bool serviceColor = false, hasForward = readyToForward();
	ImagePtr preview;
	p.fillRect(myrtlrect(0, top, width(), st::historyReplyHeight), st::historyPinnedBg);

	top += st::msgReplyPadding.top();
	QRect rbar(myrtlrect(st::msgReplyBarSkip + st::msgReplyBarPos.x(), top + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height()));
	p.fillRect(rbar, st::msgInReplyBarColor);

	int32 left = st::msgReplyBarSkip + st::msgReplyBarSkip;
	if (_pinnedBar->msg) {
		if (_pinnedBar->msg->getMedia() && _pinnedBar->msg->getMedia()->hasReplyPreview()) {
			ImagePtr replyPreview = _pinnedBar->msg->getMedia()->replyPreview();
			if (!replyPreview->isNull()) {
				QRect to(left, top, st::msgReplyBarSize.height(), st::msgReplyBarSize.height());
				p.drawPixmap(to.x(), to.y(), replyPreview->pixSingle(replyPreview->width() / cIntRetinaFactor(), replyPreview->height() / cIntRetinaFactor(), to.width(), to.height(), ImageRoundRadius::Small));
			}
			left += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x();
		}
		p.setPen(st::historyReplyNameFg);
		p.setFont(st::msgServiceNameFont);
		p.drawText(left, top + st::msgServiceNameFont->ascent, lang(lng_pinned_message));

		p.setPen(((_pinnedBar->msg->toHistoryMessage() && _pinnedBar->msg->toHistoryMessage()->emptyText()) || _pinnedBar->msg->serviceMsg()) ? st::historyComposeAreaFgService : st::historyComposeAreaFg);
		_pinnedBar->text.drawElided(p, left, top + st::msgServiceNameFont->height, width() - left - _pinnedBar->cancel->width() - st::msgReplyPadding.right());
	} else {
		p.setFont(st::msgDateFont);
		p.setPen(st::historyComposeAreaFgService);
		p.drawText(left, top + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->elided(lang(lng_profile_loading), width() - left - _pinnedBar->cancel->width() - st::msgReplyPadding.right()));
	}
}

void HistoryWidget::paintEvent(QPaintEvent *e) {
	if (!App::main() || (App::wnd() && App::wnd()->contentOverlapped(this, e))) {
		return;
	}
	if (hasPendingResizedItems()) {
		updateListSize();
	}

	Painter p(this);
	QRect r(e->rect());
	if (r != rect()) {
		p.setClipRect(r);
	}

	auto ms = getms();
	_historyDownShown.step(ms);
	_unreadMentionsShown.step(ms);
	auto progress = _a_show.current(ms, 1.);
	if (_a_show.animating()) {
		auto animationWidth = width();
		auto retina = cIntRetinaFactor();
		auto fromLeft = (_showDirection == Window::SlideDirection::FromLeft);
		auto coordUnder = fromLeft ? anim::interpolate(-st::slideShift, 0, progress) : anim::interpolate(0, -st::slideShift, progress);
		auto coordOver = fromLeft ? anim::interpolate(0, animationWidth, progress) : anim::interpolate(animationWidth, 0, progress);
		auto shadow = fromLeft ? (1. - progress) : progress;
		if (coordOver > 0) {
			p.drawPixmap(QRect(0, 0, coordOver, height()), _cacheUnder, QRect(-coordUnder * retina, 0, coordOver * retina, height() * retina));
			p.setOpacity(shadow);
			p.fillRect(0, 0, coordOver, height(), st::slideFadeOutBg);
			p.setOpacity(1);
		}
		p.drawPixmap(QRect(coordOver, 0, _cacheOver.width() / retina, height()), _cacheOver, QRect(0, 0, _cacheOver.width(), height() * retina));
		p.setOpacity(shadow);
		st::slideShadow.fill(p, QRect(coordOver - st::slideShadow.width(), 0, st::slideShadow.width(), height()));
		return;
	}

	QRect fill(0, 0, width(), App::main()->height());
	auto fromy = App::main()->backgroundFromY();
	auto x = 0, y = 0;
	QPixmap cached = App::main()->cachedBackground(fill, x, y);
	if (cached.isNull()) {
		if (Window::Theme::Background()->tile()) {
			auto &pix = Window::Theme::Background()->pixmapForTiled();
			auto left = r.left();
			auto top = r.top();
			auto right = r.left() + r.width();
			auto bottom = r.top() + r.height();
			auto w = pix.width() / cRetinaFactor();
			auto h = pix.height() / cRetinaFactor();
			auto sx = qFloor(left / w);
			auto sy = qFloor((top - fromy) / h);
			auto cx = qCeil(right / w);
			auto cy = qCeil((bottom - fromy) / h);
			for (auto i = sx; i < cx; ++i) {
				for (auto j = sy; j < cy; ++j) {
					p.drawPixmap(QPointF(i * w, fromy + j * h), pix);
				}
			}
		} else {
			PainterHighQualityEnabler hq(p);

			auto &pix = Window::Theme::Background()->pixmap();
			QRect to, from;
			Window::Theme::ComputeBackgroundRects(fill, pix.size(), to, from);
			to.moveTop(to.top() + fromy);
			p.drawPixmap(to, pix, from);
		}
	} else {
		p.drawPixmap(x, fromy + y, cached);
	}

	if (_list) {
		if (!_field->isHidden() || _recording) {
			drawField(p, r);
			if (!_send->isHidden() && _recording) {
				drawRecording(p, _send->recordActiveRatio());
			}
		} else if (isRestrictedWrite()) {
			drawRestrictedWrite(p);
		}
		if (_pinnedBar && !_pinnedBar->cancel->isHidden()) {
			drawPinnedBar(p);
		}
		if (_scroll->isHidden()) {
			p.setClipRect(_scroll->geometry());
			HistoryLayout::paintEmpty(p, width(), height() - _field->height() - 2 * st::historySendPadding);
		}
	} else {
		style::font font(st::msgServiceFont);
		int32 w = font->width(lang(lng_willbe_history)) + st::msgPadding.left() + st::msgPadding.right(), h = font->height + st::msgServicePadding.top() + st::msgServicePadding.bottom() + 2;
		QRect tr((width() - w) / 2, (height() - _field->height() - 2 * st::historySendPadding - h) / 2, w, h);
		HistoryLayout::ServiceMessagePainter::paintBubble(p, tr.x(), tr.y(), tr.width(), tr.height());

		p.setPen(st::msgServiceFg);
		p.setFont(font->f);
		p.drawText(tr.left() + st::msgPadding.left(), tr.top() + st::msgServicePadding.top() + 1 + font->ascent, lang(lng_willbe_history));
	}
}

QRect HistoryWidget::historyRect() const {
	return _scroll->geometry();
}

void HistoryWidget::destroyData() {
	showHistory(0, 0);
}

QPoint HistoryWidget::clampMousePosition(QPoint point) {
	if (point.x() < 0) {
		point.setX(0);
	} else if (point.x() >= _scroll->width()) {
		point.setX(_scroll->width() - 1);
	}
	if (point.y() < _scroll->scrollTop()) {
		point.setY(_scroll->scrollTop());
	} else if (point.y() >= _scroll->scrollTop() + _scroll->height()) {
		point.setY(_scroll->scrollTop() + _scroll->height() - 1);
	}
	return point;
}

void HistoryWidget::onScrollTimer() {
	auto d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed));
	_scroll->scrollToY(_scroll->scrollTop() + d);
}

void HistoryWidget::checkSelectingScroll(QPoint point) {
	if (point.y() < _scroll->scrollTop()) {
		_scrollDelta = point.y() - _scroll->scrollTop();
	} else if (point.y() >= _scroll->scrollTop() + _scroll->height()) {
		_scrollDelta = point.y() - _scroll->scrollTop() - _scroll->height() + 1;
	} else {
		_scrollDelta = 0;
	}
	if (_scrollDelta) {
		_scrollTimer.start(15);
	} else {
		_scrollTimer.stop();
	}
}

void HistoryWidget::noSelectingScroll() {
	_scrollTimer.stop();
}

bool HistoryWidget::touchScroll(const QPoint &delta) {
	int32 scTop = _scroll->scrollTop(), scMax = _scroll->scrollTopMax(), scNew = snap(scTop - delta.y(), 0, scMax);
	if (scNew == scTop) return false;

	_scroll->scrollToY(scNew);
	return true;
}

void HistoryWidget::synteticScrollToY(int y) {
	_synteticScrollEvent = true;
	if (_scroll->scrollTop() == y) {
		visibleAreaUpdated();
	} else {
		_scroll->scrollToY(y);
	}
	_synteticScrollEvent = false;
}

HistoryWidget::~HistoryWidget() = default;
