/*
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 "dialogs/ui/dialogs_top_bar_suggestion_content.h"

#include "base/call_delayed.h"
#include "data/data_authorization.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "settings/settings_common.h"
#include "ui/effects/animation_value.h"
#include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/power_saving.h"
#include "ui/rect.h"
#include "ui/text/format_values.h"
#include "ui/text/text_custom_emoji.h"
#include "ui/ui_rpl_filter.h"
#include "ui/ui_utility.h"
#include "ui/vertical_list.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/shadow.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_chat.h"
#include "styles/style_dialogs.h"
#include "styles/style_layers.h"
#include "styles/style_premium.h"
#include "styles/style_settings.h"

namespace Dialogs {
namespace {

[[nodiscard]] int PillRadius() {
	const auto padding = st::msgReplyPadding.top();
	return (padding
		+ st::semiboldTextStyle.font->height
		+ st::dialogsTopBarSuggestionAboutStyle.font->height
		+ padding) / 2;
}

void PaintTopFade(QPainter &p, int outerWidth, int fadeHeight) {
	if (fadeHeight <= 0) {
		return;
	}
	auto transparent = st::dialogsBg->c;
	transparent.setAlpha(0);
	auto grad = QLinearGradient(0, 0, 0, fadeHeight);
	grad.setColorAt(0, st::dialogsBg->c);
	grad.setColorAt(1, transparent);
	p.fillRect(QRect(0, 0, outerWidth, fadeHeight), grad);
}

[[nodiscard]] QString FormatUnconfirmedAuthMessage(
		const std::vector<Data::UnreviewedAuth> &list) {
	if (list.empty()) {
		return QString();
	} else if (list.size() == 1) {
		const auto &auth = list.at(0);
		return tr::lng_unconfirmed_auth_single(
			tr::now,
			lt_from,
			auth.device,
			lt_country,
			auth.location);
	}
	auto commonLocation = list.at(0).location;
	for (auto i = 1; i < list.size(); ++i) {
		if (commonLocation != list.at(i).location) {
			commonLocation.clear();
			break;
		}
	}
	if (commonLocation.isEmpty()) {
		return tr::lng_unconfirmed_auth_multiple(
			tr::now,
			lt_count,
			list.size());
	}
	return tr::lng_unconfirmed_auth_multiple_from(
		tr::now,
		lt_count,
		list.size(),
		lt_country,
		commonLocation);
}

void PaintPillTopSheen(QPainter &p, const QRect &pill, int radius) {
	if (pill.isEmpty() || st::dialogsBg->c.lightness() >= 128) {
		return;
	}
	auto top = QColor(255, 255, 255, 40);
	auto mid = QColor(255, 255, 255, 0);
	auto bottom = QColor(255, 255, 255, 20);
	auto grad = QLinearGradient(0, pill.top(), 0, pill.bottom());
	grad.setColorAt(0., top);
	grad.setColorAt(0.5, mid);
	grad.setColorAt(1., bottom);
	p.setPen(QPen(QBrush(grad), st::lineWidth));
	p.setBrush(Qt::NoBrush);
	const auto half = 0.5 * st::lineWidth;
	const auto stroke = QRectF(pill).adjusted(half, half, -half, -half);
	p.drawRoundedRect(stroke, radius - half, radius - half);
}

} // namespace

UnconfirmedAuthWrap::UnconfirmedAuthWrap(
	not_null<Ui::RpWidget*> parent,
	object_ptr<Ui::VerticalLayout> &&child)
: Ui::SlideWrap<Ui::VerticalLayout>(parent, std::move(child))
, _shadow(st::dialogsTopBarSuggestionShadow) {
	paintRequest() | rpl::on_next([=] {
		if (!_collapseSnapshot.isNull()) {
			auto p = QPainter(this);
			p.drawPixmap(0, 0, _collapseSnapshot);
		}
	}, lifetime());
}

rpl::producer<int> UnconfirmedAuthWrap::desiredHeightValue() const {
	return entity()->heightValue();
}

void UnconfirmedAuthWrap::setCollapseProgress(
		rpl::producer<float64> progress) {
	std::move(progress) | rpl::on_next([=](float64 value) {
		if (_collapseProgress == value) {
			return;
		}
		_collapseProgress = value;
		if (value == 0.) {
			releaseCollapseSnapshot();
		}
		resizeToWidth(width());
		update();
	}, lifetime());
}

void UnconfirmedAuthWrap::prepareCollapseSnapshot() {
	_collapseSnapshot = Ui::GrabWidget(this);
	if (const auto w = wrapped()) {
		w->hide();
	}
	update();
}

void UnconfirmedAuthWrap::releaseCollapseSnapshot() {
	if (_collapseSnapshot.isNull()) {
		return;
	}
	_collapseSnapshot = QPixmap();
	if (const auto w = wrapped()) {
		w->show();
	}
}

int UnconfirmedAuthWrap::resizeGetHeight(int newWidth) {
	if (!_collapseSnapshot.isNull()) {
		const auto fullHeight = int(_collapseSnapshot.height()
			/ _collapseSnapshot.devicePixelRatio());
		return int(base::SafeRound(
			fullHeight * (1. - _collapseProgress)));
	}
	if (const auto w = wrapped()) {
		w->resizeToWidth(newWidth);
	}
	return wrapped() ? wrapped()->height() : 0;
}

not_null<UnconfirmedAuthWrap*> CreateUnconfirmedAuthContent(
		not_null<Ui::RpWidget*> parent,
		rpl::producer<std::vector<Data::UnreviewedAuth>> list,
		Fn<void(bool)> callback,
		rpl::producer<float64> collapseProgress) {
	const auto wrap = Ui::CreateChild<UnconfirmedAuthWrap>(
		parent,
		object_ptr<Ui::VerticalLayout>(parent));
	wrap->setCollapseProgress(std::move(collapseProgress));
	const auto content = wrap->entity();
	const auto &margins = st::dialogsTopBarSuggestionMargins;
	content->paintOn([=](QPainter &p) {
		const auto outer = content->rect();
		const auto pill = outer - margins;
		PaintTopFade(p, outer.width(), margins.top() + pill.height() / 2);
		if (pill.isEmpty()) {
			return;
		}
		const auto radius = std::min({
			PillRadius(),
			pill.width() / 2,
			pill.height() / 2,
		});
		wrap->shadow().paint(p, pill, radius);
		auto hq = PainterHighQualityEnabler(p);
		p.setBrush(st::dialogsBg);
		p.setPen(Qt::NoPen);
		p.drawRoundedRect(pill, radius, radius);
		PaintPillTopSheen(p, pill, radius);
	});

	const auto &basePadding = st::dialogsUnconfirmedAuthPadding;
	const auto padding = QMargins(
		margins.left() + basePadding.left(),
		basePadding.top(),
		margins.right() + basePadding.right(),
		basePadding.bottom());

	Ui::AddSkip(content, margins.top());
	Ui::AddSkip(content);

	content->add(
		object_ptr<Ui::FlatLabel>(
			content,
			tr::lng_unconfirmed_auth_title(),
			st::dialogsUnconfirmedAuthTitle),
		padding,
		style::al_top);

	Ui::AddSkip(content);

	content->add(
		object_ptr<Ui::FlatLabel>(
			content,
			std::move(list) | rpl::map(FormatUnconfirmedAuthMessage),
			st::dialogsUnconfirmedAuthAbout),
		padding,
		style::al_top)->setTryMakeSimilarLines(true);

	Ui::AddSkip(content);
	const auto buttons = content->add(object_ptr<Ui::FixedHeightWidget>(
		content,
		st::dialogsUnconfirmedAuthButton.height));
	const auto yes = Ui::CreateChild<Ui::RoundButton>(
		buttons,
		tr::lng_unconfirmed_auth_confirm(),
		st::dialogsUnconfirmedAuthButton);
	const auto no = Ui::CreateChild<Ui::RoundButton>(
		buttons,
		tr::lng_unconfirmed_auth_deny(),
		st::dialogsUnconfirmedAuthButtonNo);
	yes->setClickedCallback([=] {
		wrap->toggle(false, anim::type::normal);
		base::call_delayed(st::universalDuration, wrap, [=] {
			callback(true);
		});
	});
	no->setClickedCallback([=] {
		wrap->toggle(false, anim::type::normal);
		base::call_delayed(st::universalDuration, wrap, [=] {
			callback(false);
		});
	});
	buttons->sizeValue(
	) | rpl::filter_size(
	) | rpl::on_next([=](const QSize &s) {
		const auto halfWidth = (s.width() - rect::m::sum::h(padding)) / 2;
		yes->moveToLeft(
			padding.left() + (halfWidth - yes->width()) / 2,
			0);
		no->moveToLeft(
			padding.left() + halfWidth + (halfWidth - no->width()) / 2,
			0);
	}, buttons->lifetime());
	Ui::AddSkip(content);
	Ui::AddSkip(content, margins.bottom());

	return wrap;
}

TopBarSuggestionContent::TopBarSuggestionContent(
	not_null<Ui::RpWidget*> parent,
	Fn<bool()> emojiPaused)
: Ui::RippleButton(parent, st::defaultRippleAnimationBgOver)
, _titleSt(st::semiboldTextStyle)
, _contentTitleSt(st::dialogsTopBarSuggestionTitleStyle)
, _contentTextSt(st::dialogsTopBarSuggestionAboutStyle)
, _shadow(st::dialogsTopBarSuggestionShadow)
, _emojiPaused(std::move(emojiPaused)) {
	_leftPadding = st::dialogsTopBarLeftPadding;
	setRightIcon(RightIcon::Close);
}

void TopBarSuggestionContent::setRightIcon(RightIcon icon) {
	_rightButton = nullptr;
	if (icon == _rightIcon) {
		return;
	}
	_rightHide = nullptr;
	_rightArrow = nullptr;
	_rightIcon = icon;
	const auto &margins = st::dialogsTopBarSuggestionMargins;
	if (icon == RightIcon::Close) {
		_rightHide = base::make_unique_q<Ui::IconButton>(
			this,
			st::dialogsCancelSearchInPeer);
		const auto rightHide = _rightHide.get();
		sizeValue() | rpl::filter_size(
		) | rpl::on_next([=](const QSize &s) {
			const auto &button = st::dialogsCancelSearchInPeer;
			const auto padding = PillRadius()
				- button.rippleAreaSize / 2;
			const auto pillHeight = s.height() - rect::m::sum::v(margins);
			rightHide->moveToRight(
				margins.right() + padding - button.rippleAreaPosition.x(),
				margins.top()
					+ (pillHeight - button.rippleAreaSize) / 2
					- button.rippleAreaPosition.y());
		}, rightHide->lifetime());
		rightHide->show();
	} else if (icon == RightIcon::Arrow) {
		_rightArrow = base::make_unique_q<Ui::IconButton>(
			this,
			st::backButton);
		const auto arrow = _rightArrow.get();
		arrow->setIconOverride(
			&st::settingsPremiumArrow,
			&st::settingsPremiumArrowOver);
		arrow->setAttribute(Qt::WA_TransparentForMouseEvents);
		sizeValue() | rpl::filter_size(
		) | rpl::on_next([=](const QSize &s) {
			const auto &point = st::settingsPremiumArrowShift;
			const auto pillRight = s.width() - margins.right();
			const auto pillHeight = s.height() - rect::m::sum::v(margins);
			arrow->moveToLeft(
				pillRight - arrow->width(),
				margins.top()
					+ point.y()
					+ (pillHeight - arrow->height()) / 2);
		}, arrow->lifetime());
		arrow->show();
	}
	resizeToWidth(width());
}

void TopBarSuggestionContent::setRightButton(
		rpl::producer<TextWithEntities> text,
		Fn<void()> callback) {
	_rightHide = nullptr;
	_rightArrow = nullptr;
	_rightIcon = RightIcon::None;
	if (!text) {
		_rightButton = nullptr;
		return;
	}
	using namespace Ui;
	_rightButton = base::make_unique_q<RoundButton>(
		this,
		rpl::single(QString()),
		st::dialogsTopBarRightButton);
	_rightButton->setText(std::move(text));
	const auto &margins = st::dialogsTopBarSuggestionMargins;
	rpl::combine(
		sizeValue(),
		_rightButton->sizeValue()
	) | rpl::on_next([=](QSize outer, QSize inner) {
		const auto pillHeight = outer.height() - rect::m::sum::v(margins);
		const auto verticalGap = (pillHeight - inner.height()) / 2;
		_rightButton->moveToRight(
			margins.right() + verticalGap,
			margins.top() + verticalGap,
			outer.width());
	}, _rightButton->lifetime());
	_rightButton->setFullRadius(true);
	_rightButton->setClickedCallback(std::move(callback));
	_rightButton->show();
}

void TopBarSuggestionContent::draw(QPainter &p) {
	const auto outer = Ui::RpWidget::rect();
	const auto &margins = st::dialogsTopBarSuggestionMargins;
	const auto pill = outer - margins;

	PaintTopFade(p, outer.width(), margins.top() + pill.height() / 2);

	if (pill.isEmpty()) {
		return;
	}
	const auto radius = std::min({
		PillRadius(),
		pill.width() / 2,
		pill.height() / 2,
	});

	_shadow.paint(p, pill, radius);

	auto hq = PainterHighQualityEnabler(p);
	p.setBrush(st::dialogsBg);
	p.setPen(Qt::NoPen);
	p.drawRoundedRect(pill, radius, radius);
	PaintPillTopSheen(p, pill, radius);

	auto clipPath = QPainterPath();
	clipPath.addRoundedRect(pill, radius, radius);
	p.setClipPath(clipPath);
	Ui::RippleButton::paintRipple(p, 0, 0);
	p.setClipping(false);

	const auto leftPadding = _leftPadding + margins.left();
	const auto rightPadding = margins.right();
	const auto topPadding = st::msgReplyPadding.top() + margins.top();
	const auto availableWidthNoPhoto = outer.width()
		- (_rightArrow
			? (_rightArrow->width() / 4 * 3) // Takes full height.
			: 0)
		- leftPadding
		- rightPadding;
	const auto availableWidth = availableWidthNoPhoto
		- (_rightHide ? _rightHide->width() : 0);
	const auto titleRight = leftPadding;
	const auto hasSecondLineTitle = availableWidth < _contentTitle.maxWidth();
	const auto paused = On(PowerSaving::kEmojiChat)
		|| (_emojiPaused && _emojiPaused());
	p.setPen(st::windowActiveTextFg);
	p.setPen(st::windowFg);
	{
		const auto left = leftPadding;
		const auto top = topPadding;
		_contentTitle.draw(p, {
			.position = QPoint(left, top),
			.outerWidth = hasSecondLineTitle
				? availableWidth
				: (availableWidth - titleRight),
			.availableWidth = availableWidth,
			.pausedEmoji = paused,
			.elisionLines = hasSecondLineTitle ? 2 : 1,
		});
	}
	{
		const auto left = leftPadding;
		const auto top = hasSecondLineTitle
			? (topPadding
				+ _titleSt.font->height
				+ _contentTitleSt.font->height)
			: topPadding + _titleSt.font->height;
		const auto lineHeight = _contentTextSt.font->height;
		const auto lineLayout = [=](int line) -> Ui::Text::LineGeometry {
			line++;
			const auto diff = (st::sponsoredMessageBarMaxHeight)
				- line * lineHeight;
			if (diff < 3 * lineHeight) {
				return {
					.width = availableWidth,
					.elided = true,
				};
			} else if (diff < 2 * lineHeight) {
				return {};
			}
			return { .width = availableWidth };
		};
		p.setPen(_descriptionColorOverride.value_or(st::windowSubTextFg->c));
		_contentText.draw(p, {
			.position = QPoint(left, top),
			.outerWidth = availableWidth,
			.availableWidth = availableWidth,
			.geometry = Ui::Text::GeometryDescriptor{
				.layout = std::move(lineLayout),
			},
			.pausedEmoji = paused,
		});
	}
}

void TopBarSuggestionContent::setContent(
		TextWithEntities title,
		TextWithEntities description,
		std::optional<Ui::Text::MarkedContext> context,
		std::optional<QColor> descriptionColorOverride) {
	_descriptionColorOverride = descriptionColorOverride;
	if (context) {
		context->repaint = [=] { update(); };
		_contentTitle.setMarkedText(
			_contentTitleSt,
			std::move(title),
			kMarkupTextOptions,
			*context);
		_contentText.setMarkedText(
			_contentTextSt,
			std::move(description),
			kMarkupTextOptions,
			base::take(*context));
	} else {
		_contentTitle.setMarkedText(_contentTitleSt, std::move(title));
		_contentText.setMarkedText(_contentTextSt, std::move(description));
	}
	resizeToWidth(width());
	update();
}

void TopBarSuggestionContent::paintEvent(QPaintEvent *) {
	auto p = QPainter(this);
	if (!_collapseSnapshot.isNull()) {
		p.drawPixmap(0, 0, _collapseSnapshot);
		return;
	}
	draw(p);
}

void TopBarSuggestionContent::prepareCollapseSnapshot() {
	_collapseSnapshot = Ui::GrabWidget(this);
	for (const auto child : children()) {
		if (const auto widget = qobject_cast<QWidget*>(child)) {
			widget->hide();
		}
	}
	update();
}

void TopBarSuggestionContent::releaseCollapseSnapshot() {
	if (_collapseSnapshot.isNull()) {
		return;
	}
	_collapseSnapshot = QPixmap();
	for (const auto child : children()) {
		if (const auto widget = qobject_cast<QWidget*>(child)) {
			widget->show();
		}
	}
}

int TopBarSuggestionContent::resizeGetHeight(int newWidth) {
	if (!_collapseSnapshot.isNull()) {
		const auto fullHeight = int(_collapseSnapshot.height()
			/ _collapseSnapshot.devicePixelRatio());
		return int(base::SafeRound(
			fullHeight * (1. - _collapseProgress)));
	}
	const auto &margins = st::dialogsTopBarSuggestionMargins;
	const auto topPadding = st::msgReplyPadding.top();
	const auto bottomPadding = st::msgReplyPadding.top();
	const auto availableWidthNoPhoto = newWidth
		- rect::m::sum::h(margins)
		- (_rightArrow
			? (_rightArrow->width() / 4 * 3) // Takes full height.
			: 0)
		- _leftPadding;
	const auto availableWidth = availableWidthNoPhoto
		- (_rightHide ? _rightHide->width() : 0);
	if (availableWidth <= 0) {
		return topPadding + bottomPadding + rect::m::sum::v(margins);
	}
	const auto hasSecondLineTitle
		= (availableWidth < _contentTitle.maxWidth());
	const auto textTop = hasSecondLineTitle
		? (topPadding
			+ _titleSt.font->height
			+ _contentTitleSt.font->height)
		: (topPadding + _titleSt.font->height);

	const auto lineHeight = _contentTextSt.font->height;
	auto lineLayout = [=](int line) -> Ui::Text::LineGeometry {
		line++;
		const auto diff = (st::sponsoredMessageBarMaxHeight)
			- line * lineHeight;
		if (diff < 3 * lineHeight) {
			return {
				.width = availableWidth,
				.elided = true,
			};
		} else if (diff < 2 * lineHeight) {
			return {};
		}
		return { .width = availableWidth };
	};
	const auto dims = _contentText.countDimensions(
		Ui::Text::GeometryDescriptor{ .layout = std::move(lineLayout) });
	const auto natural = textTop + dims.height + bottomPadding;
	const auto capped = std::min(
		natural,
		st::sponsoredMessageBarMaxHeight);
	const auto withMargins = capped + rect::m::sum::v(margins);
	return int(base::SafeRound(withMargins * (1. - _collapseProgress)));
}

void TopBarSuggestionContent::setCollapseProgress(
		rpl::producer<float64> progress) {
	std::move(progress) | rpl::on_next([=](float64 value) {
		if (_collapseProgress == value) {
			return;
		}
		_collapseProgress = value;
		if (value == 0.) {
			releaseCollapseSnapshot();
		}
		resizeToWidth(width());
		update();
	}, lifetime());
}

void TopBarSuggestionContent::setHideCallback(Fn<void()> hideCallback) {
	Expects(_rightHide != nullptr);
	_rightHide->setClickedCallback(std::move(hideCallback));
}

void TopBarSuggestionContent::setLeadingWidget(Ui::RpWidget *widget) {
	_leadingWidgetLifetime.destroy();
	if (_leadingWidget && _leadingWidget != widget) {
		_leadingWidget->deleteLater();
	}
	_leadingWidget = widget;
	const auto basePadding = st::dialogsTopBarLeftPadding;
	if (!widget) {
		if (_leftPadding != basePadding) {
			_leftPadding = basePadding;
			resizeToWidth(width());
			update();
		}
		return;
	}
	widget->setParent(this);
	widget->setAttribute(Qt::WA_TransparentForMouseEvents);
	const auto &margins = st::dialogsTopBarSuggestionMargins;
	const auto &row = st::defaultDialogRow;
	sizeValue() | rpl::filter_size(
	) | rpl::on_next([=](const QSize &s) {
		widget->raise();
		widget->show();
		const auto pillHeight = s.height() - rect::m::sum::v(margins);
		widget->moveToLeft(
			row.padding.left() + (row.photoSize - widget->width()) / 2,
			margins.top() + (pillHeight - widget->height()) / 2);
	}, _leadingWidgetLifetime);
	const auto padding = row.nameLeft - margins.left();
	if (_leftPadding != padding) {
		_leftPadding = padding;
		resizeToWidth(width());
		update();
	}
}

const style::TextStyle & TopBarSuggestionContent::contentTitleSt() const {
	return _contentTitleSt;
}

} // namespace Dialogs
