/*
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 "api/api_chat_filters.h"

#include "api/api_text_entities.h"
#include "apiwrap.h"
#include "base/event_filter.h"
#include "boxes/peer_list_box.h"
#include "boxes/premium_limits_box.h"
#include "boxes/filters/edit_filter_links.h" // FilterChatStatusText
#include "core/application.h"
#include "core/core_settings.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_peer.h"
#include "data/data_session.h"
#include "history/history.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/filter_link_header.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/widgets/buttons.h"
#include "ui/filter_icons.h"
#include "ui/vertical_list.h"
#include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "styles/style_filter_icons.h"
#include "styles/style_layers.h"
#include "styles/style_settings.h"

namespace Api {
namespace {

enum class ToggleAction {
	Adding,
	Removing,
};

class ToggleChatsController final
	: public PeerListController
	, public base::has_weak_ptr {
public:
	ToggleChatsController(
		not_null<Window::SessionController*> window,
		ToggleAction action,
		Data::ChatFilterTitle title,
		std::vector<not_null<PeerData*>> chats,
		std::vector<not_null<PeerData*>> additional);

	void prepare() override;
	void rowClicked(not_null<PeerListRow*> row) override;
	Main::Session &session() const override;

	[[nodiscard]] auto selectedValue() const
		-> rpl::producer<base::flat_set<not_null<PeerData*>>>;

	void adjust(int minHeight, int maxHeight, int addedTopHeight);
	void setRealContentHeight(rpl::producer<int> value);
	rpl::producer<int> boxHeightValue() const override;

private:
	void setupAboveWidget();
	void setupBelowWidget();
	void initDesiredHeightValue();
	void toggleAllSelected(bool select);

	const not_null<Window::SessionController*> _window;
	Ui::RpWidget *_addedTopWidget = nullptr;
	Ui::RpWidget *_addedBottomWidget = nullptr;

	ToggleAction _action = ToggleAction::Adding;
	base::flat_set<not_null<PeerData*>> _checkable;
	std::vector<not_null<PeerData*>> _chats;
	std::vector<not_null<PeerData*>> _additional;
	rpl::variable<base::flat_set<not_null<PeerData*>>> _selected;

	int _minTopHeight = 0;
	rpl::variable<int> _maxTopHeight;
	rpl::variable<int> _aboveHeight;
	rpl::variable<int> _belowHeight;
	rpl::variable<int> _desiredHeight;

	base::unique_qptr<Ui::PopupMenu> _menu;

	rpl::lifetime _lifetime;

};

[[nodiscard]] tr::phrase<> TitleText(Ui::FilterLinkHeaderType type) {
	using Type = Ui::FilterLinkHeaderType;
	switch (type) {
	case Type::AddingFilter: return tr::lng_filters_by_link_title;
	case Type::AddingChats: return tr::lng_filters_by_link_more;
	case Type::AllAdded: return tr::lng_filters_by_link_already;
	case Type::Removing: return tr::lng_filters_by_link_remove;
	}
	Unexpected("Ui::FilterLinkHeaderType in TitleText.");
}

[[nodiscard]] TextWithEntities AboutText(
		Ui::FilterLinkHeaderType type,
		TextWithEntities title) {
	using Type = Ui::FilterLinkHeaderType;
	auto boldTitle = Ui::Text::Wrapped(title, EntityType::Bold);
	return (type == Type::AddingFilter)
		? tr::lng_filters_by_link_sure(
			tr::now,
			lt_folder,
			std::move(boldTitle),
			Ui::Text::WithEntities)
		: (type == Type::AddingChats)
		? tr::lng_filters_by_link_more_sure(
			tr::now,
			lt_folder,
			std::move(boldTitle),
			Ui::Text::WithEntities)
		: (type == Type::AllAdded)
		? tr::lng_filters_by_link_already_about(
			tr::now,
			lt_folder,
			std::move(boldTitle),
			Ui::Text::WithEntities)
		: tr::lng_filters_by_link_remove_sure(
			tr::now,
			lt_folder,
			std::move(boldTitle),
			Ui::Text::WithEntities);
}

void InitFilterLinkHeader(
		not_null<PeerListBox*> box,
		Fn<void(int minHeight, int maxHeight, int addedTopHeight)> adjust,
		Ui::FilterLinkHeaderType type,
		Data::ChatFilterTitle title,
		QString iconEmoji,
		rpl::producer<int> count,
		bool horizontalFilters) {
	const auto icon = Ui::LookupFilterIcon(
		Ui::LookupFilterIconByEmoji(
			iconEmoji
		).value_or(Ui::FilterIcon::Custom)).active;
	const auto isStatic = title.isStatic;
	auto header = Ui::MakeFilterLinkHeader(box, {
		.type = type,
		.title = TitleText(type)(tr::now),
		.about = AboutText(type, title.text),
		.aboutContext = Core::TextContext({
			.session = &box->peerListUiShow()->session(),
			.customEmojiLoopLimit = isStatic ? -1 : 0,
		}),
		.folderTitle = title.text,
		.folderIcon = icon,
		.badge = (type == Ui::FilterLinkHeaderType::AddingChats
			? std::move(count)
			: rpl::single(0)),
		.horizontalFilters = horizontalFilters,
	});
	const auto widget = header.widget;
	widget->resizeToWidth(st::boxWideWidth);
	Ui::SendPendingMoveResizeEvents(widget);

	const auto min = widget->minimumHeight(), max = widget->maximumHeight();
	widget->resize(st::boxWideWidth, max);

	box->setAddedTopScrollSkip(max);
	std::move(
		header.wheelEvents
	) | rpl::start_with_next([=](not_null<QWheelEvent*> e) {
		box->sendScrollViewportEvent(e);
	}, widget->lifetime());

	std::move(
		header.closeRequests
	) | rpl::start_with_next([=] {
		box->closeBox();
	}, widget->lifetime());

	struct State {
		bool processing = false;
		int addedTopHeight = 0;
	};
	const auto state = widget->lifetime().make_state<State>();

	box->scrolls(
	) | rpl::filter([=] {
		return !state->processing;
	}) | rpl::start_with_next([=] {
		state->processing = true;
		const auto guard = gsl::finally([&] { state->processing = false; });

		const auto top = box->scrollTop();
		const auto headerHeight = std::max(max - top, min);
		const auto addedTopHeight = max - headerHeight;
		widget->resize(widget->width(), headerHeight);
		if (state->addedTopHeight < addedTopHeight) {
			adjust(min, max, addedTopHeight);
			box->setAddedTopScrollSkip(headerHeight);
		} else {
			box->setAddedTopScrollSkip(headerHeight);
			adjust(min, max, addedTopHeight);
		}
		state->addedTopHeight = addedTopHeight;
		box->peerListRefreshRows();
	}, widget->lifetime());

	box->setNoContentMargin(true);
	adjust(min, max, 0);
}

void ImportInvite(
		const QString &slug,
		FilterId filterId,
		const base::flat_set<not_null<PeerData*>> &peers,
		Fn<void()> done,
		Fn<void(QString)> fail) {
	Expects(!peers.empty());

	const auto peer = peers.front();
	const auto api = &peer->session().api();
	const auto callback = [=](const MTPUpdates &result) {
		api->applyUpdates(result);
		if (slug.isEmpty()) {
			peer->owner().chatsFilters().moreChatsHide(filterId, true);
		}
		done();
	};
	const auto error = [=](const MTP::Error &error) {
		fail(error.type());
	};
	auto inputs = peers | ranges::views::transform([](auto peer) {
		return MTPInputPeer(peer->input);
	}) | ranges::to<QVector<MTPInputPeer>>();
	if (!slug.isEmpty()) {
		api->request(MTPchatlists_JoinChatlistInvite(
			MTP_string(slug),
			MTP_vector<MTPInputPeer>(std::move(inputs))
		)).done(callback).fail(error).handleFloodErrors().send();
	} else {
		api->request(MTPchatlists_JoinChatlistUpdates(
			MTP_inputChatlistDialogFilter(MTP_int(filterId)),
			MTP_vector<MTPInputPeer>(std::move(inputs))
		)).done(callback).fail(error).handleFloodErrors().send();
	}
}

ToggleChatsController::ToggleChatsController(
	not_null<Window::SessionController*> window,
	ToggleAction action,
	Data::ChatFilterTitle title,
	std::vector<not_null<PeerData*>> chats,
	std::vector<not_null<PeerData*>> additional)
: _window(window)
, _action(action)
, _chats(std::move(chats))
, _additional(std::move(additional)) {
	setStyleOverrides(&st::filterLinkChatsList);
}

void ToggleChatsController::prepare() {
	auto selected = base::flat_set<not_null<PeerData*>>();
	const auto disabled = [](not_null<PeerData*> peer) {
		return peer->isChat()
			? peer->asChat()->isForbidden()
			: peer->isChannel()
			? peer->asChannel()->isForbidden()
			: false;
	};
	const auto add = [&](not_null<PeerData*> peer, bool additional = false) {
		const auto disable = disabled(peer);
		auto row = (additional || !disable)
			? std::make_unique<PeerListRow>(peer)
			: MakeFilterChatRow(
				peer,
				tr::lng_filters_link_inaccessible(tr::now),
				true);
		if (delegate()->peerListFindRow(peer->id.value)) {
			return;
		}
		const auto raw = row.get();
		delegate()->peerListAppendRow(std::move(row));
		if (!disable
			&& (!additional || _action == ToggleAction::Removing)) {
			_checkable.emplace(peer);
			if (const auto status = FilterChatStatusText(peer)
				; !status.isEmpty()) {
				raw->setCustomStatus(status);
			}
		}
		if (disable) {
		} else if (!additional) {
			delegate()->peerListSetRowChecked(raw, true);
			raw->finishCheckedAnimation();
			selected.emplace(peer);
		} else if (_action == ToggleAction::Adding) {
			raw->setDisabledState(PeerListRow::State::DisabledChecked);
			raw->setCustomStatus(peer->isBroadcast()
				? tr::lng_filters_link_already_channel(tr::now)
				: tr::lng_filters_link_already_group(tr::now));
		}
	};
	for (const auto &peer : _chats) {
		if (!disabled(peer)) {
			add(peer);
		}
	}
	for (const auto &peer : _additional) {
		add(peer, true);
	}
	for (const auto &peer : _chats) {
		if (disabled(peer)) {
			add(peer);
		}
	}
	setupAboveWidget();
	setupBelowWidget();
	initDesiredHeightValue();
	delegate()->peerListRefreshRows();
	_selected = std::move(selected);
}

void ToggleChatsController::rowClicked(not_null<PeerListRow*> row) {
	const auto peer = row->peer();
	if (!_checkable.contains(peer)) {
		return;
	}
	const auto checked = row->checked();
	auto selected = _selected.current();
	delegate()->peerListSetRowChecked(row, !checked);
	if (checked) {
		selected.remove(peer);
	} else {
		selected.emplace(peer);
	}
	_selected = std::move(selected);
}

void ToggleChatsController::setupAboveWidget() {
	using namespace Settings;

	auto wrap = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
	const auto container = wrap.data();

	_addedTopWidget = container->add(object_ptr<Ui::RpWidget>(container));
	const auto realAbove = container->add(
		object_ptr<Ui::VerticalLayout>(container));
	Ui::AddDivider(realAbove);
	const auto totalCount = [&] {
		if (_chats.empty()) {
			return _additional.size();
		} else if (_additional.empty()) {
			return _chats.size();
		}
		auto result = _chats.size();
		for (const auto &peer : _additional) {
			if (!ranges::contains(_chats, peer)) {
				++result;
			}
		}
		return result;
	};
	const auto count = (_action == ToggleAction::Removing)
		? totalCount()
		: _chats.empty()
		? _additional.size()
		: _chats.size();
	const auto selectableCount = int(_checkable.size());
	auto selectedCount = _selected.value(
	) | rpl::map([](const base::flat_set<not_null<PeerData*>> &selected) {
		return int(selected.size());
	});
	AddFilterSubtitleWithToggles(
		realAbove,
		(_action == ToggleAction::Removing
			? tr::lng_filters_by_link_quit
			: _chats.empty()
			? tr::lng_filters_by_link_in
			: tr::lng_filters_by_link_join)(
				lt_count,
				rpl::single(float64(count))),
		selectableCount,
		std::move(selectedCount),
		[=](bool select) { toggleAllSelected(select); });

	_aboveHeight = realAbove->heightValue();
	delegate()->peerListSetAboveWidget(std::move(wrap));
}

void ToggleChatsController::toggleAllSelected(bool select) {
	auto selected = _selected.current();
	if (!select) {
		if (selected.empty()) {
			return;
		}
		for (const auto &peer : selected) {
			const auto row = delegate()->peerListFindRow(peer->id.value);
			Assert(row != nullptr);
			delegate()->peerListSetRowChecked(row, false);
		}
		selected = {};
	} else {
		const auto count = delegate()->peerListFullRowsCount();
		for (auto i = 0; i != count; ++i) {
			const auto row = delegate()->peerListRowAt(i);
			const auto peer = row->peer();
			if (_action != ToggleAction::Adding ||
				!ranges::contains(_additional, peer)) {
				delegate()->peerListSetRowChecked(row, true);
				selected.emplace(peer);
			}
		}
	}
	_selected = std::move(selected);
}

void ToggleChatsController::setupBelowWidget() {
	if (_chats.empty()) {
		auto widget = object_ptr<Ui::RpWidget>((QWidget*)nullptr);
		_addedBottomWidget = widget.data();
		delegate()->peerListSetBelowWidget(std::move(widget));
		return;
	}
	auto layout = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
	const auto raw = layout.data();
	auto widget = object_ptr<Ui::DividerLabel>(
		(QWidget*)nullptr,
		std::move(layout),
		st::defaultBoxDividerLabelPadding);
	raw->add(object_ptr<Ui::FlatLabel>(
		raw,
		(_action == ToggleAction::Removing
			? tr::lng_filters_by_link_about_quit
			: tr::lng_filters_by_link_about)(tr::now),
		st::boxDividerLabel));
	_addedBottomWidget = raw->add(object_ptr<Ui::RpWidget>(raw));
	_belowHeight = widget->heightValue() | rpl::map([=](int value) {
		return value - _addedBottomWidget->height();
	});
	delegate()->peerListSetBelowWidget(std::move(widget));
}

Main::Session &ToggleChatsController::session() const {
	return _window->session();
}

auto ToggleChatsController::selectedValue() const
-> rpl::producer<base::flat_set<not_null<PeerData*>>> {
	return _selected.value();
}

void ToggleChatsController::adjust(
		int minHeight,
		int maxHeight,
		int addedTopHeight) {
	Expects(addedTopHeight >= 0);

	_addedTopWidget->resize(_addedTopWidget->width(), addedTopHeight);
	_minTopHeight = minHeight;
	_maxTopHeight = maxHeight;
}

void ToggleChatsController::setRealContentHeight(rpl::producer<int> value) {
	std::move(
		value
	) | rpl::start_with_next([=](int height) {
		const auto desired = _desiredHeight.current();
		if (height <= computeListSt().item.height) {
			return;
		} else if (height >= desired) {
			_addedBottomWidget->resize(_addedBottomWidget->width(), 0);
		} else {
			const auto available = desired - height;
			const auto required = _maxTopHeight.current() - _minTopHeight;
			const auto added = required - available;
			_addedBottomWidget->resize(
				_addedBottomWidget->width(),
				std::max(added, 0));
		}
	}, _lifetime);
}

void ToggleChatsController::initDesiredHeightValue() {
	using namespace rpl::mappers;

	const auto &st = computeListSt();
	const auto count = int(delegate()->peerListFullRowsCount());
	const auto middle = st.padding.top()
		+ (count * st.item.height)
		+ st.padding.bottom();
	_desiredHeight = rpl::combine(
		_maxTopHeight.value(),
		_aboveHeight.value(),
		_belowHeight.value(),
		_1 + _2 + middle + _3);
}

rpl::producer<int> ToggleChatsController::boxHeightValue() const {
	return _desiredHeight.value() | rpl::map([=](int value) {
		return std::min(value, st::boxMaxListHeight);
	});
}

void ShowImportError(
		not_null<Window::SessionController*> window,
		FilterId id,
		int added,
		const QString &error) {
	const auto session = &window->session();
	const auto &list = session->data().chatsFilters().list();
	const auto i = ranges::find(list, id, &Data::ChatFilter::id);
	const auto count = added
		+ ((i != end(list)) ? int(i->always().size()) : 0);
	if (error == u"CHANNELS_TOO_MUCH"_q) {
		window->show(Box(ChannelsLimitBox, session));
	} else if (error == u"FILTER_INCLUDE_TOO_MUCH"_q) {
		window->show(Box(FilterChatsLimitBox, session, count, true));
	} else if (error == u"CHATLISTS_TOO_MUCH"_q) {
		window->show(Box(ShareableFiltersLimitBox, session));
	} else {
		window->showToast((error == u"INVITE_SLUG_EXPIRED"_q)
			? tr::lng_group_invite_bad_link(tr::now)
			: error.startsWith(u"FLOOD_WAIT_"_q)
			? tr::lng_flood_error(tr::now)
			: error);
	}
}

void ShowImportToast(
		base::weak_ptr<Window::SessionController> weak,
		Data::ChatFilterTitle title,
		Ui::FilterLinkHeaderType type,
		int added) {
	const auto strong = weak.get();
	if (!strong) {
		return;
	}
	const auto created = (type == Ui::FilterLinkHeaderType::AddingFilter);
	const auto phrase = created
		? tr::lng_filters_added_title
		: tr::lng_filters_updated_title;
	auto text = Ui::Text::Wrapped(
		phrase(tr::now, lt_folder, title.text, Ui::Text::WithEntities),
		EntityType::Bold);
	if (added > 0) {
		const auto phrase = created
			? tr::lng_filters_added_also
			: tr::lng_filters_updated_also;
		text.append('\n').append(phrase(tr::now, lt_count, added));
	}
	const auto isStatic = title.isStatic;
	strong->showToast({
		.text = std::move(text),
		.textContext = Core::TextContext({
			.session = &strong->session(),
			.customEmojiLoopLimit = isStatic ? -1 : 0,
		})
	});
}

void HandleEnterInBox(not_null<Ui::BoxContent*> box) {
	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;
	});
}

void ProcessFilterInvite(
		base::weak_ptr<Window::SessionController> weak,
		const QString &slug,
		FilterId filterId,
		Data::ChatFilterTitle title,
		QString iconEmoji,
		std::vector<not_null<PeerData*>> peers,
		std::vector<not_null<PeerData*>> already) {
	const auto strong = weak.get();
	if (!strong) {
		return;
	}
	Core::App().hideMediaView();
	if (peers.empty() && !filterId) {
		strong->showToast(tr::lng_group_invite_bad_link(tr::now));
		return;
	}
	const auto fullyAdded = (peers.empty() && filterId);
	auto controller = std::make_unique<ToggleChatsController>(
		strong,
		ToggleAction::Adding,
		title,
		std::move(peers),
		std::move(already));
	const auto horizontalFilters = !strong->enoughSpaceForFilters()
		|| Core::App().settings().chatFiltersHorizontal();
	const auto raw = controller.get();
	auto initBox = [=](not_null<PeerListBox*> box) {
		box->setStyle(st::filterInviteBox);

		using Type = Ui::FilterLinkHeaderType;
		const auto type = fullyAdded
			? Type::AllAdded
			: !filterId
			? Type::AddingFilter
			: Type::AddingChats;
		auto badge = raw->selectedValue(
		) | rpl::map([=](const base::flat_set<not_null<PeerData*>> &peers) {
			return int(peers.size());
		});
		InitFilterLinkHeader(box, [=](int min, int max, int addedTop) {
			raw->adjust(min, max, addedTop);
		}, type, title, iconEmoji, rpl::duplicate(badge), horizontalFilters);

		raw->setRealContentHeight(box->heightValue());

		const auto isStatic = title.isStatic;
		auto owned = Ui::FilterLinkProcessButton(
			box,
			type,
			title.text,
			Core::TextContext({
				.session = &strong->session(),
				.customEmojiLoopLimit = isStatic ? -1 : 0,
			}),
			std::move(badge));

		const auto button = owned.data();
		box->widthValue(
		) | rpl::start_with_next([=](int width) {
			const auto &padding = st::filterInviteBox.buttonPadding;
			button->resizeToWidth(width
				- padding.left()
				- padding.right());
			button->moveToLeft(padding.left(), padding.top());
		}, button->lifetime());

		box->addButton(std::move(owned));

		HandleEnterInBox(box);

		struct State {
			bool importing = false;
		};
		const auto state = box->lifetime().make_state<State>();

		raw->selectedValue(
		) | rpl::start_with_next([=](
				base::flat_set<not_null<PeerData*>> &&peers) {
			button->setClickedCallback([=] {
				if (peers.empty()) {
					box->closeBox();
				} else if (!state->importing) {
					state->importing = true;
					const auto added = int(peers.size());
					ImportInvite(slug, filterId, peers, crl::guard(box, [=] {
						ShowImportToast(weak, title, type, peers.size());
						box->closeBox();
					}), crl::guard(box, [=](QString text) {
						if (const auto strong = weak.get()) {
							ShowImportError(strong, filterId, added, text);
						}
						state->importing = false;
					}));
				}
			});
		}, box->lifetime());
	};
	strong->show(
		Box<PeerListBox>(std::move(controller), std::move(initBox)));
}

void ProcessFilterInvite(
		base::weak_ptr<Window::SessionController> weak,
		const QString &slug,
		FilterId filterId,
		std::vector<not_null<PeerData*>> peers,
		std::vector<not_null<PeerData*>> already) {
	const auto strong = weak.get();
	if (!strong) {
		return;
	}
	Core::App().hideMediaView();
	const auto &list = strong->session().data().chatsFilters().list();
	const auto it = ranges::find(list, filterId, &Data::ChatFilter::id);
	if (it == end(list)) {
		strong->showToast(u"Filter not found :shrug:"_q);
		return;
	}
	ProcessFilterInvite(
		weak,
		slug,
		filterId,
		it->title(),
		it->iconEmoji(),
		std::move(peers),
		std::move(already));
}

} // namespace

void SaveNewFilterPinned(
		not_null<Main::Session*> session,
		FilterId filterId) {
	const auto &order = session->data().pinnedChatsOrder(filterId);
	auto &filters = session->data().chatsFilters();
	const auto &filter = filters.applyUpdatedPinned(filterId, order);
	session->api().request(MTPmessages_UpdateDialogFilter(
		MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter),
		MTP_int(filterId),
		filter.tl()
	)).send();
}

void CheckFilterInvite(
		not_null<Window::SessionController*> controller,
		const QString &slug) {
	const auto session = &controller->session();
	const auto weak = base::make_weak(controller);
	session->api().checkFilterInvite(slug, [=](
			const MTPchatlists_ChatlistInvite &result) {
		const auto strong = weak.get();
		if (!strong) {
			return;
		}
		auto title = Data::ChatFilterTitle();
		auto iconEmoji = QString();
		auto filterId = FilterId();
		auto peers = std::vector<not_null<PeerData*>>();
		auto already = std::vector<not_null<PeerData*>>();
		auto &owner = strong->session().data();
		result.match([&](const auto &data) {
			owner.processUsers(data.vusers());
			owner.processChats(data.vchats());
		});
		const auto parseList = [&](const MTPVector<MTPPeer> &list) {
			auto result = std::vector<not_null<PeerData*>>();
			result.reserve(list.v.size());
			for (const auto &peer : list.v) {
				result.push_back(owner.peer(peerFromMTP(peer)));
			}
			return result;
		};
		result.match([&](const MTPDchatlists_chatlistInvite &data) {
			title.text = ParseTextWithEntities(session, data.vtitle());
			title.isStatic = data.is_title_noanimate();
			iconEmoji = data.vemoticon().value_or_empty();
			peers = parseList(data.vpeers());
		}, [&](const MTPDchatlists_chatlistInviteAlready &data) {
			filterId = data.vfilter_id().v;
			peers = parseList(data.vmissing_peers());
			already = parseList(data.valready_peers());
		});

		const auto notLoaded = filterId
			&& !ranges::contains(
				owner.chatsFilters().list(),
				filterId,
				&Data::ChatFilter::id);
		if (notLoaded) {
			const auto lifetime = std::make_shared<rpl::lifetime>();
			owner.chatsFilters().changed(
			) | rpl::start_with_next([=] {
				lifetime->destroy();
				ProcessFilterInvite(
					weak,
					slug,
					filterId,
					std::move(peers),
					std::move(already));
			}, *lifetime);
			owner.chatsFilters().reload();
		} else if (filterId) {
			ProcessFilterInvite(
				weak,
				slug,
				filterId,
				std::move(peers),
				std::move(already));
		} else {
			ProcessFilterInvite(
				weak,
				slug,
				filterId,
				title,
				iconEmoji,
				std::move(peers),
				std::move(already));
		}
	}, [=](const MTP::Error &error) {
		if (error.code() != 400) {
			return;
		}
		ProcessFilterInvite(weak, slug, {}, {}, {}, {}, {});
	});
}

void ProcessFilterUpdate(
		base::weak_ptr<Window::SessionController> weak,
		FilterId filterId,
		std::vector<not_null<PeerData*>> missing) {
	if (const auto strong = missing.empty() ? weak.get() : nullptr) {
		strong->session().data().chatsFilters().moreChatsHide(filterId);
		return;
	}
	ProcessFilterInvite(weak, QString(), filterId, std::move(missing), {});
}

void ProcessFilterRemove(
		base::weak_ptr<Window::SessionController> weak,
		Data::ChatFilterTitle title,
		QString iconEmoji,
		std::vector<not_null<PeerData*>> all,
		std::vector<not_null<PeerData*>> suggest,
		Fn<void(std::vector<not_null<PeerData*>>)> done) {
	const auto strong = weak.get();
	if (!strong) {
		return;
	}
	Core::App().hideMediaView();
	if (all.empty() && suggest.empty()) {
		done({});
		return;
	}
	auto controller = std::make_unique<ToggleChatsController>(
		strong,
		ToggleAction::Removing,
		title,
		std::move(suggest),
		std::move(all));
	const auto horizontalFilters = !strong->enoughSpaceForFilters()
		|| Core::App().settings().chatFiltersHorizontal();
	const auto raw = controller.get();
	auto initBox = [=](not_null<PeerListBox*> box) {
		box->setStyle(st::filterInviteBox);

		const auto type = Ui::FilterLinkHeaderType::Removing;
		auto badge = raw->selectedValue(
		) | rpl::map([=](const base::flat_set<not_null<PeerData*>> &peers) {
			return int(peers.size());
		});
		InitFilterLinkHeader(box, [=](int min, int max, int addedTop) {
			raw->adjust(min, max, addedTop);
		}, type, title, iconEmoji, rpl::single(0), horizontalFilters);

		const auto isStatic = title.isStatic;
		auto owned = Ui::FilterLinkProcessButton(
			box,
			type,
			title.text,
			Core::TextContext({
				.session = &strong->session(),
				.customEmojiLoopLimit = isStatic ? -1 : 0,
			}),
			std::move(badge));

		const auto button = owned.data();
		box->widthValue(
		) | rpl::start_with_next([=](int width) {
			const auto &padding = st::filterInviteBox.buttonPadding;
			button->resizeToWidth(width
				- padding.left()
				- padding.right());
			button->moveToLeft(padding.left(), padding.top());
		}, button->lifetime());

		box->addButton(std::move(owned));

		HandleEnterInBox(box);

		raw->selectedValue(
		) | rpl::start_with_next([=](
				base::flat_set<not_null<PeerData*>> &&peers) {
			button->setClickedCallback([=] {
				done(peers | ranges::to_vector);
				box->closeBox();
			});
		}, box->lifetime());
	};
	strong->show(
		Box<PeerListBox>(std::move(controller), std::move(initBox)));
}

[[nodiscard]] std::vector<not_null<PeerData*>> ExtractSuggestRemoving(
		const Data::ChatFilter &filter) {
	if (!filter.chatlist()) {
		return {};
	}
	return filter.always() | ranges::views::filter([](
		not_null<History*> history) {
		return history->peer->isChannel();
	}) | ranges::views::transform(&History::peer) | ranges::to_vector;
}

} // namespace Api
