/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.

For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/moderate_messages_box.h"

#include "api/api_blocked_peers.h"
#include "api/api_chat_participants.h"
#include "api/api_messages_search.h"
#include "api/api_report.h"
#include "apiwrap.h"
#include "base/event_filter.h"
#include "base/options.h"
#include "base/timer.h"
#include "boxes/delete_messages_box.h"
#include "boxes/peers/edit_peer_permissions_box.h"
#include "core/application.h"
#include "core/ui_integration.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_chat_filters.h"
#include "data/data_chat_participant_status.h"
#include "data/data_histories.h"
#include "data/data_peer.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
#include "history/history.h"
#include "history/history_item.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "main/main_session.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h"
#include "ui/effects/ripple_animation.h"
#include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/rect_part.h"
#include "ui/text/text_lottie_custom_emoji.h"
#include "ui/text/text_utilities.h"
#include "ui/vertical_list.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/expandable_peer_list.h"
#include "ui/widgets/participants_check_view.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/ui_utility.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include "styles/style_window.h"

#include "window/window_session_controller.h"
#include "window/window_controller.h"
#include "boxes/choose_filter_box.h"
#include "boxes/peer_list_box.h"
#include "main/main_session_settings.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/menu/menu_add_action_callback.h"
#include "ui/widgets/menu/menu_multiline_action.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/menu/menu_action.h"
#include "base/qt/qt_key_modifiers.h"
#include "ui/effects/round_checkbox.h"
#include "styles/style_chat.h"
#include "styles/style_menu_icons.h"
#include "styles/style_info.h"
#include "styles/style_media_player.h" // mediaPlayerMenuCheck

base::options::toggle ModerateCommonGroups({
	.id = kModerateCommonGroups,
	.name = "Ban users from several groups at once.",
});

const char kModerateCommonGroups[] = "moderate-common-groups";

namespace {

struct ModerateOptions final {
	bool allCanBan = false;
	bool allCanDelete = false;
	Participants participants;
};

ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) {
	Expects(!items.empty());

	auto result = ModerateOptions{
		.allCanBan = true,
		.allCanDelete = true,
	};

	const auto peer = items.front()->history()->peer;
	for (const auto &item : items) {
		if (!result.allCanBan && !result.allCanDelete) {
			return {};
		}
		if (peer != item->history()->peer) {
			return {};
		}
		{
			const auto author = item->author();
			if (author == peer) {
				return {};
			} else if (const auto channel = author->asChannel()) {
				if (channel->discussionLink() == peer) {
					return {};
				}
			}
		}
		if (!item->suggestBanReport()) {
			result.allCanBan = false;
		}
		if (!item->suggestDeleteAllReport()) {
			result.allCanDelete = false;
		}
		if (const auto p = item->from()) {
			if (!ranges::contains(result.participants, not_null{ p })) {
				result.participants.push_back(p);
			}
		}
	}
	return result;
}

[[nodiscard]] rpl::producer<base::flat_map<PeerId, int>> MessagesCountValue(
		not_null<History*> history,
		std::vector<not_null<PeerData*>> from) {
	return [=](auto consumer) {
		auto lifetime = rpl::lifetime();
		struct State final {
			base::flat_map<PeerId, int> messagesCounts;
			int index = 0;
			rpl::lifetime apiLifetime;
		};
		const auto search = lifetime.make_state<Api::MessagesSearch>(history);
		const auto state = lifetime.make_state<State>();
		const auto send = [=](auto repeat) -> void {
			if (state->index >= from.size()) {
				consumer.put_next_copy(state->messagesCounts);
				return;
			}
			const auto peer = from[state->index];
			const auto peerId = peer->id;
			state->apiLifetime = search->messagesFounds(
			) | rpl::on_next([=](const Api::FoundMessages &found) {
				state->messagesCounts[peerId] = found.total;
				state->index++;
				repeat(repeat);
			});
			search->searchMessages({ .from = peer });
		};
		consumer.put_next({});
		send(send);

		return lifetime;
	};
}

using CommonGroups = std::vector<not_null<PeerData*>>;
using CollectCommon = std::shared_ptr<std::vector<PeerId>>;

void FillMenuModerateCommonGroups(
		not_null<Ui::PopupMenu*> menu,
		CommonGroups common,
		CollectCommon collectCommon,
		not_null<UserData*> user,
		Fn<void()> onDestroyedCallback) {
	const auto resultList
		= menu->lifetime().make_state<base::flat_set<PeerId>>();
	const auto rememberCheckbox = Ui::CreateChild<Ui::Checkbox>(
		menu,
		QString());
	auto multiline = base::make_unique_q<Ui::Menu::MultilineAction>(
		menu->menu(),
		menu->st().menu,
		st::historyHasCustomEmoji,
		st::historyHasCustomEmojiPosition,
		tr::lng_restrict_users_kick_from_common_group(tr::now, tr::rich));
	multiline->setAttribute(Qt::WA_TransparentForMouseEvents);
	menu->addAction(std::move(multiline));
	const auto session = &common.front()->session();
	const auto settingsOnStart = session->settings().moderateCommonGroups();
	const auto checkboxesUpdate = std::make_shared<rpl::event_stream<>>();
	const auto save = [=] {
		auto result = std::vector<PeerId>(
			resultList->begin(),
			resultList->end());
		*collectCommon = std::move(result);
	};
	for (const auto &group : common) {
		struct State {
			std::optional<Ui::RoundImageCheckbox> checkbox;
			Ui::RpWidget *checkboxWidget = nullptr;
		};
		auto item = base::make_unique_q<Ui::Menu::Action>(
			menu->menu(),
			menu->st().menu,
			Ui::Menu::CreateAction(
				menu->menu(),
				group->name(),
				[] {}),
			nullptr,
			nullptr);
		const auto state = item->lifetime().make_state<State>();
		const auto setChecked = [=, peerId = group->id](bool checked) {
			state->checkbox->setChecked(checked);
			if (state->checkbox->checked()) {
				resultList->insert(peerId);
			} else {
				resultList->erase(peerId);
			}
			save();
		};
		item->setActionTriggered([=] {
			setChecked(!state->checkbox->checked());
		});
		const auto raw = item.get();
		checkboxesUpdate->events() | rpl::on_next([=, peerId = group->id] {
			setChecked(ranges::contains(*collectCommon, peerId));
		}, raw->lifetime());
		state->checkboxWidget = Ui::CreateChild<Ui::RpWidget>(raw);
		state->checkboxWidget->setAttribute(Qt::WA_TransparentForMouseEvents);
		state->checkboxWidget->resize(item->width() * 2, item->height());
		state->checkboxWidget->show();
		state->checkbox.emplace(
			st::moderateCommonGroupsCheckbox,
			[=] { state->checkboxWidget->update(); },
			PaintUserpicCallback(group, true),
			[=](int size) { return (group->isForum() || group->isMonoforum())
				? int(size * Ui::ForumUserpicRadiusMultiplier())
				: std::optional<int>(); });
		state->checkbox->setChecked(
			/*ranges::contains(
				session->settings().moderateCommonGroups(),
				group->id)
			|| */(collectCommon
				&& ranges::contains(*collectCommon, group->id)),
			anim::type::instant);
		state->checkboxWidget->paintOn([=](QPainter &p) {
			auto pp = Painter(state->checkboxWidget);
			state->checkbox->paint(
				pp,
				st::menuWithIcons.itemIconPosition.x(),
				st::menuWithIcons.itemIconPosition.y(),
				raw->width());
		});
		menu->addAction(std::move(item));
	}
	menu->addSeparator();
	if (const auto window = Core::App().findWindow(menu->parentWidget())) {
		auto hasActions = false;
		Ui::Menu::CreateAddActionCallback(menu)(Ui::Menu::MenuCallback::Args{
			.text = tr::lng_restrict_users_kick_from_common_group(tr::now),
			.handler = nullptr,
			.icon = &st::menuIconAddToFolder,
			.fillSubmenu = [&](not_null<Ui::PopupMenu*> menu) {
				hasActions = FillChooseFilterWithAdminedGroupsMenu(
					window->sessionController(),
					menu,
					user,
					checkboxesUpdate,
					common,
					collectCommon);
			},
			.submenuSt = &st::foldersMenu,
		});
		if (!hasActions) {
			menu->removeAction(menu->actions().size() - 1);
			menu->removeAction(menu->actions().size() - 1); // Separator.
		}
	}
	menu->addSeparator();
	{
		auto item = base::make_unique_q<Ui::Menu::Action>(
			menu->menu(),
			menu->st().menu,
			Ui::Menu::CreateAction(
				menu->menu(),
				tr::lng_remember(tr::now),
				[] {}),
			nullptr,
			nullptr);
		item->setPreventClose(true);
		item->setActionTriggered([=] {
			rememberCheckbox->setChecked(!rememberCheckbox->checked());
		});
		rememberCheckbox->setParent(item.get());
		rememberCheckbox->setAttribute(Qt::WA_TransparentForMouseEvents);
		rememberCheckbox->move(st::lineWidth * 8, -st::lineWidth * 2);
		rememberCheckbox->show();
		menu->addAction(std::move(item));
	}
	menu->setDestroyedCallback([=] {
		onDestroyedCallback();
		if (!rememberCheckbox->checked()) {
			session->settings().setModerateCommonGroups(settingsOnStart);
			session->saveSettingsDelayed();
		}
	});
}

void ProccessCommonGroups(
		const HistoryItemsList &items,
		Fn<void(CommonGroups, not_null<UserData*>)> processHas) {
	const auto moderateOptions = CalculateModerateOptions(items);
	if (moderateOptions.participants.size() != 1
		|| !moderateOptions.allCanBan) {
		return;
	}
	const auto participant = moderateOptions.participants.front();
	const auto user = participant->asUser();
	if (!user) {
		return;
	}
	const auto currentGroupId = items.front()->history()->peer->id;
	user->session().api().requestBotCommonGroups(user, [=] {
		const auto commonGroups = user->session().api().botCommonGroups(user);
		if (!commonGroups || commonGroups->empty()) {
			return;
		}

		auto filtered = CommonGroups();
		for (const auto &group : *commonGroups) {
			if (group->id == currentGroupId) {
				continue;
			}
			const auto channel = group->asChannel();
			if (channel && channel->canRestrictParticipant(user)) {
				if (channel->isGroupAdmin(user) && !channel->amCreator()) {
					continue;
				}
				filtered.push_back(group);
			}
		}

		if (!filtered.empty()) {
			processHas(filtered, user);
		}
	});
}

} // namespace

void CreateModerateMessagesBox(
		not_null<Ui::GenericBox*> box,
		const HistoryItemsList &items,
		Fn<void()> confirmed,
		ModerateMessagesBoxOptions options) {
	Expects(!items.empty());

	using Controller = Ui::ExpandablePeerListController;

	const auto [allCanBan, allCanDelete, participants]
		= CalculateModerateOptions(items);
	const auto inner = box->verticalLayout();

	Assert(!participants.empty());

	const auto confirms = inner->lifetime().make_state<rpl::event_stream<>>();
	const auto collectCommon = std::make_shared<std::vector<PeerId>>();

	const auto isSingle = participants.size() == 1;
	const auto buttonPadding = isSingle
		? QMargins()
		: QMargins(
			0,
			0,
			Ui::ParticipantsCheckView::ComputeSize(
				participants.size()).width(),
			0);

	const auto itemsCount = int(items.size());
	const auto firstItem = items.front();
	const auto history = firstItem->history();
	const auto session = &history->session();
	const auto historyPeerId = history->peer->id;
	const auto ids = session->data().itemsToIds(items);

	{
		const auto remainingIds
			= box->lifetime().make_state<base::flat_set<FullMsgId>>(
				ids.begin(),
				ids.end());
		session->data().itemRemoved(
		) | rpl::on_next([=](not_null<const HistoryItem*> item) {
			remainingIds->erase(item->fullId());
			if (remainingIds->empty()) {
				box->closeBox();
			}
		}, box->lifetime());
	}

	if (ModerateCommonGroups.value() || session->supportMode()) {
	ProccessCommonGroups(
		items,
		crl::guard(box, [=](CommonGroups groups, not_null<UserData*> user) {
			using namespace Ui;
			const auto top = box->addTopButton(st::infoTopBarMenu);
			auto &lifetime = top->lifetime();
			const auto menu
				= lifetime.make_state<base::unique_qptr<Ui::PopupMenu>>();

			{
				const auto was = collectCommon->size();
				*menu = base::make_unique_q<Ui::PopupMenu>(
					top,
					st::popupMenuExpandedSeparator);
				FillMenuModerateCommonGroups(
					*menu,
					groups,
					collectCommon,
					user,
					[]{});
				*menu = nullptr;
				if (was != collectCommon->size()) {
					top->setIconOverride(
						&st::infoTopBarMenuActive,
						&st::infoTopBarMenuActive);
					const auto minicheck = Ui::CreateChild<Ui::RpWidget>(top);
					minicheck->paintRequest() | rpl::on_next([=] {
						auto p = Painter(minicheck);
						const auto rect = minicheck->rect();
						const auto iconSize = QSize(
							st::mediaPlayerMenuCheck.width(),
							st::mediaPlayerMenuCheck.height());
						const auto scale = std::min(
							rect.width() / float64(iconSize.width()),
							rect.height() / float64(iconSize.height()));
						if (scale < 1.0) {
							p.save();
							p.translate(rect.center());
							p.scale(scale, scale);
							p.translate(-rect.center());
						}
						st::mediaPlayerMenuCheck.paintInCenter(
							p,
							rect,
							st::windowActiveTextFg->c);
						if (scale < 1.0) {
							p.restore();
						}
					}, minicheck->lifetime());
					minicheck->resize(
						st::mediaPlayerMenuCheck.width() / 1.5,
						st::mediaPlayerMenuCheck.width() / 1.5);
					minicheck->show();
					minicheck->moveToLeft(
						top->width() - st::lineWidth * 26,
						top->height() - st::lineWidth * 29);
				}
			}

			top->setClickedCallback([=] {
				top->setForceRippled(true);
				*menu = base::make_unique_q<Ui::PopupMenu>(
					top,
					st::popupMenuExpandedSeparator);
				const auto onDestroyedCallback = [=, weak = top] {
					if (const auto strong = weak.data()) {
						strong->setForceRippled(false);
					}
				};
				FillMenuModerateCommonGroups(
					*menu,
					groups,
					collectCommon,
					user,
					onDestroyedCallback);
				(*menu)->setForcedOrigin(PanelAnimation::Origin::TopRight);
				const auto point = QPoint(top->width(), top->height());
				(*menu)->popup(top->mapToGlobal(point));
			});
		}));
	}

	using Request = Fn<void(not_null<PeerData*>, not_null<ChannelData*>)>;
	const auto sequentiallyRequest = [=](
			Request request,
			Participants participants,
			std::optional<std::vector<PeerId>> channelIds = {}) {
		constexpr auto kSmallDelayMs = 5;
		const auto participantIds = ranges::views::all(
			participants
		) | ranges::views::transform([](not_null<PeerData*> peer) {
			return peer->id;
		}) | ranges::to_vector;
		const auto channelIdList = channelIds.value_or(
			std::vector<PeerId>{ historyPeerId });
		const auto lifetime = std::make_shared<rpl::lifetime>();
		const auto participantIndex = lifetime->make_state<int>(0);
		const auto channelIndex = lifetime->make_state<int>(0);
		const auto timer = lifetime->make_state<base::Timer>();
		timer->setCallback(crl::guard(session, [=] {
			if ((*participantIndex) < participantIds.size()) {
				if ((*channelIndex) < channelIdList.size()) {
					const auto from = session->data().peer(
						participantIds[*participantIndex]);
					const auto channel = session->data().peer(
						channelIdList[*channelIndex])->asChannel();
					if (from && channel) {
						request(from, channel);
					}
					(*channelIndex)++;
				} else {
					(*participantIndex)++;
					*channelIndex = 0;
				}
			} else {
				lifetime->destroy();
			}
		}));
		timer->callEach(kSmallDelayMs);
	};

	const auto handleConfirmation = [=](
			not_null<Ui::Checkbox*> checkbox,
			not_null<Controller*> controller,
			Request request) {
		confirms->events() | rpl::on_next([=] {
			if (checkbox->checked() && controller->collectRequests) {
				sequentiallyRequest(request, controller->collectRequests());
			}
		}, checkbox->lifetime());
	};

	const auto isEnter = [=](not_null<QEvent*> event) {
		if (event->type() == QEvent::KeyPress) {
			if (const auto k = static_cast<QKeyEvent*>(event.get())) {
				return (k->key() == Qt::Key_Enter)
					|| (k->key() == Qt::Key_Return);
			}
		}
		return false;
	};

	base::install_event_filter(box, [=](not_null<QEvent*> event) {
		if (isEnter(event)) {
			box->triggerButton(0);
			return base::EventFilterResult::Cancel;
		}
		return base::EventFilterResult::Continue;
	});

	const auto handleSubmition = [=](not_null<Ui::Checkbox*> checkbox) {
		base::install_event_filter(box, [=](not_null<QEvent*> event) {
			if (!isEnter(event) || !checkbox->checked()) {
				return base::EventFilterResult::Continue;
			}
			box->uiShow()->show(Ui::MakeConfirmBox({
				.text = tr::lng_gigagroup_warning_title(),
				.confirmed = [=](Fn<void()> close) {
					box->triggerButton(0);
					close();
				},
				.confirmText = tr::lng_box_yes(),
				.cancelText = tr::lng_box_no(),
			}));
			return base::EventFilterResult::Cancel;
		});
	};

	Ui::AddSkip(inner);
	const auto title = box->addRow(
		object_ptr<Ui::FlatLabel>(
			box,
			(itemsCount == 1)
				? tr::lng_selected_delete_sure_this()
				: tr::lng_selected_delete_sure(
					lt_count,
					rpl::single(itemsCount) | tr::to_count()),
			st::boxLabel));
	Ui::AddSkip(inner);
	Ui::AddSkip(inner);
	Ui::AddSkip(inner);
	{
		const auto report = box->addRow(
			object_ptr<Ui::Checkbox>(
				box,
				tr::lng_report_spam(tr::now),
				options.reportSpam,
				st::defaultBoxCheckbox),
			st::boxRowPadding + buttonPadding);
		const auto controller = box->lifetime().make_state<Controller>(
			Controller::Data{ .participants = participants });
		Ui::AddExpandablePeerList(report, controller, inner);
		handleSubmition(report);

		handleConfirmation(report, controller, [=](
				not_null<PeerData*> p,
				not_null<ChannelData*> c) {
			Api::ReportSpam(p, ids);
		});
	}

	if (allCanDelete) {
		Ui::AddSkip(inner);
		Ui::AddSkip(inner);

		const auto deleteAll = inner->add(
			object_ptr<Ui::Checkbox>(
				inner,
				!(isSingle)
					? tr::lng_delete_all_from_users(
						tr::now,
						tr::marked)
					: tr::lng_delete_all_from_user(
						tr::now,
						lt_user,
						tr::bold(firstItem->from()->name()),
						tr::marked),
				options.deleteAll,
				st::defaultBoxCheckbox),
			st::boxRowPadding + buttonPadding);
		auto messagesCounts = MessagesCountValue(history, participants);

		const auto controller = box->lifetime().make_state<Controller>(
			Controller::Data{
				.messagesCounts = rpl::duplicate(messagesCounts),
				.participants = participants,
			});
		Ui::AddExpandablePeerList(deleteAll, controller, inner);
		{
			auto itemFromIds = items | ranges::views::transform([](
					const auto &item) {
				return item->from()->id;
			}) | ranges::to_vector;

			rpl::combine(
				std::move(messagesCounts),
				isSingle
					? deleteAll->checkedValue()
					: rpl::merge(
						controller->toggleRequestsFromInner.events(),
						controller->checkAllRequests.events())
			) | rpl::map([=](const auto &map, bool c) {
				const auto checked = (isSingle && !c)
					? Participants()
					: controller->collectRequests
					? controller->collectRequests()
					: Participants();
				auto result = 0;
				for (const auto &[peerId, count] : map) {
					for (const auto &peer : checked) {
						if (peer->id == peerId) {
							result += count;
							break;
						}
					}
				}
				for (const auto &fromId : itemFromIds) {
					for (const auto &peer : checked) {
						if (peer->id == fromId) {
							result--;
							break;
						}
					}
					result++;
				}
				return float64(result);
			}) | rpl::on_next([=](int amount) {
				auto text = tr::lng_selected_delete_sure(
					tr::now,
					lt_count,
					float64(amount));
				if (amount > 0) {
					title->setText(std::move(text));
				} else {
					const auto zeroIndex = text.indexOf('0');
					if (zeroIndex != -1) {
						auto descriptor = Lottie::IconDescriptor{
							.name = u"transcribe_loading"_q,
							.color = &st::attentionButtonFg, // Any contrast.
							.sizeOverride = Size(
								st::historyTranscribeLoadingSize),
							.colorizeUsingAlpha = true,
						};
						auto result = TextWithEntities()
							.append(text.mid(0, zeroIndex))
							.append(Ui::Text::LottieEmoji(descriptor))
							.append(text.mid(zeroIndex + 1));
						using namespace Ui::Text;
						title->setMarkedText(
							std::move(result),
							LottieEmojiContext(std::move(descriptor)));
					} else {
						title->setText(std::move(text));
					}
				}
				title->resizeToWidth(inner->width()
					- rect::m::sum::h(st::boxRowPadding));
			}, title->lifetime());
		}
		handleSubmition(deleteAll);

		handleConfirmation(deleteAll, controller, [=](
				not_null<PeerData*> p,
				not_null<ChannelData*> c) {
			p->session().api().deleteAllFromParticipant(c, p);
		});

		if (GetEnhancedInt("always_delete_for") == 1 || GetEnhancedInt("always_delete_for") == 3) {
			deleteAll->setChecked(true);
		}
	}
	if (allCanBan) {
		const auto peer = items.front()->history()->peer;
		auto ownedWrap = peer->isMonoforum()
			? nullptr
			: object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
				inner,
				object_ptr<Ui::VerticalLayout>(inner));
		auto computeRestrictions = Fn<ChatRestrictions()>();
		const auto wrap = ownedWrap.data();

		Ui::AddSkip(inner);
		Ui::AddSkip(inner);
		const auto ban = inner->add(
			object_ptr<Ui::Checkbox>(
				box,
				rpl::conditional(
					(ownedWrap
						? ownedWrap->toggledValue()
						: rpl::single(false) | rpl::type_erased),
					tr::lng_restrict_user(
						lt_count,
						rpl::single(participants.size()) | tr::to_count()),
					rpl::conditional(
						rpl::single(isSingle),
						tr::lng_ban_user(),
						tr::lng_ban_users())),
				options.banUser,
				st::defaultBoxCheckbox),
			st::boxRowPadding + buttonPadding);
		const auto controller = box->lifetime().make_state<Controller>(
			Controller::Data{ .participants = participants });
		Ui::AddExpandablePeerList(ban, controller, inner);
		handleSubmition(ban);

		Ui::AddSkip(inner);
		Ui::AddSkip(inner);

		if (ownedWrap) {
			inner->add(std::move(ownedWrap));

			const auto container = wrap->entity();
			wrap->toggle(false, anim::type::instant);

			const auto emojiUp = Ui::Text::IconEmoji(
				&st::moderateBoxExpandIcon);
			const auto emojiDown = Ui::Text::IconEmoji(
				&st::moderateBoxExpandIconDown);

			auto label = object_ptr<Ui::FlatLabel>(
				inner,
				QString(),
				st::moderateBoxDividerLabel);
			const auto raw = label.data();

			auto &lifetime = wrap->lifetime();
			const auto scrollLifetime = lifetime.make_state<rpl::lifetime>();
			label->setClickHandlerFilter([=](
					const ClickHandlerPtr &handler,
					Qt::MouseButton button) {
				if (button != Qt::LeftButton) {
					return false;
				}
				wrap->toggle(!wrap->toggled(), anim::type::normal);
				{
					inner->heightValue() | rpl::on_next([=] {
						if (!wrap->animating()) {
							scrollLifetime->destroy();
							Ui::PostponeCall(crl::guard(box, [=] {
								box->scrollToY(std::numeric_limits<int>::max());
							}));
						} else {
							box->scrollToY(std::numeric_limits<int>::max());
						}
					}, *scrollLifetime);
				}
				return true;
			});
			wrap->toggledValue(
			) | rpl::map([isSingle, emojiUp, emojiDown](bool toggled) {
				return ((toggled && isSingle)
					? tr::lng_restrict_user_part
					: (toggled && !isSingle)
					? tr::lng_restrict_users_part
					: isSingle
					? tr::lng_restrict_user_full
					: tr::lng_restrict_users_full)(
						lt_emoji,
						rpl::single(toggled ? emojiUp : emojiDown),
						tr::marked);
			}) | rpl::flatten_latest(
			) | rpl::on_next([=](const TextWithEntities &text) {
				raw->setMarkedText(tr::link(text, u"internal:"_q));
			}, label->lifetime());

			Ui::AddSkip(inner);
			inner->add(object_ptr<Ui::DividerLabel>(
				inner,
				std::move(label),
				st::defaultBoxDividerLabelPadding));

			using Flag = ChatRestriction;
			using Flags = ChatRestrictions;
			const auto chat = peer->asChat();
			const auto channel = peer->asChannel();
			const auto defaultRestrictions = chat
				? chat->defaultRestrictions()
				: channel->defaultRestrictions();
			const auto prepareFlags = FixDependentRestrictions(
				defaultRestrictions
				| ((channel && channel->isPublic())
					? (Flag::ChangeInfo | Flag::PinMessages)
					: Flags(0)));
			const auto disabledMessages = [&] {
				auto result = base::flat_map<Flags, QString>();
				{
					const auto disabled = FixDependentRestrictions(
						defaultRestrictions
						| ((channel && channel->isPublic())
							? (Flag::ChangeInfo | Flag::PinMessages)
							: Flags(0)));
					result.emplace(
						disabled,
						tr::lng_rights_restriction_for_all(tr::now));
				}
				return result;
			}();

			auto [checkboxes, getRestrictions, changes, highlightWidget] = CreateEditRestrictions(
				box,
				prepareFlags,
				disabledMessages,
				{ .isForum = peer->isForum(), .isUserSpecific = true });
			computeRestrictions = getRestrictions;
			std::move(changes) | rpl::on_next([=] {
				ban->setChecked(true);
			}, ban->lifetime());
			Ui::AddSkip(container);
			Ui::AddDivider(container);
			Ui::AddSkip(container);
			Ui::AddSubsectionTitle(
				container,
				rpl::conditional(
					rpl::single(isSingle),
					tr::lng_restrict_users_part_single_header(),
					tr::lng_restrict_users_part_header(
						lt_count,
						rpl::single(participants.size()) | tr::to_count())));
			container->add(std::move(checkboxes));
		}

		// Handle confirmation manually.
		confirms->events() | rpl::on_next([=] {
			if (ban->checked() && controller->collectRequests) {
				const auto kick = !wrap || !wrap->toggled();
				const auto restrictions = computeRestrictions
					? computeRestrictions()
					: ChatRestrictions();
				const auto request = [=](
						not_null<PeerData*> peer,
						not_null<ChannelData*> channel) {
					if (base::IsAltPressed() || base::IsCtrlPressed()) {
						return;
					}
					if (!kick) {
						Api::ChatParticipants::Restrict(
							channel,
							peer,
							ChatRestrictionsInfo(), // Unused.
							ChatRestrictionsInfo(restrictions, 0),
							nullptr,
							nullptr);
					} else {
						const auto block = channel->isMonoforum()
							? channel->monoforumBroadcast()
							: channel.get();
						if (block) {
							block->session().api().chatParticipants().kick(
								block,
								peer,
								{ block->restrictions(), 0 });
						}
					}
				};
				if (collectCommon && !collectCommon->empty()) {
					sequentiallyRequest(
						request,
						controller->collectRequests(),
						*collectCommon);
				} else {
					sequentiallyRequest(
						request,
						controller->collectRequests());
				}
			}
		}, ban->lifetime());
	}

	const auto close = crl::guard(box, [=] { box->closeBox(); });
	{
		const auto data = &participants.front()->session().data();
		const auto ids = data->itemsToIds(items);
		box->addButton(tr::lng_box_delete(), [=] {
			confirms->fire({});
			if (confirmed) {
				confirmed();
			}
			data->histories().deleteMessages(ids, true);
			data->sendHistoryChangeNotifications();
			close();
		});
	}
	box->addButton(tr::lng_cancel(), close);
}

bool CanCreateModerateMessagesBox(const HistoryItemsList &items) {
	const auto options = CalculateModerateOptions(items);
	return (options.allCanBan || options.allCanDelete)
		&& !options.participants.empty();
}

void SafeSubmitOnEnter(not_null<Ui::GenericBox*> box) {
	base::install_event_filter(box, [=](not_null<QEvent*> event) {
		if (event->type() == QEvent::KeyPress) {
			if (const auto k = static_cast<QKeyEvent*>(event.get())) {
				if ((k->key() == Qt::Key_Enter)
					|| (k->key() == Qt::Key_Return)) {
					box->uiShow()->show(Ui::MakeConfirmBox({
						.text = tr::lng_gigagroup_warning_title(),
						.confirmed = [=](Fn<void()> close) {
							box->triggerButton(0);
							close();
						},
						.confirmText = tr::lng_box_yes(),
						.cancelText = tr::lng_box_no(),
						}));
				}
			}
		}
		return base::EventFilterResult::Continue;
	});
}

void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
	const auto container = box->verticalLayout();

	const auto userpicPeer = peer->userpicPaintingPeer();
	const auto maybeUser = peer->asUser();
	const auto isBot = maybeUser && maybeUser->isBot();

	Ui::AddSkip(container);
	Ui::AddSkip(container);

	SafeSubmitOnEnter(box);

	const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
		container,
		userpicPeer,
		st::mainMenuUserpic,
		peer->userpicShape());
	userpic->showSavedMessagesOnSelf(true);
	Ui::IconWithTitle(
		container,
		userpic,
		Ui::CreateChild<Ui::FlatLabel>(
			container,
			peer->isSelf()
				? tr::lng_saved_messages(tr::bold)
				: maybeUser
				? tr::lng_profile_delete_conversation(tr::bold)
				: rpl::single(
					tr::bold(userpicPeer->name())
				) | rpl::type_erased,
			box->getDelegate()->style().title));

	Ui::AddSkip(container);
	Ui::AddSkip(container);

	box->addRow(
		object_ptr<Ui::FlatLabel>(
			container,
			peer->isSelf()
				? tr::lng_sure_delete_saved_messages()
				: maybeUser
				? tr::lng_sure_delete_history(
					lt_contact,
					rpl::single(peer->name()))
				: (peer->isChannel() && !peer->isMegagroup())
				? tr::lng_sure_leave_channel()
				: tr::lng_sure_leave_group(),
			st::boxLabel));

	const auto maybeCheckbox = [&]() -> Ui::Checkbox* {
		if (!peer->canRevokeFullHistory()) {
			return nullptr;
		}
		Ui::AddSkip(container);
		Ui::AddSkip(container);
		return box->addRow(
			object_ptr<Ui::Checkbox>(
				container,
				maybeUser
					? tr::lng_delete_for_other_check(
						tr::now,
						lt_user,
						TextWithEntities{ maybeUser->firstName },
						tr::rich)
					: tr::lng_delete_for_everyone_check(
						tr::now,
						tr::marked),
				GetEnhancedInt("always_delete_for") == 2 || GetEnhancedInt("always_delete_for") == 3,
				st::defaultBoxCheckbox));
	}();

	const auto maybeBotCheckbox = [&]() -> Ui::Checkbox* {
		if (!isBot) {
			return nullptr;
		}
		Ui::AddSkip(container);
		Ui::AddSkip(container);
		return box->addRow(
			object_ptr<Ui::Checkbox>(
				container,
				tr::lng_profile_block_bot(tr::now, tr::marked),
				false,
				st::defaultBoxCheckbox));
	}();

	const auto removeFromChatsFilters = [=](
			not_null<History*> history) -> std::vector<FilterId> {
		auto result = std::vector<FilterId>();
		for (const auto &filter : peer->owner().chatsFilters().list()) {
			if (filter.withoutAlways(history) != filter) {
				result.push_back(filter.id());
			}
		}
		return result;
	};

	const auto maybeChatsFiltersCheckbox = [&]() -> Ui::Checkbox* {
		const auto history = (isBot || !maybeUser)
			? peer->owner().history(peer).get()
			: nullptr;
		if (!history || removeFromChatsFilters(history).empty()) {
			return nullptr;
		}
		Ui::AddSkip(container);
		Ui::AddSkip(container);
		return box->addRow(
			object_ptr<Ui::Checkbox>(
				container,
				(maybeBotCheckbox
					? tr::lng_filters_checkbox_remove_bot
					: (peer->isChannel() && !peer->isMegagroup())
					? tr::lng_filters_checkbox_remove_channel
					: tr::lng_filters_checkbox_remove_group)(
						tr::now,
						tr::marked),
				false,
				st::defaultBoxCheckbox));
	}();

	Ui::AddSkip(container);

	auto buttonText = maybeUser
		? tr::lng_box_delete()
		: !maybeCheckbox
		? tr::lng_box_leave()
		: maybeCheckbox->checkedValue() | rpl::map([](bool checked) {
			return checked ? tr::lng_box_delete() : tr::lng_box_leave();
		}) | rpl::flatten_latest();

	const auto close = crl::guard(box, [=] { box->closeBox(); });
	box->addButton(std::move(buttonText), [=] {
		const auto revoke = maybeCheckbox && maybeCheckbox->checked();
		const auto stopBot = maybeBotCheckbox && maybeBotCheckbox->checked();
		const auto removeFromChats = maybeChatsFiltersCheckbox
			&& maybeChatsFiltersCheckbox->checked();
		Core::App().closeChatFromWindows(peer);
		if (stopBot) {
			peer->session().api().blockedPeers().block(peer);
		}
		if (removeFromChats) {
			const auto history = peer->owner().history(peer).get();
			const auto removeFrom = removeFromChatsFilters(history);
			for (const auto &filter : peer->owner().chatsFilters().list()) {
				if (!ranges::contains(removeFrom, filter.id())) {
					continue;
				}
				const auto result = filter.withoutAlways(history);
				if (result == filter) {
					continue;
				}
				const auto tl = result.tl();
				peer->owner().chatsFilters().apply(MTP_updateDialogFilter(
					MTP_flags(MTPDupdateDialogFilter::Flag::f_filter),
					MTP_int(filter.id()),
					tl));
				peer->session().api().request(MTPmessages_UpdateDialogFilter(
					MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter),
					MTP_int(filter.id()),
					tl
				)).send();
			}
		}
		// Don't delete old history by default,
		// because Android app doesn't.
		//
		//if (const auto from = peer->migrateFrom()) {
		//	peer->session().api().deleteConversation(from, false);
		//}
		peer->session().api().deleteConversation(peer, revoke);
		close();
	}, st::attentionBoxButton);
	box->addButton(tr::lng_cancel(), close);
}

void DeleteSublistBox(
		not_null<Ui::GenericBox*> box,
		not_null<Data::SavedSublist*> sublist) {
	const auto container = box->verticalLayout();

	const auto weak = base::make_weak(sublist.get());
	const auto peer = sublist->sublistPeer();

	Ui::AddSkip(container);
	Ui::AddSkip(container);

	SafeSubmitOnEnter(box);

	const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
		container,
		peer,
		st::mainMenuUserpic);
	Ui::IconWithTitle(
		container,
		userpic,
		Ui::CreateChild<Ui::FlatLabel>(
			container,
			tr::lng_profile_delete_conversation(tr::bold),
			box->getDelegate()->style().title));

	Ui::AddSkip(container);
	Ui::AddSkip(container);

	box->addRow(
		object_ptr<Ui::FlatLabel>(
			container,
			tr::lng_sure_delete_history(
				lt_contact,
				rpl::single(peer->name())),
			st::boxLabel));

	Ui::AddSkip(container);

	const auto close = crl::guard(box, [=] { box->closeBox(); });
	box->addButton(tr::lng_box_delete(), [=] {
		const auto strong = weak.get();
		const auto parentChat = strong ? strong->parentChat() : nullptr;
		if (!parentChat) {
			return;
		}
		peer->session().api().deleteSublistHistory(parentChat, peer);
		close();
	}, st::attentionBoxButton);
	box->addButton(tr::lng_cancel(), close);
}

ModerateMessagesBoxOptions DefaultModerateMessagesBoxOptions() {
	return base::IsCtrlPressed()
		? ModerateMessagesBoxOptions{
			.reportSpam = true,
			.deleteAll = true,
			.banUser = true,
		}
		: ModerateMessagesBoxOptions{};
}
