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

#include "apiwrap.h"
#include "base/unixtime.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_histories.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "history/history.h"
#include "history/history_item.h"
#include "lang/lang_keys.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "menu/menu_ttl_validator.h"
#include "ui/boxes/confirm_box.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/labels.h"
#include "ui/rect.h"
#include "ui/wrap/slide_wrap.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"

namespace {

constexpr auto kDeleteMessagesBoxAnimationDuration = crl::time(80);

} // namespace

DeleteMessagesBox::DeleteMessagesBox(
	QWidget*,
	not_null<HistoryItem*> item)
: _session(&item->history()->session())
, _ids(1, item->fullId()) {
}

DeleteMessagesBox::DeleteMessagesBox(
	QWidget*,
	not_null<Main::Session*> session,
	MessageIdsList &&selected)
: _session(session)
, _ids(std::move(selected)) {
	Expects(!_ids.empty());
}

DeleteMessagesBox::DeleteMessagesBox(
	QWidget*,
	not_null<PeerData*> peer,
	QDate firstDayToDelete,
	QDate lastDayToDelete)
: _session(&peer->session())
, _wipeHistoryPeer(peer)
, _wipeHistoryJustClear(true)
, _wipeHistoryFirstToDelete(firstDayToDelete)
, _wipeHistoryLastToDelete(lastDayToDelete) {
}

DeleteMessagesBox::DeleteMessagesBox(
	QWidget*,
	not_null<PeerData*> peer,
	bool justClear)
: _session(&peer->session())
, _wipeHistoryPeer(peer)
, _wipeHistoryJustClear(justClear) {
}

crl::time DeleteMessagesBox::layerAnimationDuration() const {
	return kDeleteMessagesBoxAnimationDuration;
}

void DeleteMessagesBox::prepare() {
	auto details = TextWithEntities();
	const auto appendDetails = [&](TextWithEntities &&text) {
		details.append(u"\n\n"_q).append(std::move(text));
	};
	auto deleteText = lifetime().make_state<rpl::variable<QString>>();
	*deleteText = tr::lng_box_delete();
	auto deleteStyle = &st::defaultBoxButton;
	auto canDelete = true;
	if (const auto peer = _wipeHistoryPeer) {
		if (!_wipeHistoryFirstToDelete.isNull()) {
			details = (_wipeHistoryFirstToDelete
				== _wipeHistoryLastToDelete)
				? tr::lng_sure_delete_by_date_one(
					tr::now,
					lt_date,
					TextWithEntities{
						langDayOfMonthFull(_wipeHistoryFirstToDelete) },
					tr::rich)
				: tr::lng_sure_delete_by_date_many(
					tr::now,
					lt_days,
					tr::lng_sure_delete_selected_days(
						tr::now,
						lt_count,
						_wipeHistoryFirstToDelete.daysTo(
							_wipeHistoryLastToDelete) + 1,
						tr::marked),
					tr::rich);
			deleteStyle = &st::attentionBoxButton;
		} else if (_wipeHistoryJustClear) {
			const auto isChannel = peer->isChannel() && !peer->isMegagroup();
			_revokeJustClearForChannel = isChannel;
			details.text = isChannel
				? tr::lng_sure_delete_channel_history(
					tr::now,
					lt_channel,
					peer->name())
				: peer->isSelf()
				? tr::lng_sure_delete_saved_messages(tr::now)
				: peer->isUser()
				? tr::lng_sure_delete_history(
					tr::now,
					lt_contact,
					peer->name())
				: tr::lng_sure_delete_group_history(
					tr::now,
					lt_group,
					peer->name());
			details = tr::rich(details.text);
			deleteStyle = &st::attentionBoxButton;
		} else {
			details.text = peer->isSelf()
				? tr::lng_sure_delete_saved_messages(tr::now)
				: peer->isUser()
				? tr::lng_sure_delete_history(
					tr::now,
					lt_contact,
					peer->name())
				: peer->isChat()
				? tr::lng_sure_delete_and_exit(
					tr::now,
					lt_group,
					peer->name())
				: peer->isMegagroup()
				? tr::lng_sure_leave_group(tr::now)
				: tr::lng_sure_leave_channel(tr::now);
			details = tr::rich(details.text);
			if (!peer->isUser()) {
				*deleteText = tr::lng_box_leave();
			}
			deleteStyle = &st::attentionBoxButton;
		}
		if (_revokeJustClearForChannel) {
		} else if (auto revoke = revokeText(peer)) {
			_revoke.create(
				this,
				revoke->checkbox,
				false,
				st::defaultBoxCheckbox);
			appendDetails(std::move(revoke->description));
			if (!peer->isUser() && !_wipeHistoryJustClear) {
				_revoke->checkedValue(
				) | rpl::on_next([=](bool revokeForAll) {
					*deleteText = revokeForAll
						? tr::lng_box_delete()
						: tr::lng_box_leave();
				}, _revoke->lifetime());
			}
		} else if (canDelete
			&& _wipeHistoryJustClear
			&& (peer->isMegagroup() || peer->isChat())) {
			appendDetails({
				tr::lng_delete_clear_for_me(tr::now)
			});
		}
	} else {
		details.text = hasSavedMusicMessages()
			? tr::lng_selected_remove_saved_music(tr::now)
			: (_ids.size() == 1)
			? tr::lng_selected_delete_sure_this(tr::now)
			: tr::lng_selected_delete_sure(tr::now, lt_count, _ids.size());
		if (const auto peer = checkFromSinglePeer()) {
			auto count = int(_ids.size());
			if (hasScheduledMessages() || hasSavedMusicMessages()) {
			} else if (auto revoke = revokeText(peer)) {
				const auto &settings = Core::App().settings();
				const auto revokeByDefault
					= !settings.rememberedDeleteMessageOnlyForYou();
				_revoke.create(
					this,
					revoke->checkbox,
					revokeByDefault,
					st::defaultBoxCheckbox);
				_revokeRemember.create(
					this,
					object_ptr<Ui::Checkbox>(
						this,
						tr::lng_remember(),
						false,
						st::defaultBoxCheckbox));
				_revokeRemember->hide(anim::type::instant);
				_revoke->checkedValue(
				) | rpl::on_next([=](bool checked) {
					_revokeRemember->toggle(
						checked != revokeByDefault,
						anim::type::normal);
				}, _revokeRemember->lifetime());
				_revokeRemember->heightValue(
				) | rpl::on_next([=](int h) {
					setDimensions(st::boxWidth, _fullHeight + h);
				}, lifetime());
				appendDetails(std::move(revoke->description));
			} else if (peer->isChannel()) {
				if (peer->isMegagroup()) {
					appendDetails({
						tr::lng_delete_for_everyone_hint(
							tr::now,
							lt_count,
							count)
					});
				}
			} else if (peer->isChat()) {
				appendDetails({
					tr::lng_delete_for_me_chat_hint(tr::now, lt_count, count)
				});
			} else if (!peer->isSelf()
				&& (!peer->isUser() || !peer->asUser()->isInaccessible())) {
				if (const auto user = peer->asUser(); user && user->isBot()) {
					_revokeForBot = true;
				}
				appendDetails({
					tr::lng_delete_for_me_hint(tr::now, lt_count, count)
				});
			}
		}
	}
	_text.create(this, rpl::single(std::move(details)), st::boxLabel);
	_text->resizeToWidth(st::boxWidth - rect::m::sum::h(st::boxPadding));

	if (_wipeHistoryJustClear && _wipeHistoryPeer) {
		const auto validator = TTLMenu::TTLValidator(
			uiShow(),
			_wipeHistoryPeer);
		if (validator.can()) {
			_wipeHistoryPeer->updateFull();
			_autoDeleteSettings.create(
				this,
				(_wipeHistoryPeer->messagesTTL()
					? tr::lng_edit_auto_delete_settings(tr::now)
					: tr::lng_enable_auto_delete(tr::now)),
				st::boxLinkButton);
			_autoDeleteSettings->setClickedCallback([=] {
				validator.showBox();
			});
		}
	}

	if (canDelete) {
		addButton(
			deleteText->value(),
			[=] { deleteAndClear(); },
			*deleteStyle);
		addButton(tr::lng_cancel(), [=] { closeBox(); });
	} else {
		addButton(tr::lng_about_done(), [=] { closeBox(); });
	}

	const auto &padding = st::boxPadding;
	rpl::combine(
		widthValue(),
		_text->naturalWidthValue()
	) | rpl::on_next([=](int full, int) {
		_text->resizeToNaturalWidth(full - padding.left() - padding.right());

		auto fullHeight = st::boxPadding.top()
			+ _text->height()
			+ st::boxPadding.bottom();
		if (_revoke) {
			fullHeight += st::boxMediumSkip + _revoke->heightNoMargins();
		}
		if (_autoDeleteSettings) {
			fullHeight += st::boxMediumSkip
				+ _autoDeleteSettings->height()
				+ st::boxLittleSkip;
		}
		setDimensions(st::boxWidth, fullHeight);
		_fullHeight = fullHeight;
	}, lifetime());
}

bool DeleteMessagesBox::hasScheduledMessages() const {
	for (const auto &fullId : _ids) {
		if (const auto item = _session->data().message(fullId)) {
			if (item->isScheduled()) {
				return true;
			}
		}
	}
	return false;
}

bool DeleteMessagesBox::hasSavedMusicMessages() const {
	for (const auto &fullId : _ids) {
		if (const auto item = _session->data().message(fullId)) {
			if (item->isSavedMusicItem()) {
				return true;
			}
		}
	}
	return false;
}

PeerData *DeleteMessagesBox::checkFromSinglePeer() const {
	auto result = (PeerData*)nullptr;
	for (const auto &fullId : _ids) {
		if (const auto item = _session->data().message(fullId)) {
			const auto peer = item->history()->peer;
			if (!result) {
				result = peer;
			} else if (result != peer) {
				return nullptr;
			}
		}
	}
	return result;
}

auto DeleteMessagesBox::revokeText(not_null<PeerData*> peer) const
-> std::optional<RevokeConfig> {
	auto result = RevokeConfig();
	if (peer == _wipeHistoryPeer) {
		if (!peer->canRevokeFullHistory()) {
			return std::nullopt;
		} else if (const auto user = peer->asUser()) {
			result.checkbox = tr::lng_delete_for_other_check(
				tr::now,
				lt_user,
				{ user->firstName },
				tr::rich);
		} else {
			result.checkbox.text = tr::lng_delete_for_everyone_check(tr::now);
		}
		return result;
	}

	const auto items = peer->owner().idsToItems(_ids);

	if (items.size() != _ids.size()) {
		// We don't have information about all messages.
		return std::nullopt;
	}

	const auto now = base::unixtime::now();
	const auto canRevoke = [&](HistoryItem * item) {
		return item->canDeleteForEveryone(now);
	};
	const auto cannotRevoke = [&](HistoryItem *item) {
		return !item->canDeleteForEveryone(now);
	};
	const auto canRevokeAll = ranges::none_of(items, cannotRevoke);
	auto outgoing = items | ranges::views::filter(&HistoryItem::out);
	const auto canRevokeOutgoingCount = canRevokeAll
		? -1
		: ranges::count_if(outgoing, canRevoke);

	if (canRevokeAll) {
		if (const auto user = peer->asUser()) {
			result.checkbox = tr::lng_delete_for_other_check(
				tr::now,
				lt_user,
				{ user->firstName },
				tr::rich);
		} else {
			result.checkbox.text = tr::lng_delete_for_everyone_check(tr::now);
		}
		return result;
	} else if (canRevokeOutgoingCount > 0) {
		result.checkbox.text = tr::lng_delete_for_other_my(tr::now);
		if (const auto user = peer->asUser()) {
			if (canRevokeOutgoingCount == 1) {
				result.description = tr::lng_selected_unsend_about_user_one(
					tr::now,
					lt_user,
					tr::bold(user->shortName()),
					tr::marked);
			} else {
				result.description = tr::lng_selected_unsend_about_user(
					tr::now,
					lt_count,
					canRevokeOutgoingCount,
					lt_user,
					tr::bold(user->shortName()),
					tr::marked);
			}
		} else if (canRevokeOutgoingCount == 1) {
			result.description = tr::lng_selected_unsend_about_group_one(
				tr::now,
				tr::marked);
		} else {
			result.description = tr::lng_selected_unsend_about_group(
				tr::now,
				lt_count,
				canRevokeOutgoingCount,
				tr::marked);
		}
		return result;
	}
	return std::nullopt;
}

void DeleteMessagesBox::resizeEvent(QResizeEvent *e) {
	BoxContent::resizeEvent(e);

	const auto &padding = st::boxPadding;
	_text->moveToLeft(padding.left(), padding.top());
	auto top = _text->bottomNoMargins() + st::boxMediumSkip;
	if (_revoke) {
		const auto availableWidth = width() - 2 * padding.left();
		_revoke->resizeToNaturalWidth(availableWidth);
		_revoke->moveToLeft(padding.left(), top);
		top += _revoke->heightNoMargins() + st::boxLittleSkip;
		if (_revokeRemember) {
			_revokeRemember->resizeToNaturalWidth(availableWidth);
			_revokeRemember->moveToLeft(padding.left(),top);
			top += _revokeRemember->heightNoMargins();
		}
	}
	if (_autoDeleteSettings) {
		top += st::boxMediumSkip - st::boxLittleSkip;
		_autoDeleteSettings->moveToLeft(padding.left(), top);
	}
}

void DeleteMessagesBox::keyPressEvent(QKeyEvent *e) {
	if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
		// Don't make the clearing history so easy.
		if (!_wipeHistoryPeer) {
			deleteAndClear();
		}
	} else {
		BoxContent::keyPressEvent(e);
	}
}

PaidPostType DeleteMessagesBox::paidPostType() const {
	auto result = PaidPostType::None;
	const auto now = base::unixtime::now();
	for (const auto &id : _ids) {
		if (const auto item = _session->data().message(id)) {
			const auto type = item->paidType();
			if (type != PaidPostType::None) {
				const auto date = item->date();
				const auto config = &item->history()->session().appConfig();
				const auto limit = config->suggestedPostAgeMin();
				if (now < date || now - date <= limit) {
					if (type == PaidPostType::Ton) {
						return type;
					} else if (type == PaidPostType::Stars) {
						result = type;
					}
				}
			}
		}
	}
	return result;
}

void DeleteMessagesBox::deleteAndClear() {
	const auto warnPaidType = _confirmedDeletePaidSuggestedPosts
		? PaidPostType::None
		: paidPostType();
	if (warnPaidType != PaidPostType::None) {
		const auto weak = base::make_weak(this);
		const auto callback = [=](Fn<void()> close) {
			close();
			if (const auto strong = weak.get()) {
				strong->_confirmedDeletePaidSuggestedPosts = true;
				strong->deleteAndClear();
			}
		};
		const auto ton = (warnPaidType == PaidPostType::Ton);
		uiShow()->show(Ui::MakeConfirmBox({
			.text = (ton
				? tr::lng_suggest_warn_text_ton
				: tr::lng_suggest_warn_text_stars)(
					tr::now,
					tr::rich),
			.confirmed = callback,
			.confirmText = tr::lng_suggest_warn_delete_anyway(tr::now),
			.confirmStyle = &st::attentionBoxButton,
			.title = (ton
				? tr::lng_suggest_warn_title_ton
				: tr::lng_suggest_warn_title_stars)(tr::now),
		}));
		return;
	}
	if (_revoke
		&& _revokeRemember
		&& _revokeRemember->toggled()
		&& _revokeRemember->entity()->checked()) {
		Core::App().settings().setRememberedDeleteMessageOnlyForYou(
			!_revoke->checked());
		Core::App().saveSettingsDelayed();
	}
	const auto revoke = _revoke
		? _revoke->checked()
		: (_revokeForBot || _revokeJustClearForChannel);
	const auto session = _session;
	const auto invokeCallbackAndClose = [&] {
		// deleteMessages can initiate closing of the current section,
		// which will cause this box to be destroyed.
		const auto weak = base::make_weak(this);
		if (hasSavedMusicMessages()) {
			uiShow()->showToast(tr::lng_saved_music_removed(tr::now));
		}
		if (const auto callback = _deleteConfirmedCallback) {
			callback();
		}
		if (const auto strong = weak.get()) {
			strong->closeBox();
		}
	};
	if (!_wipeHistoryFirstToDelete.isNull()) {
		const auto peer = _wipeHistoryPeer;
		const auto firstDayToDelete = _wipeHistoryFirstToDelete;
		const auto lastDayToDelete = _wipeHistoryLastToDelete;

		invokeCallbackAndClose();
		session->data().histories().deleteMessagesByDates(
			session->data().history(peer),
			firstDayToDelete,
			lastDayToDelete,
			revoke);
		session->data().sendHistoryChangeNotifications();
		return;
	} else if (const auto peer = _wipeHistoryPeer) {
		const auto justClear = _wipeHistoryJustClear;
		invokeCallbackAndClose();

		if (justClear) {
			session->api().clearHistory(peer, revoke);
		} else {
			Core::App().closeChatFromWindows(peer);
			// Don't delete old history by default,
			// because Android app doesn't.
			//
			//if (const auto from = peer->migrateFrom()) {
			//	peer->session().api().deleteConversation(from, false);
			//}
			session->api().deleteConversation(peer, revoke);
		}
		return;
	}
	const auto ids = _ids;
	invokeCallbackAndClose();
	session->data().histories().deleteMessages(ids, revoke);
	session->data().sendHistoryChangeNotifications();
}
