/*
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/peers/edit_peer_info_box.h"

#include "apiwrap.h"
#include "auth_session.h"
#include "boxes/add_contact_box.h"
#include "boxes/confirm_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/peers/edit_participants_box.h"
#include "boxes/peers/edit_peer_type_box.h"
#include "boxes/peers/edit_peer_history_visibility_box.h"
#include "boxes/peers/edit_peer_permissions_box.h"
#include "boxes/peers/edit_linked_chat_box.h"
#include "boxes/stickers_box.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "history/admin_log/history_admin_log_section.h"
#include "info/profile/info_profile_button.h"
#include "info/profile/info_profile_values.h"
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "mtproto/sender.h"
#include "observer_peer.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"
#include "ui/rp_widget.h"
#include "ui/special_buttons.h"
#include "ui/toast/toast.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_controller.h"
#include <rpl/flatten_latest.h>
#include <rpl/range.h>
#include "info/profile/info_profile_icon.h"

namespace {

auto ToPositiveNumberString() {
	return rpl::map([](int count) {
		return count ? QString::number(count) : QString();
	});
}

auto ToPositiveNumberStringRestrictions() {
	return rpl::map([](int count) {
		return QString::number(count)
		+ QString("/")
		+ QString::number(int(Data::ListOfRestrictions().size()));
	});
}

void AddSkip(
		not_null<Ui::VerticalLayout*> container,
		int top = st::editPeerTopButtonsLayoutSkip,
		int bottom = st::editPeerTopButtonsLayoutSkipToBottom) {
	container->add(object_ptr<Ui::FixedHeightWidget>(
		container,
		top));
	container->add(object_ptr<BoxContentDivider>(container));
	container->add(object_ptr<Ui::FixedHeightWidget>(
		container,
		bottom));
}

void AddButtonWithCount(
		not_null<Ui::VerticalLayout*> parent,
		rpl::producer<QString> &&text,
		rpl::producer<QString> &&count,
		Fn<void()> callback,
		const style::icon &icon) {
	parent->add(EditPeerInfoBox::CreateButton(
		parent,
		std::move(text),
		std::move(count),
		std::move(callback),
		st::manageGroupButton,
		&icon));
}

object_ptr<Info::Profile::Button> CreateButtonWithText(
		not_null<QWidget*> parent,
		rpl::producer<QString> &&text,
		rpl::producer<QString> &&label,
		Fn<void()> callback) {
	return EditPeerInfoBox::CreateButton(
		parent,
		std::move(text),
		std::move(label),
		std::move(callback),
		st::manageGroupTopButtonWithText,
		nullptr);
}

Info::Profile::Button *AddButtonWithText(
		not_null<Ui::VerticalLayout*> parent,
		rpl::producer<QString> &&text,
		rpl::producer<QString> &&label,
		Fn<void()> callback) {
	return parent->add(CreateButtonWithText(
		parent,
		std::move(text),
		std::move(label),
		std::move(callback)));
}

void AddButtonDelete(
		not_null<Ui::VerticalLayout*> parent,
		rpl::producer<QString> &&text,
		Fn<void()> callback) {
	parent->add(EditPeerInfoBox::CreateButton(
		parent,
		std::move(text),
		rpl::single(QString()),
		std::move(callback),
		st::manageDeleteGroupButton,
		nullptr));
}

void ShowEditPermissions(not_null<PeerData*> peer) {
	const auto box = Ui::show(
		Box<EditPeerPermissionsBox>(peer),
		LayerOption::KeepOther);
	box->saveEvents(
	) | rpl::start_with_next([=](MTPDchatBannedRights::Flags restrictions) {
		const auto callback = crl::guard(box, [=](bool success) {
			if (success) {
				box->closeBox();
			}
		});
		peer->session().api().saveDefaultRestrictions(
			peer->migrateToOrMe(),
			MTP_chatBannedRights(MTP_flags(restrictions), MTP_int(0)),
			callback);
	}, box->lifetime());
}

} // namespace

namespace {

constexpr auto kMaxGroupChannelTitle = 255; // See also add_contact_box.
constexpr auto kMaxChannelDescription = 255; // See also add_contact_box.

class Controller
	: public base::has_weak_ptr
	, private MTP::Sender {
public:
	Controller(
		not_null<BoxContent*> box,
		not_null<PeerData*> peer);

	object_ptr<Ui::VerticalLayout> createContent();
	void setFocus();

private:
	struct Controls {
		Ui::InputField *title = nullptr;
		Ui::InputField *description = nullptr;
		Ui::UserpicButton *photo = nullptr;
		rpl::lifetime initialPhotoImageWaiting;
		Ui::VerticalLayout *buttonsLayout = nullptr;
		Ui::SlideWrap<Ui::RpWidget> *historyVisibilityWrap = nullptr;
	};
	struct Saving {
		std::optional<QString> username;
		std::optional<QString> title;
		std::optional<QString> description;
		std::optional<bool> hiddenPreHistory;
		std::optional<bool> signatures;
		std::optional<ChannelData*> linkedChat;
	};

	object_ptr<Ui::RpWidget> createPhotoAndTitleEdit();
	object_ptr<Ui::RpWidget> createTitleEdit();
	object_ptr<Ui::RpWidget> createPhotoEdit();
	object_ptr<Ui::RpWidget> createDescriptionEdit();
	object_ptr<Ui::RpWidget> createManageGroupButtons();
	object_ptr<Ui::RpWidget> createStickersEdit();

	bool canEditInformation() const;
	void refreshHistoryVisibility(bool instant);
	void showEditPeerTypeBox(std::optional<LangKey> error = std::nullopt);
	void showEditLinkedChatBox();
	void fillPrivacyTypeButton();
	void fillLinkedChatButton();
	void fillInviteLinkButton();
	void fillSignaturesButton();
	void fillHistoryVisibilityButton();
	void fillManageSection();

	void submitTitle();
	void submitDescription();
	void deleteWithConfirmation();
	void deleteChannel();

	std::optional<Saving> validate() const;
	bool validateUsername(Saving &to) const;
	bool validateLinkedChat(Saving &to) const;
	bool validateTitle(Saving &to) const;
	bool validateDescription(Saving &to) const;
	bool validateHistoryVisibility(Saving &to) const;
	bool validateSignatures(Saving &to) const;

	void save();
	void saveUsername();
	void saveLinkedChat();
	void saveTitle();
	void saveDescription();
	void saveHistoryVisibility();
	void saveSignatures();
	void savePhoto();
	void pushSaveStage(FnMut<void()> &&lambda);
	void continueSave();
	void cancelSave();

	void subscribeToMigration();
	void migrate(not_null<ChannelData*> channel);

	std::optional<Privacy> _privacySavedValue;
	std::optional<ChannelData*> _linkedChatSavedValue;
	ChannelData *_linkedChatOriginalValue = nullptr;
	std::optional<HistoryVisibility> _historyVisibilitySavedValue;
	std::optional<QString> _usernameSavedValue;
	std::optional<bool> _signaturesSavedValue;

	not_null<BoxContent*> _box;
	not_null<PeerData*> _peer;
	bool _isGroup = false;

	base::unique_qptr<Ui::VerticalLayout> _wrap;
	Controls _controls;

	std::deque<FnMut<void()>> _saveStagesQueue;
	Saving _savingData;

	const rpl::event_stream<Privacy> _privacyTypeUpdates;
	const rpl::event_stream<ChannelData*> _linkedChatUpdates;
	MTP::Sender _linkedChatsRequester;
	mtpRequestId _linkedChatsRequestId = 0;

	rpl::lifetime _lifetime;

};

Controller::Controller(
	not_null<BoxContent*> box,
	not_null<PeerData*> peer)
: _box(box)
, _peer(peer)
, _isGroup(_peer->isChat() || _peer->isMegagroup()) {
	_box->setTitle(langFactory(_isGroup
		? lng_edit_group
		: lng_edit_channel_title));
	_box->addButton(langFactory(lng_settings_save), [this] {
		save();
	});
	_box->addButton(langFactory(lng_cancel), [this] {
		_box->closeBox();
	});
	subscribeToMigration();
	_peer->updateFull();
}

void Controller::subscribeToMigration() {
	SubscribeToMigration(
		_peer,
		_lifetime,
		[=](not_null<ChannelData*> channel) { migrate(channel); });
}

void Controller::migrate(not_null<ChannelData*> channel) {
	_peer = channel;
	_peer->updateFull();
}

object_ptr<Ui::VerticalLayout> Controller::createContent() {
	auto result = object_ptr<Ui::VerticalLayout>(_box);
	_wrap.reset(result.data());
	_controls = Controls();

	_wrap->add(createPhotoAndTitleEdit());
	_wrap->add(createDescriptionEdit());
	_wrap->add(createManageGroupButtons());

	return result;
}

void Controller::setFocus() {
	if (_controls.title) {
		_controls.title->setFocusFast();
	}
}

object_ptr<Ui::RpWidget> Controller::createPhotoAndTitleEdit() {
	Expects(_wrap != nullptr);

	if (!canEditInformation()) {
		return nullptr;
	}

	auto result = object_ptr<Ui::RpWidget>(_wrap);
	const auto container = result.data();

	const auto photoWrap = Ui::AttachParentChild(
		container,
		createPhotoEdit());
	const auto titleEdit = Ui::AttachParentChild(
		container,
		createTitleEdit());
	photoWrap->heightValue(
	) | rpl::start_with_next([container](int height) {
		container->resize(container->width(), height);
	}, photoWrap->lifetime());
	container->widthValue(
	) | rpl::start_with_next([titleEdit](int width) {
		const auto left = st::editPeerPhotoMargins.left()
			+ st::defaultUserpicButton.size.width();
		titleEdit->resizeToWidth(width - left);
		titleEdit->moveToLeft(left, 0, width);
	}, titleEdit->lifetime());

	return result;
}

object_ptr<Ui::RpWidget> Controller::createPhotoEdit() {
	Expects(_wrap != nullptr);

	using PhotoWrap = Ui::PaddingWrap<Ui::UserpicButton>;
	auto photoWrap = object_ptr<PhotoWrap>(
		_wrap,
		object_ptr<Ui::UserpicButton>(
			_wrap,
			_peer,
			Ui::UserpicButton::Role::ChangePhoto,
			st::defaultUserpicButton),
		st::editPeerPhotoMargins);
	_controls.photo = photoWrap->entity();

	return photoWrap;
}

object_ptr<Ui::RpWidget> Controller::createTitleEdit() {
	Expects(_wrap != nullptr);

	auto result = object_ptr<Ui::PaddingWrap<Ui::InputField>>(
		_wrap,
		object_ptr<Ui::InputField>(
			_wrap,
			st::defaultInputField,
			langFactory(_isGroup
				? lng_dlg_new_group_name
				: lng_dlg_new_channel_name),
			_peer->name),
		st::editPeerTitleMargins);
	result->entity()->setMaxLength(kMaxGroupChannelTitle);
	result->entity()->setInstantReplaces(Ui::InstantReplaces::Default());
	result->entity()->setInstantReplacesEnabled(
		Global::ReplaceEmojiValue());
	Ui::Emoji::SuggestionsController::Init(
		_wrap->window(),
		result->entity());

	QObject::connect(
		result->entity(),
		&Ui::InputField::submitted,
		[=] { submitTitle(); });

	_controls.title = result->entity();
	return std::move(result);
}

object_ptr<Ui::RpWidget> Controller::createDescriptionEdit() {
	Expects(_wrap != nullptr);

	if (!canEditInformation()) {
		return nullptr;
	}

	auto result = object_ptr<Ui::PaddingWrap<Ui::InputField>>(
		_wrap,
		object_ptr<Ui::InputField>(
			_wrap,
			st::editPeerDescription,
			Ui::InputField::Mode::MultiLine,
			langFactory(lng_create_group_description),
			_peer->about()),
		st::editPeerDescriptionMargins);
	result->entity()->setMaxLength(kMaxChannelDescription);
	result->entity()->setInstantReplaces(Ui::InstantReplaces::Default());
	result->entity()->setInstantReplacesEnabled(
		Global::ReplaceEmojiValue());
	Ui::Emoji::SuggestionsController::Init(
		_wrap->window(),
		result->entity());

	QObject::connect(
		result->entity(),
		&Ui::InputField::submitted,
		[=] { submitDescription(); });

	_controls.description = result->entity();
	return std::move(result);
}

object_ptr<Ui::RpWidget> Controller::createManageGroupButtons() {
	Expects(_wrap != nullptr);

	auto result = object_ptr<Ui::PaddingWrap<Ui::VerticalLayout>>(
		_wrap,
		object_ptr<Ui::VerticalLayout>(_wrap),
		st::editPeerBottomButtonsLayoutMargins);
	_controls.buttonsLayout = result->entity();

	fillManageSection();

	return std::move(result);
}

object_ptr<Ui::RpWidget> Controller::createStickersEdit() {
	Expects(_wrap != nullptr);

	const auto channel = _peer->asChannel();

	auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
		_wrap,
		object_ptr<Ui::VerticalLayout>(_wrap),
		st::editPeerInvitesMargins);
	const auto container = result->entity();

	container->add(object_ptr<Ui::FlatLabel>(
		container,
		Lang::Viewer(lng_group_stickers),
		st::editPeerSectionLabel));
	container->add(object_ptr<Ui::FixedHeightWidget>(
		container,
		st::editPeerInviteLinkSkip));

	container->add(object_ptr<Ui::FlatLabel>(
		container,
		Lang::Viewer(lng_group_stickers_description),
		st::editPeerPrivacyLabel));
	container->add(object_ptr<Ui::FixedHeightWidget>(
		container,
		st::editPeerInviteLinkSkip));

	container->add(object_ptr<Ui::LinkButton>(
		_wrap,
		lang(lng_group_stickers_add),
		st::editPeerInviteLinkButton)
	)->addClickHandler([=] {
		Ui::show(Box<StickersBox>(channel), LayerOption::KeepOther);
	});

	return std::move(result);
}

bool Controller::canEditInformation() const {
	if (const auto channel = _peer->asChannel()) {
		return channel->canEditInformation();
	} else if (const auto chat = _peer->asChat()) {
		return chat->canEditInformation();
	}
	return false;
}

void Controller::refreshHistoryVisibility(bool instant = false) {
	if (!_controls.historyVisibilityWrap) {
		return;
	}
	_controls.historyVisibilityWrap->toggle(
		_privacySavedValue != Privacy::Public,
		instant ? anim::type::instant : anim::type::normal);
};

void Controller::showEditPeerTypeBox(std::optional<LangKey> error) {
	const auto boxCallback = crl::guard(this, [=](
			Privacy checked, QString publicLink) {
		_privacyTypeUpdates.fire(std::move(checked));
		_privacySavedValue = checked;
		_usernameSavedValue = publicLink;
		refreshHistoryVisibility();
	});
	Ui::show(
		Box<EditPeerTypeBox>(
			_peer,
			boxCallback,
			_privacySavedValue,
			_usernameSavedValue,
			error),
		LayerOption::KeepOther);
}

void Controller::showEditLinkedChatBox() {
	Expects(_peer->isChannel());

	const auto box = std::make_shared<QPointer<BoxContent>>();
	const auto channel = _peer->asChannel();
	const auto callback = [=](ChannelData *result) {
		if (*box) {
			(*box)->closeBox();
		}
		*_linkedChatSavedValue = result;
		_linkedChatUpdates.fire_copy(result);
	};
	if (const auto chat = *_linkedChatSavedValue) {
		*box = Ui::show(
			Box<EditLinkedChatBox>(channel, chat, callback),
			LayerOption::KeepOther);
		return;
	} else if (_linkedChatsRequestId) {
		return;
	} else if (channel->isMegagroup()) {
		// Restore original linked channel.
		callback(_linkedChatOriginalValue);
		return;
	}
	_linkedChatsRequestId = _linkedChatsRequester.request(
		MTPchannels_GetGroupsForDiscussion()
	).done([=](const MTPmessages_Chats &result) {
		_linkedChatsRequestId = 0;
		const auto list = result.match([&](const auto &data) {
			return data.vchats.v;
		});
		auto chats = std::vector<not_null<ChannelData*>>();
		chats.reserve(list.size());
		for (const auto &item : list) {
			const auto chat = _peer->owner().processChat(item);
			if (chat->isChannel()) {
				chats.emplace_back(chat->asChannel());
			}
		}
		*box = Ui::show(
			Box<EditLinkedChatBox>(channel, chats, callback),
			LayerOption::KeepOther);
	}).fail([=](const RPCError &error) {
		_linkedChatsRequestId = 0;
	}).send();
}

void Controller::fillPrivacyTypeButton() {
	Expects(_controls.buttonsLayout != nullptr);

	// Create Privacy Button.
	_privacySavedValue = (_peer->isChannel()
		&& _peer->asChannel()->isPublic())
		? Privacy::Public
		: Privacy::Private;

	const auto isGroup = (_peer->isChat() || _peer->isMegagroup());
	AddButtonWithText(
		_controls.buttonsLayout,
		Lang::Viewer(isGroup
			? lng_manage_peer_group_type
			: lng_manage_peer_channel_type),
		_privacyTypeUpdates.events(
		) | rpl::map([=](Privacy flag) {
			return lang(Privacy::Public == flag
				? (isGroup
					? lng_manage_public_group_title
					: lng_manage_public_peer_title)
				: (isGroup
					? lng_manage_private_group_title
					: lng_manage_private_peer_title));
		}),
		[=] { showEditPeerTypeBox(); });

	_privacyTypeUpdates.fire_copy(*_privacySavedValue);
}

void Controller::fillLinkedChatButton() {
	Expects(_controls.buttonsLayout != nullptr);

	_linkedChatSavedValue = _linkedChatOriginalValue = _peer->isChannel()
		? _peer->asChannel()->linkedChat()
		: nullptr;

	const auto isGroup = (_peer->isChat() || _peer->isMegagroup());
	auto text = !isGroup
		? Lang::Viewer(lng_manage_discussion_group)
		: rpl::combine(
			Lang::Viewer(lng_manage_linked_channel),
			Lang::Viewer(lng_manage_linked_channel_restore),
			_linkedChatUpdates.events()
		) | rpl::map([=](
				const QString &edit,
				const QString &restore,
				ChannelData *chat) {
			return chat ? edit : restore;
		});
	auto label = isGroup
		? _linkedChatUpdates.events() | rpl::map([](ChannelData *chat) {
			return chat ? chat->name : QString();
		}) : rpl::combine(
			Lang::Viewer(lng_manage_discussion_group_add),
			_linkedChatUpdates.events()
		) | rpl::map([=](const QString &add, ChannelData *chat) {
			return chat
				? chat->name
				: add;
		});
	AddButtonWithText(
		_controls.buttonsLayout,
		std::move(text),
		std::move(label),
		[=] { showEditLinkedChatBox(); });
	_linkedChatUpdates.fire_copy(*_linkedChatSavedValue);
}

void Controller::fillInviteLinkButton() {
	Expects(_controls.buttonsLayout != nullptr);

	const auto buttonCallback = [=] {
		Ui::show(
			Box<EditPeerTypeBox>(_peer),
			LayerOption::KeepOther);
	};

	AddButtonWithText(
		_controls.buttonsLayout,
		Lang::Viewer(lng_profile_invite_link_section),
		rpl::single(QString()), //Empty text.
		buttonCallback);
}

void Controller::fillSignaturesButton() {
	Expects(_controls.buttonsLayout != nullptr);
	const auto channel = _peer->asChannel();
	if (!channel) return;

	AddButtonWithText(
		_controls.buttonsLayout,
		Lang::Viewer(lng_edit_sign_messages),
		rpl::single(QString()),
		[=] {}
	)->toggleOn(rpl::single(channel->addsSignature())
	)->toggledValue(
	) | rpl::start_with_next([=](bool toggled) {
		_signaturesSavedValue = toggled;
	}, _controls.buttonsLayout->lifetime());
}

void Controller::fillHistoryVisibilityButton() {
	Expects(_controls.buttonsLayout != nullptr);

	const auto wrapLayout = _controls.buttonsLayout->add(
		object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
			_controls.buttonsLayout,
			object_ptr<Ui::VerticalLayout>(_controls.buttonsLayout),
			st::boxOptionListPadding)); // Empty margins.
	_controls.historyVisibilityWrap = wrapLayout;

	const auto channel = _peer->asChannel();
	const auto container = wrapLayout->entity();

	_historyVisibilitySavedValue = (!channel || channel->hiddenPreHistory())
		? HistoryVisibility::Hidden
		: HistoryVisibility::Visible;

	const auto updateHistoryVisibility =
		std::make_shared<rpl::event_stream<HistoryVisibility>>();

	const auto boxCallback = crl::guard(this, [=](HistoryVisibility checked) {
		updateHistoryVisibility->fire(std::move(checked));
		_historyVisibilitySavedValue = checked;
	});
	const auto buttonCallback = [=] {
		Ui::show(
			Box<EditPeerHistoryVisibilityBox>(
				_peer,
				boxCallback,
				*_historyVisibilitySavedValue),
			LayerOption::KeepOther);
	};
	AddButtonWithText(
		container,
		Lang::Viewer(lng_manage_history_visibility_title),
		updateHistoryVisibility->events(
		) | rpl::map([](HistoryVisibility flag) {
			return lang((HistoryVisibility::Visible == flag)
				? lng_manage_history_visibility_shown
				: lng_manage_history_visibility_hidden);
		}),
		buttonCallback);

	updateHistoryVisibility->fire_copy(*_historyVisibilitySavedValue);

	//While appearing box we should use instant animation.
	refreshHistoryVisibility(true);
}

void Controller::fillManageSection() {
	Expects(_controls.buttonsLayout != nullptr);

	const auto navigation = App::wnd()->controller();

	const auto chat = _peer->asChat();
	const auto channel = _peer->asChannel();
	const auto isChannel = (!chat);
	if (!chat && !channel) return;

	const auto canEditUsername = [&] {
		return isChannel
			? channel->canEditUsername()
			: chat->canEditUsername();
	}();
	const auto canEditInviteLink = [&] {
		return isChannel
			? (channel->amCreator()
				|| (channel->adminRights() & ChatAdminRight::f_invite_users))
			: (chat->amCreator()
				|| (chat->adminRights() & ChatAdminRight::f_invite_users));
	}();
	const auto canEditSignatures = [&] {
		return isChannel
			? (channel->canEditSignatures() && !channel->isMegagroup())
			: false;
	}();
	const auto canEditPreHistoryHidden = [&] {
		return isChannel
			? channel->canEditPreHistoryHidden()
			: chat->canEditPreHistoryHidden();
	}();

	const auto canEditPermissions = [&] {
		return isChannel
			? channel->canEditPermissions()
			: chat->canEditPermissions();
	}();
	const auto canViewAdmins = [&] {
		return isChannel
			? channel->canViewAdmins()
			: chat->amIn();
	}();
	const auto canViewMembers = [&] {
		return isChannel
			? channel->canViewMembers()
			: chat->amIn();
	}();
	const auto canViewKicked = [&] {
		return isChannel
			? (!channel->isMegagroup())
			: false;
	}();
	const auto hasRecentActions = [&] {
		return isChannel
			? (channel->hasAdminRights() || channel->amCreator())
			: false;
	}();

	const auto canEditStickers = [&] {
		// return true;
		return isChannel
			? channel->canEditStickers()
			: false;
	}();
	const auto canDeleteChannel = [&] {
		return isChannel
			? channel->canDelete()
			: false;
	}();

	const auto canEditLinkedChat = [&] {
		return !isChannel
			? false
			: channel->isBroadcast()
			? channel->canEditInformation()
			: (channel->linkedChat()
				&& channel->canPinMessages()
				&& channel->adminRights() != 0);
	}();

	AddSkip(_controls.buttonsLayout, 0);

	if (canEditUsername) {
		fillPrivacyTypeButton();
	} else if (canEditInviteLink) {
		fillInviteLinkButton();
	}
	if (canEditLinkedChat) {
		fillLinkedChatButton();
	}
	if (canEditSignatures) {
		fillSignaturesButton();
	}
	if (canEditPreHistoryHidden) {
		fillHistoryVisibilityButton();
	}
	if (canEditPreHistoryHidden
		|| canEditSignatures
		|| canEditInviteLink
		|| canEditLinkedChat
		|| canEditUsername) {
		AddSkip(
			_controls.buttonsLayout,
			st::editPeerTopButtonsLayoutSkip,
			st::editPeerTopButtonsLayoutSkipCustomBottom);
	}

	if (canEditPermissions) {
		AddButtonWithCount(
			_controls.buttonsLayout,
			Lang::Viewer(lng_manage_peer_permissions),
			Info::Profile::RestrictionsCountValue(_peer)
			| ToPositiveNumberStringRestrictions(),
			[=] { ShowEditPermissions(_peer); },
			st::infoIconPermissions);
	}
	if (canViewAdmins) {
		AddButtonWithCount(
			_controls.buttonsLayout,
			Lang::Viewer(lng_manage_peer_administrators),
			Info::Profile::AdminsCountValue(_peer)
			| ToPositiveNumberString(),
			[=] {
				ParticipantsBoxController::Start(
					navigation,
					_peer,
					ParticipantsBoxController::Role::Admins);
			},
			st::infoIconAdministrators);
	}
	if (canViewMembers) {
		AddButtonWithCount(
			_controls.buttonsLayout,
			Lang::Viewer(lng_manage_peer_members),
			Info::Profile::MembersCountValue(_peer)
			| ToPositiveNumberString(),
			[=] {
				ParticipantsBoxController::Start(
					navigation,
					_peer,
					ParticipantsBoxController::Role::Members);
			},
			st::infoIconMembers);
	}
	if (canViewKicked) {
		AddButtonWithCount(
			_controls.buttonsLayout,
			Lang::Viewer(lng_manage_peer_removed_users),
			Info::Profile::KickedCountValue(channel)
			| ToPositiveNumberString(),
			[=] {
				ParticipantsBoxController::Start(
					navigation,
					_peer,
					ParticipantsBoxController::Role::Kicked);
			},
			st::infoIconBlacklist);
	}
	if (hasRecentActions) {
		AddButtonWithCount(
			_controls.buttonsLayout,
			Lang::Viewer(lng_manage_peer_recent_actions),
			rpl::single(QString()), //Empty count.
			[=] {
				navigation->showSection(AdminLog::SectionMemento(channel));
			},
			st::infoIconRecentActions);
	}

	if (canEditStickers || canDeleteChannel) {
		AddSkip(_controls.buttonsLayout,
			st::editPeerTopButtonsLayoutSkipCustomTop);
	}

	if (canEditStickers) {
		_controls.buttonsLayout->add(createStickersEdit());
	}

	if (canDeleteChannel) {
		AddButtonDelete(
			_controls.buttonsLayout,
			Lang::Viewer(_isGroup
				? lng_profile_delete_group
				: lng_profile_delete_channel),
			[=]{ deleteWithConfirmation(); }
		);
	}
}

void Controller::submitTitle() {
	Expects(_controls.title != nullptr);

	if (_controls.title->getLastText().isEmpty()) {
		_controls.title->showError();
		_box->scrollToWidget(_controls.title);
	} else if (_controls.description) {
		_controls.description->setFocus();
		_box->scrollToWidget(_controls.description);
	}
}

void Controller::submitDescription() {
	Expects(_controls.title != nullptr);
	Expects(_controls.description != nullptr);

	if (_controls.title->getLastText().isEmpty()) {
		_controls.title->showError();
		_box->scrollToWidget(_controls.title);
	} else {
		save();
	}
}

std::optional<Controller::Saving> Controller::validate() const {
	auto result = Saving();
	if (validateUsername(result)
		&& validateLinkedChat(result)
		&& validateTitle(result)
		&& validateDescription(result)
		&& validateHistoryVisibility(result)
		&& validateSignatures(result)) {
		return result;
	}
	return {};
}

bool Controller::validateUsername(Saving &to) const {
	if (!_privacySavedValue) {
		return true;
	} else if (_privacySavedValue != Privacy::Public) {
		to.username = QString();
		return true;
	}
	const auto username = _usernameSavedValue.value_or(
		_peer->isChannel()
			? _peer->asChannel()->username
			: QString()
	);
	if (username.isEmpty()) {
		return false;
	}
	to.username = username;
	return true;
}

bool Controller::validateLinkedChat(Saving &to) const {
	if (!_linkedChatSavedValue) {
		return true;
	}
	to.linkedChat = *_linkedChatSavedValue;
	return true;
}

bool Controller::validateTitle(Saving &to) const {
	if (!_controls.title) {
		return true;
	}
	const auto title = _controls.title->getLastText().trimmed();
	if (title.isEmpty()) {
		_controls.title->showError();
		_box->scrollToWidget(_controls.title);
		return false;
	}
	to.title = title;
	return true;
}

bool Controller::validateDescription(Saving &to) const {
	if (!_controls.description) {
		return true;
	}
	to.description = _controls.description->getLastText().trimmed();
	return true;
}

bool Controller::validateHistoryVisibility(Saving &to) const {
	if (!_controls.historyVisibilityWrap
		|| !_controls.historyVisibilityWrap->toggled()
		|| (_privacySavedValue == Privacy::Public)) {
		return true;
	}
	to.hiddenPreHistory
		= (_historyVisibilitySavedValue == HistoryVisibility::Hidden);
	return true;
}

bool Controller::validateSignatures(Saving &to) const {
	if (!_signaturesSavedValue.has_value()) {
		return true;
	}
	to.signatures = _signaturesSavedValue;
	return true;
}

void Controller::save() {
	Expects(_wrap != nullptr);

	if (!_saveStagesQueue.empty()) {
		return;
	}
	if (const auto saving = validate()) {
		_savingData = *saving;
		pushSaveStage([=] { saveUsername(); });
		pushSaveStage([=] { saveLinkedChat(); });
		pushSaveStage([=] { saveTitle(); });
		pushSaveStage([=] { saveDescription(); });
		pushSaveStage([=] { saveHistoryVisibility(); });
		pushSaveStage([=] { saveSignatures(); });
		pushSaveStage([=] { savePhoto(); });
		continueSave();
	}
}

void Controller::pushSaveStage(FnMut<void()> &&lambda) {
	_saveStagesQueue.push_back(std::move(lambda));
}

void Controller::continueSave() {
	if (!_saveStagesQueue.empty()) {
		auto next = std::move(_saveStagesQueue.front());
		_saveStagesQueue.pop_front();
		next();
	}
}

void Controller::cancelSave() {
	_saveStagesQueue.clear();
}

void Controller::saveUsername() {
	const auto channel = _peer->asChannel();
	const auto username = (channel ? channel->username : QString());
	if (!_savingData.username || *_savingData.username == username) {
		return continueSave();
	} else if (!channel) {
		const auto saveForChannel = [=](not_null<ChannelData*> channel) {
			if (_peer->asChannel() == channel) {
				saveUsername();
			} else {
				cancelSave();
			}
		};
		_peer->session().api().migrateChat(
			_peer->asChat(),
			crl::guard(this, saveForChannel));
		return;
	}

	request(MTPchannels_UpdateUsername(
		channel->inputChannel,
		MTP_string(*_savingData.username)
	)).done([=](const MTPBool &result) {
		channel->setName(
			TextUtilities::SingleLine(channel->name),
			*_savingData.username);
		continueSave();
	}).fail([=](const RPCError &error) {
		const auto &type = error.type();
		if (type == qstr("USERNAME_NOT_MODIFIED")) {
			channel->setName(
				TextUtilities::SingleLine(channel->name),
				TextUtilities::SingleLine(*_savingData.username));
			continueSave();
			return;
		}
		const auto errorKey = [&] {
			if (type == qstr("USERNAME_INVALID")) {
				return lng_create_channel_link_invalid;
			} else if (type == qstr("USERNAME_OCCUPIED")
				|| type == qstr("USERNAMES_UNAVAILABLE")) {
				return lng_create_channel_link_occupied;
			}
			return lng_create_channel_link_invalid;
		}();
		// Very rare case.
		showEditPeerTypeBox(errorKey);
		cancelSave();
	}).send();
}

void Controller::saveLinkedChat() {
	const auto channel = _peer->asChannel();
	if (!channel) {
		return continueSave();
	}
	const auto linkedChat = channel->linkedChat();
	if (!_savingData.linkedChat || *_savingData.linkedChat == linkedChat) {
		return continueSave();
	}

	const auto input = *_savingData.linkedChat
		? (*_savingData.linkedChat)->inputChannel
		: MTP_inputChannelEmpty();
	request(MTPchannels_SetDiscussionGroup(
		(channel->isBroadcast() ? channel->inputChannel : input),
		(channel->isBroadcast() ? input : channel->inputChannel)
	)).done([=](const MTPBool &result) {
		channel->setLinkedChat(*_savingData.linkedChat);
		continueSave();
	}).fail([=](const RPCError &error) {
		const auto &type = error.type();
		cancelSave();
	}).send();
}

void Controller::saveTitle() {
	if (!_savingData.title || *_savingData.title == _peer->name) {
		return continueSave();
	}

	const auto onDone = [=](const MTPUpdates &result) {
		_peer->session().api().applyUpdates(result);
		continueSave();
	};
	const auto onFail = [=](const RPCError &error) {
		const auto &type = error.type();
		if (type == qstr("CHAT_NOT_MODIFIED")
			|| type == qstr("CHAT_TITLE_NOT_MODIFIED")) {
			if (const auto channel = _peer->asChannel()) {
				channel->setName(*_savingData.title, channel->username);
			} else if (const auto chat = _peer->asChat()) {
				chat->setName(*_savingData.title);
			}
			continueSave();
			return;
		}
		_controls.title->showError();
		if (type == qstr("NO_CHAT_TITLE")) {
			_box->scrollToWidget(_controls.title);
		}
		cancelSave();
	};

	if (const auto channel = _peer->asChannel()) {
		request(MTPchannels_EditTitle(
			channel->inputChannel,
			MTP_string(*_savingData.title)
		)).done(std::move(onDone)
		).fail(std::move(onFail)
		).send();
	} else if (const auto chat = _peer->asChat()) {
		request(MTPmessages_EditChatTitle(
			chat->inputChat,
			MTP_string(*_savingData.title)
		)).done(std::move(onDone)
		).fail(std::move(onFail)
		).send();
	} else {
		continueSave();
	}
}

void Controller::saveDescription() {
	const auto channel = _peer->asChannel();
	if (!_savingData.description
		|| *_savingData.description == _peer->about()) {
		return continueSave();
	}
	const auto successCallback = [=] {
		_peer->setAbout(*_savingData.description);
		continueSave();
	};
	request(MTPmessages_EditChatAbout(
		_peer->input,
		MTP_string(*_savingData.description)
	)).done([=](const MTPBool &result) {
		successCallback();
	}).fail([=](const RPCError &error) {
		const auto &type = error.type();
		if (type == qstr("CHAT_ABOUT_NOT_MODIFIED")) {
			successCallback();
			return;
		}
		_controls.description->showError();
		cancelSave();
	}).send();
}

void Controller::saveHistoryVisibility() {
	const auto channel = _peer->asChannel();
	const auto hidden = channel ? channel->hiddenPreHistory() : true;
	if (!_savingData.hiddenPreHistory
		|| *_savingData.hiddenPreHistory == hidden) {
		return continueSave();
	} else if (!channel) {
		const auto saveForChannel = [=](not_null<ChannelData*> channel) {
			if (_peer->asChannel() == channel) {
				saveHistoryVisibility();
			} else {
				cancelSave();
			}
		};
		_peer->session().api().migrateChat(
			_peer->asChat(),
			crl::guard(this, saveForChannel));
		return;
	}
	request(MTPchannels_TogglePreHistoryHidden(
		channel->inputChannel,
		MTP_bool(*_savingData.hiddenPreHistory)
	)).done([=](const MTPUpdates &result) {
		// Update in the result doesn't contain the
		// channelFull:flags field which holds this value.
		// So after saving we need to update it manually.
		channel->updateFullForced();

		channel->session().api().applyUpdates(result);
		continueSave();
	}).fail([=](const RPCError &error) {
		if (error.type() == qstr("CHAT_NOT_MODIFIED")) {
			continueSave();
		} else {
			cancelSave();
		}
	}).send();
}

void Controller::saveSignatures() {
	const auto channel = _peer->asChannel();
	if (!_savingData.signatures
		|| !channel
		|| *_savingData.signatures == channel->addsSignature()) {
		return continueSave();
	}
	request(MTPchannels_ToggleSignatures(
		channel->inputChannel,
		MTP_bool(*_savingData.signatures)
	)).done([=](const MTPUpdates &result) {
		channel->session().api().applyUpdates(result);
		continueSave();
	}).fail([=](const RPCError &error) {
		if (error.type() == qstr("CHAT_NOT_MODIFIED")) {
			continueSave();
		} else {
			cancelSave();
		}
	}).send();
}

void Controller::savePhoto() {
	auto image = _controls.photo
		? _controls.photo->takeResultImage()
		: QImage();
	if (!image.isNull()) {
		_peer->session().api().uploadPeerPhoto(_peer, std::move(image));
	}
	_box->closeBox();
}

void Controller::deleteWithConfirmation() {
	const auto channel = _peer->asChannel();
	Assert(channel != nullptr);

	const auto text = lang(_isGroup
		? lng_sure_delete_group
		: lng_sure_delete_channel);
	const auto deleteCallback = crl::guard(this, [=] {
		deleteChannel();
	});
	Ui::show(
		Box<ConfirmBox>(
			text,
			lang(lng_box_delete),
			st::attentionBoxButton,
			deleteCallback),
		LayerOption::KeepOther);
}

void Controller::deleteChannel() {
	Expects(_peer->isChannel());

	const auto channel = _peer->asChannel();
	const auto chat = channel->migrateFrom();

	Ui::hideLayer();
	Ui::showChatsList();
	if (chat) {
		chat->session().api().deleteConversation(chat, false);
	}
	MTP::send(
		MTPchannels_DeleteChannel(channel->inputChannel),
		App::main()->rpcDone(&MainWidget::sentUpdatesReceived),
		App::main()->rpcFail(&MainWidget::deleteChannelFailed));
}

} // namespace

EditPeerInfoBox::EditPeerInfoBox(
	QWidget*,
	not_null<PeerData*> peer)
: _peer(peer->migrateToOrMe()) {
}

void EditPeerInfoBox::prepare() {
	const auto controller = Ui::CreateChild<Controller>(this, this, _peer);
	_focusRequests.events(
	) | rpl::start_with_next(
		[=] { controller->setFocus(); },
		lifetime());
	auto content = controller->createContent();
	setDimensionsToContent(st::boxWideWidth, content);
	setInnerWidget(object_ptr<Ui::OverrideMargins>(
		this,
		std::move(content)));
}

object_ptr<Info::Profile::Button> EditPeerInfoBox::CreateButton(
		not_null<QWidget*> parent,
		rpl::producer<QString> &&text,
		rpl::producer<QString> &&count,
		Fn<void()> callback,
		const style::InfoProfileCountButton &st,
		const style::icon *icon) {
	auto result = object_ptr<Info::Profile::Button>(
		parent,
		rpl::duplicate(text),
		st.button);
	const auto button = result.data();
	button->addClickHandler(callback);
	if (icon) {
		Ui::CreateChild<Info::Profile::FloatingIcon>(
			button,
			*icon,
			st.iconPosition);
	}

	auto labelText = rpl::combine(
		std::move(text),
		std::move(count),
		button->widthValue()
	) | rpl::map([&st](const QString &text, const QString &count, int width) {
		const auto available = width
			- st.button.padding.left()
			- (st.button.font->spacew * 2)
			- st.button.font->width(text)
			- st.labelPosition.x();
		const auto required = st.label.style.font->width(count);
		return (required > available)
			? st.label.style.font->elided(count, std::max(available, 0))
			: count;
	});

	const auto label = Ui::CreateChild<Ui::FlatLabel>(
		button,
		std::move(labelText),
		st.label);
	label->setAttribute(Qt::WA_TransparentForMouseEvents);

	rpl::combine(
		button->widthValue(),
		label->widthValue()
	) | rpl::start_with_next([=, &st](int outerWidth, int width) {
		label->moveToRight(
			st.labelPosition.x(),
			st.labelPosition.y(),
			outerWidth);
	}, label->lifetime());

	return result;
}

bool EditPeerInfoBox::Available(not_null<PeerData*> peer) {
	if (const auto chat = peer->asChat()) {
		return false
			|| chat->canEditInformation()
			|| chat->canEditPermissions();
	} else if (const auto channel = peer->asChannel()) {
		// canViewMembers() is removed, because in supergroups you
		// see them in profile and in channels only admins can see them.

		// canViewAdmins() is removed, because in supergroups it is
		// always true and in channels it is equal to canViewBanned().

		return false
			//|| channel->canViewMembers()
			//|| channel->canViewAdmins()
			|| channel->canViewBanned()
			|| channel->canEditInformation()
			|| channel->canEditPermissions()
			|| channel->hasAdminRights()
			|| channel->amCreator();
	} else {
		return false;
	}
}
