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

#include "boxes/peers/edit_tag_control.h"
#include "boxes/peers/edit_participants_box.h"
#include "history/view/history_view_message.h"
#include "lang/lang_keys.h"
#include "ui/controls/userpic_button.h"
#include "ui/vertical_list.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/box_content_divider.h"
#include "ui/layers/generic_box.h"
#include "ui/toast/toast.h"
#include "ui/text/text_utilities.h"
#include "ui/text/text_options.h"
#include "ui/painter.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "settings/sections/settings_privacy_security.h"
#include "ui/boxes/choose_date_time.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/peers/channel_ownership_transfer.h"
#include "boxes/peers/add_bot_to_chat_box.h"
#include "boxes/peers/edit_peer_permissions_box.h"
#include "boxes/peers/edit_peer_info_box.h"
#include "boxes/peers/replace_boost_box.h"
#include "settings/settings_common.h"
#include "data/data_peer_values.h"
#include "data/data_channel.h"
#include "data/data_changes.h"
#include "data/data_chat.h"
#include "data/data_user.h"
#include "base/unixtime.h"
#include "apiwrap.h"
#include "main/main_session.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"
#include "styles/style_premium.h"
#include "styles/style_settings.h"

namespace {

constexpr auto kMaxRestrictDelayDays = 366;
constexpr auto kSecondsInDay = 24 * 60 * 60;
constexpr auto kSecondsInWeek = 7 * kSecondsInDay;

[[nodiscard]] bool CanProcessJoinRequests(
		not_null<PeerData*> peer,
		not_null<UserData*> user) {
	const auto info = user->botInfo.get();
	return peer->isChannel()
		&& user->isBot()
		&& info
		&& info->supportsGuard;
}

[[nodiscard]] object_ptr<Ui::GenericBox> GuardBotReplaceBox(
		not_null<UserData*> current,
		not_null<UserData*> replacement,
		Fn<void(Fn<void()>)> confirmed) {
	return Box([=](not_null<Ui::GenericBox*> box) {
		box->setStyle(st::guardBotReplaceBox);
		const auto content = box->verticalLayout();
		const auto weak = base::make_weak(box);
		content->add(
			CreateUserpicsTransfer(
				content,
				rpl::single(std::vector{ not_null<PeerData*>(current) }),
				replacement,
				UserpicsTransferType::GuardBotReplace),
			st::boxRowPadding + st::boostReplaceUserpicsPadding);
		content->add(
			object_ptr<Ui::FlatLabel>(
				content,
				tr::lng_guard_bot_replace_title(),
				st::boostCenteredTitle),
			st::boxRowPadding + st::starrefJoinTitlePadding,
			style::al_top);
		content->add(
			object_ptr<Ui::FlatLabel>(
				content,
				rpl::single(tr::lng_guard_bot_replace_text(
					tr::now,
					lt_from,
					tr::bold(current->name()),
					lt_to,
					tr::bold(replacement->name()),
					tr::rich)),
				st::boxLabel),
			QMargins(
				st::boxPadding.left(),
				0,
				st::boxPadding.right(),
				st::boxPadding.bottom()));
		Ui::AddSkip(content);
		const auto use = content->add(
			object_ptr<Ui::RoundButton>(
				content,
				rpl::single(tr::lng_guard_bot_replace_confirm(
					tr::now,
					lt_bot,
					replacement->shortName())),
				st::defaultLightButton),
			st::boxRowPadding,
			style::al_justify);
		Ui::AddSkip(content);
		const auto keep = content->add(
			object_ptr<Ui::RoundButton>(
				content,
				rpl::single(tr::lng_guard_bot_replace_cancel(
					tr::now,
					lt_bot,
					current->shortName())),
				st::defaultLightButton),
			st::boxRowPadding,
			style::al_justify);
		use->setClickedCallback([=] {
			confirmed(crl::guard(weak, [=] { weak->closeBox(); }));
		});
		keep->setClickedCallback([=] {
			box->closeBox();
		});
	});
}

[[nodiscard]] object_ptr<Ui::GenericBox> GuardBotApproveBox(
		bool isGroup,
		not_null<UserData*> bot,
		Fn<void(Fn<void()>)> confirmed) {
	const auto text = isGroup
		? tr::lng_guard_bot_approve_text(
			tr::now,
			lt_bot,
			tr::bold(bot->name()),
			tr::rich)
		: tr::lng_guard_bot_approve_text_channel(
			tr::now,
			lt_bot,
			tr::bold(bot->name()),
			tr::rich);
	return Ui::MakeConfirmBox({
		.text = text,
		.confirmed = confirmed,
		.confirmText = tr::lng_guard_bot_approve_confirm(),
		.title = tr::lng_guard_bot_approve_title(),
	});
}

class Cover final : public Ui::FixedHeightWidget {
public:
	Cover(QWidget *parent, not_null<UserData*> user, bool hasAdminRights);

private:
	void setupChildGeometry();

	const style::InfoProfileCover &_st;
	const not_null<UserData*> _user;
	object_ptr<Ui::UserpicButton> _userpic;
	object_ptr<Ui::FlatLabel> _name = { nullptr };
	object_ptr<Ui::FlatLabel> _status = { nullptr };

};

Cover::Cover(
	QWidget *parent,
	not_null<UserData*> user,
	bool hasAdminRights)
: FixedHeightWidget(parent, st::infoEditContactCover.height)
, _st(st::infoEditContactCover)
, _user(user)
, _userpic(this, _user, _st.photo) {
	_userpic->setAttribute(Qt::WA_TransparentForMouseEvents);

	_name = object_ptr<Ui::FlatLabel>(this, _st.name);
	_name->setText(_user->name());

	const auto statusText = [&] {
		if (_user->isBot()) {
			const auto seesAllMessages = _user->botInfo->readsAllHistory
				|| hasAdminRights;
			return (seesAllMessages
				? tr::lng_status_bot_reads_all
				: tr::lng_status_bot_not_reads_all)(tr::now);
		}
		return Data::OnlineText(_user->lastseen(), base::unixtime::now());
	}();
	_status = object_ptr<Ui::FlatLabel>(this, _st.status);
	_status->setText(statusText);
	_status->setAttribute(Qt::WA_TransparentForMouseEvents);

	setupChildGeometry();
}

void Cover::setupChildGeometry() {
	widthValue(
	) | rpl::on_next([this](int newWidth) {
		_userpic->moveToLeft(_st.photoLeft, _st.photoTop, newWidth);
		auto nameWidth = newWidth - _st.nameLeft - _st.rightSkip;
		_name->resizeToNaturalWidth(nameWidth);
		_name->moveToLeft(_st.nameLeft, _st.nameTop, newWidth);
		auto statusWidth = newWidth - _st.statusLeft - _st.rightSkip;
		_status->resizeToNaturalWidth(statusWidth);
		_status->moveToLeft(_st.statusLeft, _st.statusTop, newWidth);
	}, lifetime());
}

} // namespace

class EditParticipantBox::Inner : public Ui::RpWidget {
public:
	Inner(
		QWidget *parent,
		not_null<PeerData*> peer,
		not_null<UserData*> user,
		bool hasAdminRights);

	template <typename Widget>
	Widget *addControl(object_ptr<Widget> widget, QMargins margin);

	[[nodiscard]] not_null<Ui::VerticalLayout*> verticalLayout() const {
		return _rows;
	}

	void setCoverMode(bool enabled);

protected:
	int resizeGetHeight(int newWidth) override;
	void paintEvent(QPaintEvent *e) override;

private:
	not_null<PeerData*> _peer;
	not_null<UserData*> _user;
	object_ptr<Ui::UserpicButton> _userPhoto;
	Ui::Text::String _userName;
	bool _hasAdminRights = false;
	bool _coverMode = false;
	object_ptr<Ui::VerticalLayout> _rows;

};

EditParticipantBox::Inner::Inner(
	QWidget *parent,
	not_null<PeerData*> peer,
	not_null<UserData*> user,
	bool hasAdminRights)
: RpWidget(parent)
, _peer(peer)
, _user(user)
, _userPhoto(this, _user, st::rightsPhotoButton)
, _hasAdminRights(hasAdminRights)
, _rows(this) {
	_rows->heightValue(
	) | rpl::on_next([=] {
		resizeToWidth(width());
	}, lifetime());

	_userPhoto->setAttribute(Qt::WA_TransparentForMouseEvents);
	_userName.setText(
		st::rightsNameStyle,
		_user->name(),
		Ui::NameTextOptions());
}

template <typename Widget>
Widget *EditParticipantBox::Inner::addControl(
		object_ptr<Widget> widget,
		QMargins margin) {
	return _rows->add(std::move(widget), margin);
}

void EditParticipantBox::Inner::setCoverMode(bool enabled) {
	_coverMode = enabled;
	if (enabled) {
		_userPhoto->hide();
	}
	resizeToWidth(width());
}

int EditParticipantBox::Inner::resizeGetHeight(int newWidth) {
	if (_coverMode) {
		_rows->resizeToWidth(newWidth);
		_rows->moveToLeft(0, 0, newWidth);
		return _rows->heightNoMargins();
	}
	_userPhoto->moveToLeft(
		st::rightsPhotoMargin.left(),
		st::rightsPhotoMargin.top());
	const auto rowsTop = st::rightsPhotoMargin.top()
		+ st::rightsPhotoButton.size.height()
		+ st::rightsPhotoMargin.bottom();
	_rows->resizeToWidth(newWidth);
	_rows->moveToLeft(0, rowsTop, newWidth);
	return rowsTop + _rows->heightNoMargins();
}

void EditParticipantBox::Inner::paintEvent(QPaintEvent *e) {
	Painter p(this);

	p.fillRect(e->rect(), st::boxBg);

	if (_coverMode) {
		return;
	}

	p.setPen(st::contactsNameFg);
	auto namex = st::rightsPhotoMargin.left()
		+ st::rightsPhotoButton.size .width()
		+ st::rightsPhotoMargin.right();
	auto namew = width() - namex - st::rightsPhotoMargin.right();
	_userName.drawLeftElided(
		p,
		namex,
		st::rightsPhotoMargin.top() + st::rightsNameTop,
		namew,
		width());
	const auto statusText = [&] {
		if (_user->isBot()) {
			const auto seesAllMessages = _user->botInfo->readsAllHistory
				|| _hasAdminRights;
			return (seesAllMessages
				? tr::lng_status_bot_reads_all
				: tr::lng_status_bot_not_reads_all)(tr::now);
		}
		return Data::OnlineText(_user->lastseen(), base::unixtime::now());
	}();
	p.setFont(st::contactsStatusFont);
	p.setPen(st::contactsStatusFg);
	p.drawTextLeft(
		namex,
		st::rightsPhotoMargin.top() + st::rightsStatusTop,
		width(),
		statusText);
}

EditParticipantBox::EditParticipantBox(
	QWidget*,
	not_null<PeerData*> peer,
	not_null<UserData*> user,
	bool hasAdminRights)
: _peer(peer)
, _user(user)
, _hasAdminRights(hasAdminRights) {
}

not_null<Ui::VerticalLayout*> EditParticipantBox::verticalLayout() const {
	return _inner->verticalLayout();
}

void EditParticipantBox::prepare() {
	_inner = setInnerWidget(object_ptr<Inner>(
		this,
		_peer,
		_user,
		hasAdminRights()));
	setDimensionsToContent(st::boxWideWidth, _inner);
}

template <typename Widget>
Widget *EditParticipantBox::addControl(
		object_ptr<Widget> widget,
		QMargins margin) {
	Expects(_inner != nullptr);

	return _inner->addControl(std::move(widget), margin);
}

bool EditParticipantBox::amCreator() const {
	if (const auto chat = _peer->asChat()) {
		return chat->amCreator();
	} else if (const auto channel = _peer->asChannel()) {
		return channel->amCreator();
	}
	Unexpected("Peer type in EditParticipantBox::Inner::amCreator.");
}

void EditParticipantBox::setCoverMode(bool enabled) {
	Expects(_inner != nullptr);

	_inner->setCoverMode(enabled);
}

EditAdminBox::EditAdminBox(
	QWidget*,
	not_null<PeerData*> peer,
	not_null<UserData*> user,
	ChatAdminRightsInfo rights,
	const QString &rank,
	TimeId promotedSince,
	UserData *by,
	std::optional<EditAdminBotFields> addingBot)
: EditParticipantBox(
	nullptr,
	peer,
	user,
	(rights.flags != 0))
, _oldRights(rights)
, _oldRank(rank)
, _promotedSince(promotedSince)
, _by(by)
, _addingBot(std::move(addingBot)) {
}

ChatAdminRightsInfo EditAdminBox::defaultRights() const {
	using Flag = ChatAdminRight;

	return peer()->isChat()
		? peer()->asChat()->defaultAdminRights(user())
		: peer()->isMegagroup()
		? ChatAdminRightsInfo{ (Flag::ChangeInfo
			| Flag::DeleteMessages
			| Flag::PostStories
			| Flag::EditStories
			| Flag::DeleteStories
			| Flag::BanUsers
			| Flag::InviteByLinkOrAdd
			| Flag::ManageTopics
			| Flag::PinMessages
			| Flag::ManageCall
			| Flag::ManageRanks
			| (CanProcessJoinRequests(peer(), user())
				? Flag::ProcessJoinRequests
				: Flag())) }
		: ChatAdminRightsInfo{ (Flag::ChangeInfo
			| Flag::PostMessages
			| Flag::EditMessages
			| Flag::DeleteMessages
			| Flag::PostStories
			| Flag::EditStories
			| Flag::DeleteStories
			| Flag::InviteByLinkOrAdd
			| Flag::ManageCall
			| Flag::ManageDirect
			| Flag::BanUsers) };
}

void EditAdminBox::prepare() {
	using namespace rpl::mappers;
	using Flag = ChatAdminRight;
	using Flags = ChatAdminRights;

	EditParticipantBox::prepare();

	setCoverMode(true);
	addControl(
		object_ptr<Cover>(this, user(), hasAdminRights()),
		style::margins());

	setTitle(_addingBot
		? (_addingBot->existing
			? tr::lng_rights_edit_admin()
			: tr::lng_bot_add_title())
		: _oldRights.flags
		? tr::lng_rights_edit_admin()
		: tr::lng_channel_add_admin());

	if (_addingBot
		&& !_addingBot->existing
		&& !peer()->isBroadcast()
		&& _saveCallback) {
		addControl(
			object_ptr<Ui::BoxContentDivider>(this),
			st::rightsDividerMargin / 2);
		_addAsAdmin = addControl(
			object_ptr<Ui::Checkbox>(
				this,
				tr::lng_bot_as_admin_check(tr::now),
				st::rightsCheckbox,
				std::make_unique<Ui::ToggleView>(
					st::rightsToggle,
					true)),
			st::rightsToggleMargin + (st::rightsDividerMargin / 2));
		_addAsAdmin->checkedChanges(
		) | rpl::on_next([=](bool checked) {
			_adminControlsWrap->toggle(checked, anim::type::normal);
			refreshButtons();
		}, _addAsAdmin->lifetime());
	}

	_adminControlsWrap = addControl(
		object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
			this,
			object_ptr<Ui::VerticalLayout>(this)));
	const auto inner = _adminControlsWrap->entity();

	if (_promotedSince) {
		const auto parsed = base::unixtime::parse(_promotedSince);
		const auto label = Ui::AddDividerText(
			inner,
			tr::lng_rights_about_by(
				lt_user,
				rpl::single(_by
					? tr::link(_by->name(), 1)
					: TextWithEntities{ QString::fromUtf8("\U0001F47B") }),
				lt_date,
				rpl::single(TextWithEntities{ langDateTimeFull(parsed) }),
				tr::marked));
		if (_by) {
			label->setLink(1, _by->createOpenLink());
		}
		Ui::AddSkip(inner);
	} else {
		Ui::AddDivider(inner);
		Ui::AddSkip(inner);
	}

	const auto chat = peer()->asChat();
	const auto channel = peer()->asChannel();
	const auto supportsProcessJoinRequests = canSave()
		&& CanProcessJoinRequests(peer(), user());
	const auto canProcessJoinRequests = supportsProcessJoinRequests
		&& peer()->isMegagroup();
	const auto guardBotRight = (canProcessJoinRequests
		&& channel
		&& (channel->guardBotId() == peerToUser(user()->id)))
		? Flag::ProcessJoinRequests
		: Flag();
	const auto prepareRights = _addingBot
		? ChatAdminRightsInfo(_oldRights.flags | _addingBot->existing)
		: _oldRights.flags
		? _oldRights
		: defaultRights();
	const auto disabledByDefaults = (channel && !channel->isMegagroup())
		? ChatAdminRights()
		: DisabledByDefaultRestrictions(peer());
	const auto filterByMyRights = canSave()
		&& !_oldRights.flags
		&& channel
		&& !channel->amCreator();
	const auto myEditableRights = channel
		? (channel->adminRights()
			| (canProcessJoinRequests
				? Flag::ProcessJoinRequests
				: Flag()))
		: Flags();
	const auto prepareFlags = disabledByDefaults
		| guardBotRight
		| (prepareRights.flags
			& (filterByMyRights ? myEditableRights : ~Flag(0)));

	const auto disabledMessages = [&] {
		auto result = base::flat_map<Flags, QString>();
		if (!canSave()) {
			result.emplace(
				~Flags(0),
				tr::lng_rights_about_admin_cant_edit(tr::now));
		} else {
			result.emplace(
				disabledByDefaults,
				tr::lng_rights_permission_for_all(tr::now));
			if (amCreator() && user()->isSelf()) {
				result.emplace(
					~Flag::Anonymous,
					tr::lng_rights_permission_cant_edit(tr::now));
			} else if (const auto channel = peer()->asChannel()) {
				if (!channel->amCreator()) {
					result.emplace(
						~myEditableRights,
						tr::lng_rights_permission_cant_edit(tr::now));
				}
			}
		}
		return result;
	}();

	const auto isGroup = chat || channel->isMegagroup();
	const auto anyoneCanAddMembers = chat
		? chat->anyoneCanAddMembers()
		: channel->anyoneCanAddMembers();
	const auto options = Data::AdminRightsSetOptions{
		.isGroup = isGroup,
		.isForum = peer()->isForum(),
		.anyoneCanAddMembers = anyoneCanAddMembers,
		.canProcessJoinRequests = canProcessJoinRequests,
	};
	Ui::AddSubsectionTitle(inner, tr::lng_rights_edit_admin_header());
	auto [checkboxes, getChecked, changes, highlightWidget] = CreateEditAdminRights(
		inner,
		prepareFlags,
		disabledMessages,
		options);
	inner->add(std::move(checkboxes), QMargins());

	auto selectedFlags = rpl::single(
		getChecked()
	) | rpl::then(std::move(
		changes
	));

	const auto hasRank = canSave() && (chat || channel->isMegagroup());

	{
		const auto aboutAddAdminsInner = inner->add(
			object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
				inner,
				object_ptr<Ui::VerticalLayout>(inner)));
		const auto emptyAboutAddAdminsInner = inner->add(
			object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
				inner,
				object_ptr<Ui::VerticalLayout>(inner)));
		aboutAddAdminsInner->toggle(false, anim::type::instant);
		emptyAboutAddAdminsInner->toggle(false, anim::type::instant);
		Ui::AddSkip(emptyAboutAddAdminsInner->entity());
		if (hasRank) {
			Ui::AddDivider(emptyAboutAddAdminsInner->entity());
		}
		Ui::AddSkip(aboutAddAdminsInner->entity());
		if (canProcessJoinRequests) {
			aboutAddAdminsInner->toggle(true, anim::type::instant);
			Ui::AddDividerText(
				aboutAddAdminsInner->entity(),
				tr::lng_rights_about_process_join_requests());
		} else {
			Ui::AddDividerText(
				aboutAddAdminsInner->entity(),
				rpl::duplicate(
					selectedFlags
				) | rpl::map(
					(_1 & Flag::AddAdmins) != 0
				) | rpl::distinct_until_changed(
				) | rpl::map([=](bool canAddAdmins) -> rpl::producer<QString> {
					const auto empty = (amCreator() && user()->isSelf());
					aboutAddAdminsInner->toggle(!empty, anim::type::instant);
					emptyAboutAddAdminsInner->toggle(empty, anim::type::instant);
					if (empty) {
						return rpl::single(QString());
					} else if (!canSave()) {
						return tr::lng_rights_about_admin_cant_edit();
					} else if (canAddAdmins) {
						return tr::lng_rights_about_add_admins_yes();
					}
					return tr::lng_rights_about_add_admins_no();
				}) | rpl::flatten_latest());
		}
	}

	if (canTransferOwnership()) {
		const auto allFlags = AdminRightsForOwnershipTransfer(options);
		setupTransferButton(
			inner,
			isGroup
		)->toggleOn(rpl::duplicate(
			selectedFlags
		) | rpl::map(
			((_1 & allFlags) == allFlags)
		))->setDuration(0);
	}

	if (canSave()) {
		if (hasRank) {
			const auto role = _oldRights.flags
				? LookupBadgeRole(peer(), user())
				: HistoryView::BadgeRole::Admin;
			_tagControl = inner->add(
				object_ptr<EditTagControl>(
					inner,
					&peer()->session(),
					user(),
					_oldRank,
					role),
				style::margins());
		}
		if (_tagControl) {
			Ui::AddSkip(inner);
			Ui::AddDividerText(
				inner,
				user()->isSelf()
					? tr::lng_rights_tag_about_self()
					: tr::lng_rights_tag_about(
						lt_name,
						rpl::single(user()->shortName())));
		}
		if (_oldRights.flags && !_addingBot) {
			const auto isTargetCreator = (LookupBadgeRole(peer(), user())
				== HistoryView::BadgeRole::Creator);
			if (!isTargetCreator) {
				if (!_tagControl) {
					Ui::AddSkip(inner);
					inner->add(
						object_ptr<Ui::BoxContentDivider>(inner),
						{ 0, st::infoProfileSkip, 0, st::infoProfileSkip });
				}
				Ui::AddSkip(inner);
				const auto dismissButton = Settings::AddButtonWithIcon(
					inner,
					tr::lng_rights_dismiss_admin(),
					st::settingsAttentionButton,
					{ nullptr });
				dismissButton->setClickedCallback([=] {
					if (const auto callback = _saveCallback) {
						const auto old = _oldRights;
						const auto confirmed = [=](Fn<void()> close) {
							callback(old, {}, {});
							close();
						};
						uiShow()->showBox(
							Ui::MakeConfirmBox({
								.text = tr::lng_profile_sure_remove_admin(
									tr::now,
									lt_user,
									user()->firstName),
								.confirmed = confirmed,
								.confirmText = tr::lng_box_remove(),
							}));
					}
				});
			}
		}
		_finishSave = [=, value = getChecked] {
			const auto newFlags = (value() | ChatAdminRight::Other)
				& ((!channel || channel->amCreator())
					? ~Flags(0)
					: myEditableRights);
			confirmGuardBotSave(ChatAdminRightsInfo(newFlags), [=] {
				_saveCallback(
					_oldRights,
					ChatAdminRightsInfo(newFlags),
					_tagControl
						? std::optional<QString>(_tagControl->currentRank())
						: std::nullopt);
			});
		};
		_save = [=] {
			const auto show = uiShow();
			if (!_saveCallback) {
				return;
			} else if (_addAsAdmin && !_addAsAdmin->checked()) {
				const auto weak = base::make_weak(this);
				AddBotToGroup(show, user(), peer(), _addingBot->token);
				if (const auto strong = weak.get()) {
					strong->closeBox();
				}
				return;
			} else if (_addingBot && !_addingBot->existing) {
				const auto phrase = peer()->isBroadcast()
					? tr::lng_bot_sure_add_text_channel
					: tr::lng_bot_sure_add_text_group;
				_confirmBox = getDelegate()->show(Ui::MakeConfirmBox({
					phrase(
						tr::now,
						lt_group,
						tr::bold(peer()->name()),
						tr::marked),
					crl::guard(this, [=] { finishAddAdmin(); })
				}));
			} else {
				_finishSave();
			}
		};
	}

	refreshButtons();
}

void EditAdminBox::finishAddAdmin() {
	const auto confirmBox = _confirmBox;
	_confirmBox = nullptr;
	if (confirmBox) {
		confirmBox->closeBox();
	}
	_finishSave();
}

void EditAdminBox::confirmGuardBotSave(
		ChatAdminRightsInfo rights,
		Fn<void()> done) {
	using Flag = ChatAdminRight;

	const auto channel = peer()->asChannel();
	if (!channel
		|| !(rights.flags & Flag::ProcessJoinRequests)
		|| !CanProcessJoinRequests(peer(), user())) {
		done();
		return;
	}

	const auto confirmed = crl::guard(this, [=](Fn<void()> close) {
		close();
		done();
	});
	const auto current = channel->guardBot();
	if (current && current != user().get()) {
		_confirmBox = getDelegate()->show(GuardBotReplaceBox(
			current,
			user(),
			confirmed));
	} else if (!channel->requestToJoin()) {
		_confirmBox = getDelegate()->show(GuardBotApproveBox(
			channel->isMegagroup(),
			user(),
			confirmed));
	} else {
		done();
	}
}

void EditAdminBox::refreshButtons() {
	clearButtons();
	if (canSave()) {
		addButton((!_addingBot || _addingBot->existing)
			? tr::lng_settings_save()
			: _adminControlsWrap->toggled()
			? tr::lng_bot_add_as_admin()
			: tr::lng_bot_add_as_member(), _save);
		addButton(tr::lng_cancel(), [=] { closeBox(); });
	} else {
		addButton(tr::lng_box_ok(), [=] { closeBox(); });
	}
}

bool EditAdminBox::canTransferOwnership() const {
	if (user()->isInaccessible() || user()->isBot() || user()->isSelf()) {
		return false;
	} else if (const auto chat = peer()->asChat()) {
		return chat->amCreator();
	} else if (const auto channel = peer()->asChannel()) {
		return channel->amCreator();
	}
	Unexpected("Chat type in EditAdminBox::canTransferOwnership.");
}

not_null<Ui::SlideWrap<Ui::RpWidget>*> EditAdminBox::setupTransferButton(
		not_null<Ui::VerticalLayout*> container,
		bool isGroup) {
	const auto wrap = container->add(
		object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
			container,
			object_ptr<Ui::VerticalLayout>(container)));

	const auto inner = wrap->entity();

	inner->add(
		object_ptr<Ui::BoxContentDivider>(inner),
		{ 0, st::infoProfileSkip, 0, st::infoProfileSkip });
	inner->add(EditPeerInfoBox::CreateButton(
		inner,
		(isGroup
			? tr::lng_rights_transfer_group
			: tr::lng_rights_transfer_channel)(),
		rpl::single(QString()),
		[=] { transferOwnership(); },
		st::peerPermissionsButton,
		{}));

	return wrap;
}

void EditAdminBox::transferOwnership() {
	_ownershipTransfer = std::make_unique<ChannelOwnershipTransfer>(
		peer(),
		user(),
		uiShow(),
		[](std::shared_ptr<Ui::Show> show) { show->hideLayer(); });
	_ownershipTransfer->start();
}

EditRestrictedBox::EditRestrictedBox(
	QWidget*,
	not_null<PeerData*> peer,
	not_null<UserData*> user,
	bool hasAdminRights,
	ChatRestrictionsInfo rights,
	const QString &rank,
	UserData *by,
	TimeId since)
: EditParticipantBox(nullptr, peer, user, hasAdminRights)
, _oldRights(rights)
, _oldRank(rank)
, _by(by)
, _since(since) {
}

void EditRestrictedBox::prepare() {
	using Flag = ChatRestriction;
	using Flags = ChatRestrictions;

	EditParticipantBox::prepare();

	setCoverMode(true);
	addControl(
		object_ptr<Cover>(this, user(), hasAdminRights()),
		style::margins());

	setTitle(tr::lng_rights_user_restrictions());

	Ui::AddDivider(verticalLayout());
	Ui::AddSkip(verticalLayout());

	const auto chat = peer()->asChat();
	const auto channel = peer()->asChannel();
	const auto defaultRestrictions = chat
		? chat->defaultRestrictions()
		: channel->defaultRestrictions();
	const auto prepareRights = _oldRights.flags
		? _oldRights
		: defaultRights();
	const auto prepareFlags = FixDependentRestrictions(
		prepareRights.flags
		| defaultRestrictions
		| ((channel && channel->isPublic())
			? (Flag::ChangeInfo | Flag::PinMessages)
			: Flags(0)));
	const auto disabledMessages = [&] {
		auto result = base::flat_map<Flags, QString>();
		if (!canSave()) {
			result.emplace(
				~Flags(0),
				tr::lng_rights_about_restriction_cant_edit(tr::now));
		} else {
			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;
	}();

	Ui::AddSubsectionTitle(
		verticalLayout(),
		tr::lng_rights_user_restrictions_header());
	auto [checkboxes, getRestrictions, changes, highlightWidget] = CreateEditRestrictions(
		this,
		prepareFlags,
		disabledMessages,
		{ .isForum = peer()->isForum(), .isUserSpecific = true });
	addControl(std::move(checkboxes), QMargins());

	if (canSave() && peer()->canManageRanks()) {
		Ui::AddSkip(verticalLayout());
		Ui::AddDivider(verticalLayout());
		_tagControl = addControl(
			object_ptr<EditTagControl>(
				this,
				&peer()->session(),
				user(),
				_oldRank,
				HistoryView::BadgeRole::User),
			style::margins());
		Ui::AddSkip(verticalLayout());
		Ui::AddDividerText(
			verticalLayout(),
			user()->isSelf()
				? tr::lng_rights_tag_about_self()
				: tr::lng_rights_tag_about(
					lt_name,
					rpl::single(user()->shortName())));
	}

	_until = prepareRights.until;
	if (!_tagControl) {
		addControl(
			object_ptr<Ui::FixedHeightWidget>(this, st::defaultVerticalListSkip));
		Ui::AddDivider(verticalLayout());
	}
	addControl(
		object_ptr<Ui::FlatLabel>(
			this,
			tr::lng_rights_chat_banned_until_header(tr::now),
			st::rightsHeaderLabel),
		st::rightsHeaderMargin);
	setRestrictUntil(_until);

	//addControl(
	//	object_ptr<Ui::LinkButton>(
	//		this,
	//		tr::lng_rights_chat_banned_block(tr::now),
	//		st::boxLinkButton));

	if (_since) {
		const auto parsed = base::unixtime::parse(_since);
		const auto inner = addControl(object_ptr<Ui::VerticalLayout>(this));
		const auto isBanned = (_oldRights.flags
			& ChatRestriction::ViewMessages);
		Ui::AddSkip(inner);
		const auto label = Ui::AddDividerText(
			inner,
			(isBanned
				? tr::lng_rights_chat_banned_by
				: tr::lng_rights_chat_restricted_by)(
					lt_user,
					rpl::single(_by
						? tr::link(_by->name(), 1)
						: TextWithEntities{ QString::fromUtf8("\U0001F47B") }),
					lt_date,
					rpl::single(TextWithEntities{ langDateTimeFull(parsed) }),
					tr::marked));
		if (_by) {
			label->setLink(1, _by->createOpenLink());
		}
	}

	if (canSave()) {
		Ui::AddSkip(verticalLayout());
		Ui::AddDivider(verticalLayout());
		Ui::AddSkip(verticalLayout());

		const auto canAddAdmins = (chat && chat->canAddAdmins())
			|| (channel && channel->canAddAdmins());
		if (canAddAdmins) {
			const auto promoteButton = Settings::AddButtonWithIcon(
				verticalLayout(),
				tr::lng_rights_promote_member(),
				st::settingsButtonNoIcon,
				{ nullptr });
			promoteButton->setClickedCallback([=] {
				const auto rank = _tagControl
					? _tagControl->currentRank()
					: _oldRank;
				auto adminBox = Box<EditAdminBox>(
					peer(),
					user(),
					ChatAdminRightsInfo(),
					rank,
					TimeId(0),
					nullptr);
				const auto adminBoxWeak = QPointer<Ui::BoxContent>(
					adminBox.data());
				const auto show = uiShow();
				const auto restrictWeak = QPointer<EditRestrictedBox>(
					this);
				const auto closeBoth = [=] {
					if (adminBoxWeak) {
						adminBoxWeak->closeBox();
					}
					if (restrictWeak) {
						restrictWeak->closeBox();
					}
				};
				const auto savedUser = user();
				const auto savedPeer = peer();
				const auto done = [=](
						ChatAdminRightsInfo newRights,
						const std::optional<QString> &rank) {
					closeBoth();
					const auto effectiveRank = rank.value_or([&] {
						const auto ch = savedPeer->asChannel();
						if (!ch) {
							return QString();
						}
						const auto info = ch->mgInfo.get();
						if (!info) {
							return QString();
						}
						const auto i = info->memberRanks.find(
							peerToUser(savedUser->id));
						return (i != end(info->memberRanks))
							? i->second
							: QString();
					}());
					savedUser->session().changes().chatAdminChanged(
						savedPeer,
						savedUser,
						newRights.flags,
						effectiveRank);
				};
				const auto fail = closeBoth;
				adminBox->setSaveCallback(
					SaveAdminCallback(
						show,
						peer(),
						user(),
						done,
						fail));
				show->showBox(std::move(adminBox));
			});
		}

		const auto removeButton = Settings::AddButtonWithIcon(
			verticalLayout(),
			tr::lng_rights_remove_member(),
			st::settingsAttentionButton,
			{ nullptr });
		removeButton->setClickedCallback([=] {
			if (const auto callback = _saveCallback) {
				const auto old = _oldRights;
				const auto confirmed = [=](Fn<void()> close) {
					callback(
						old,
						ChannelData::KickedRestrictedRights(user()));
					close();
				};
				uiShow()->show(
					Ui::MakeConfirmBox({
						.text = tr::lng_profile_sure_kick(
							tr::now,
							lt_user,
							user()->firstName),
						.confirmed = confirmed,
						.confirmText = tr::lng_box_remove(),
					}));
			}
		});

		const auto save = [=, value = getRestrictions] {
			if (!_saveCallback) {
				return;
			}
			const auto show = uiShow();
			const auto rankPeer = peer();
			const auto rankUser = user();
			const auto rank = _tagControl
				? _tagControl->currentRank()
				: _oldRank;
			const auto saveRank = (rank != _oldRank);

			// May destroy the box.
			_saveCallback(
				_oldRights,
				ChatRestrictionsInfo{ value(), getRealUntilValue() });

			if (saveRank) {
				SaveMemberRank(
					show,
					rankPeer,
					rankUser,
					rank,
					nullptr,
					nullptr);
			}
		};
		addButton(tr::lng_settings_save(), save);
		addButton(tr::lng_cancel(), [=] { closeBox(); });
	} else {
		addButton(tr::lng_box_ok(), [=] { closeBox(); });
	}
}

ChatRestrictionsInfo EditRestrictedBox::defaultRights() const {
	return ChatRestrictionsInfo();
}

void EditRestrictedBox::showRestrictUntil() {
	uiShow()->showBox(Box([=](not_null<Ui::GenericBox*> box) {
		const auto save = [=](TimeId result) {
			if (!result) {
				return;
			}
			setRestrictUntil(result);
			box->closeBox();
		};
		const auto now = base::unixtime::now();
		const auto time = isUntilForever()
			? (now + kSecondsInDay)
			: getRealUntilValue();
		ChooseDateTimeBox(box, {
			.title = tr::lng_rights_chat_banned_until_header(),
			.submit = tr::lng_settings_save(),
			.done = save,
			.min = [=] { return now; },
			.time = time,
			.max = [=] {
				return now + kSecondsInDay * kMaxRestrictDelayDays;
			},
		});
	}));
}

void EditRestrictedBox::setRestrictUntil(TimeId until) {
	_until = until;
	_untilVariants.clear();
	createUntilGroup();
	createUntilVariants();
}

bool EditRestrictedBox::isUntilForever() const {
	return ChannelData::IsRestrictedForever(_until);
}

void EditRestrictedBox::createUntilGroup() {
	_untilGroup = std::make_shared<Ui::RadiobuttonGroup>(
		isUntilForever() ? 0 : _until);
	_untilGroup->setChangedCallback([this](int value) {
		if (value == kUntilCustom) {
			_untilGroup->setValue(_until);
			showRestrictUntil();
		} else if (_until != value) {
			_until = value;
		}
	});
}

void EditRestrictedBox::createUntilVariants() {
	auto addVariant = [&](int value, const QString &text) {
		if (!canSave() && _untilGroup->current() != value) {
			return;
		}
		_untilVariants.emplace_back(
			addControl(
				object_ptr<Ui::Radiobutton>(
					this,
					_untilGroup,
					value,
					text,
					st::defaultCheckbox),
				st::rightsToggleMargin));
		if (!canSave()) {
			_untilVariants.back()->setDisabled(true);
		}
	};
	auto addCustomVariant = [&](TimeId until, TimeId from, TimeId to) {
		if (!ChannelData::IsRestrictedForever(until)
			&& until > from
			&& until <= to) {
			addVariant(
				until,
				tr::lng_rights_chat_banned_custom_date(
					tr::now,
					lt_date,
					langDateTime(base::unixtime::parse(until))));
		}
	};
	auto addCurrentVariant = [&](TimeId from, TimeId to) {
		auto oldUntil = _oldRights.until;
		if (oldUntil < _until) {
			addCustomVariant(oldUntil, from, to);
		}
		addCustomVariant(_until, from, to);
		if (oldUntil > _until) {
			addCustomVariant(oldUntil, from, to);
		}
	};
	addVariant(0, tr::lng_rights_chat_banned_forever(tr::now));

	auto now = base::unixtime::now();
	auto nextDay = now + kSecondsInDay;
	auto nextWeek = now + kSecondsInWeek;
	addCurrentVariant(0, nextDay);
	addVariant(kUntilOneDay, tr::lng_rights_chat_banned_day(tr::now, lt_count, 1));
	addCurrentVariant(nextDay, nextWeek);
	addVariant(kUntilOneWeek, tr::lng_rights_chat_banned_week(tr::now, lt_count, 1));
	addCurrentVariant(nextWeek, INT_MAX);
	addVariant(kUntilCustom, tr::lng_rights_chat_banned_custom(tr::now));
}

TimeId EditRestrictedBox::getRealUntilValue() const {
	Expects(_until != kUntilCustom);
	if (_until == kUntilOneDay) {
		return base::unixtime::now() + kSecondsInDay;
	} else if (_until == kUntilOneWeek) {
		return base::unixtime::now() + kSecondsInWeek;
	}
	Assert(_until >= 0);
	return _until;
}
