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

#include "base/call_delayed.h"
#include "base/event_filter.h"
#include "base/random.h"
#include "base/timer_rpl.h"
#include "base/unixtime.h"
#include "boxes/star_gift_auction_box.h"
#include "boxes/star_gift_craft_animation.h"
#include "boxes/star_gift_preview_box.h"
#include "boxes/star_gift_resale_box.h"
#include "apiwrap.h"
#include "api/api_credits.h"
#include "api/api_premium.h"
#include "boxes/star_gift_box.h"
#include "data/components/gift_auctions.h"
#include "core/ui_integration.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_document.h"
#include "data/data_session.h"
#include "data/data_star_gift.h"
#include "data/data_user.h"
#include "info/peer_gifts/info_peer_gifts_common.h"
#include "lang/lang_keys.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "ui/boxes/about_cocoon_box.h" // AddUniqueCloseButton.
#include "ui/boxes/confirm_box.h"
#include "ui/controls/button_labels.h"
#include "ui/controls/feature_list.h"
#include "ui/effects/numbers_animation.h"
#include "ui/layers/generic_box.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/gradient_round_button.h"
#include "ui/widgets/tooltip.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/painter.h"
#include "ui/top_background_gradient.h"
#include "ui/ui_utility.h"
#include "ui/vertical_list.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_chat_helpers.h" // stickerPanDeleteIconFg
#include "styles/style_credits.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"

namespace Ui {
namespace {

using namespace Info::PeerGifts;

struct ColorScheme {
	Data::UniqueGiftBackdrop backdrop;
	QColor button1;
	QColor button2;
};

[[nodiscard]] QColor ForgeBgOverlay() {
	return QColor(0xBA, 0xDF, 0xFF, 32);
}

[[nodiscard]] QColor ForgeFailOverlay() {
	return QColor(0xE1, 0x79, 0x23, 41);
}

[[nodiscard]] std::array<ColorScheme, 5> CraftBackdrops() {
	struct Colors {
		int center = 0;
		int edge = 0;
		int pattern = 0;
		int button1 = 0;
		int button2 = 0;
	};
	const auto hardcoded = [](Colors colors) {
		auto result = ColorScheme();
		const auto color = [](int value) {
			return QColor(
				(uint32(value) >> 16) & 0xFF,
				(uint32(value) >> 8) & 0xFF,
				(uint32(value)) & 0xFF);
		};
		result.backdrop.centerColor = color(colors.center);
		result.backdrop.edgeColor = color(colors.edge);
		result.backdrop.patternColor = color(colors.pattern);
		result.button1 = color(colors.button1);
		result.button2 = color(colors.button2);
		return result;
	};
	return {
		hardcoded({ 0x2C4359, 0x232E3F, 0x040C1A, 0x10A5DF, 0x2091E9 }),
		hardcoded({ 0x2C4359, 0x232E3F, 0x040C1A, 0x10A5DF, 0x2091E9 }),
		hardcoded({ 0x2C4359, 0x232E3F, 0x040C1A, 0x10A5DF, 0x2091E9 }),
		hardcoded({ 0x1C4843, 0x1A2E37, 0x040C1A, 0x3ACA49, 0x007D9E }),
		hardcoded({ 0x5D2E16, 0x371B1A, 0x040C1A, 0xE27519, 0xDD4819 }),
	};
}

struct GiftForCraft {
	std::shared_ptr<Data::UniqueGift> unique;
	Data::SavedStarGiftId manageId;

	[[nodiscard]] QString slugId() const {
		return unique ? unique->slug : QString();
	}

	explicit operator bool() const {
		return unique != nullptr;
	}
	friend inline bool operator==(
		const GiftForCraft &,
		const GiftForCraft &) = default;
};

struct CraftingView {
	object_ptr<RpWidget> widget;
	rpl::producer<int> editRequests;
	rpl::producer<int> removeRequests;
	Fn<void(std::shared_ptr<CraftState>)> grabForAnimation;
};

void ShowGiftCraftBox(
	not_null<Window::SessionController*> controller,
	std::vector<GiftForCraft> gifts,
	bool autoStartCraft);

[[nodiscard]] QString FormatPercent(int permille) {
	const auto rounded = (permille + 5) / 10;
	return QString::number(rounded) + '%';
}

[[nodiscard]] not_null<RpWidget*> MakeRadialPercent(
		not_null<RpWidget*> parent,
		const style::CraftRadialPercent &st,
		rpl::producer<int> permille,
		Fn<QString(int)> format = FormatPercent) {
	auto raw = CreateChild<RpWidget>(parent);

	struct State {
		State(const style::CraftRadialPercent &st, Fn<void()> callback)
		: numbers(st.font, std::move(callback)) {
			numbers.setDisabledMonospace(true);
		}

		Animations::Simple animation;
		NumbersAnimation numbers;
		int permille = -1;
	};
	const auto state = raw->lifetime().make_state<State>(
		st,
		[=] { raw->update(); });

	std::move(permille) | rpl::on_next([=](int value) {
		if (state->permille == value) {
			return;
		}
		state->animation.start([=] {
			raw->update();
		}, state->permille, value, st::slideWrapDuration);
		state->permille = value;
		state->numbers.setText(format(value), value);
	}, raw->lifetime());
	state->animation.stop();
	state->numbers.finishAnimating();

	raw->show();
	raw->setAttribute(Qt::WA_TransparentForMouseEvents);
	raw->paintOn([=, &st](QPainter &p) {
		static constexpr auto kArcSkip = arc::kFullLength / 4;
		static constexpr auto kArcStart = -(arc::kHalfLength - kArcSkip) / 2;
		static constexpr auto kArcLength = arc::kFullLength - kArcSkip;

		const auto paint = [&](QColor color, float64 permille) {
			p.setPen(QPen(color, st.stroke, Qt::SolidLine, Qt::RoundCap));
			p.setBrush(Qt::NoBrush);
			const auto part = kArcLength * (permille / 1000.);
			const auto length = int(base::SafeRound(part));
			const auto inner = raw->rect().marginsRemoved(
				{ st.stroke, st.stroke, st.stroke, st.stroke });
			p.drawArc(inner, kArcStart + kArcLength - length, length);
		};

		auto hq = PainterHighQualityEnabler(p);

		auto inactive = QColor(255, 255, 255, 64);
		paint(inactive, 1000.);
		paint(st::white->c, state->animation.value(state->permille));

		state->numbers.paint(
			p,
			(raw->width() - state->numbers.countWidth()) / 2,
			raw->height() - st.font->height,
			raw->width());
	});

	return raw;
}

AbstractButton *MakeCornerButton(
		not_null<RpWidget*> parent,
		not_null<GiftButton*> button,
		object_ptr<RpWidget> content,
		style::align align,
		const GiftForCraft &gift,
		rpl::producer<QColor> edgeColor) {
	Expects(content != nullptr);

	const auto result = CreateChild<AbstractButton>(parent);
	result->show();

	const auto inner = content.release();
	inner->setParent(result);
	inner->show();
	inner->sizeValue() | rpl::on_next([=](QSize size) {
		result->resize(size);
	}, result->lifetime());
	inner->move(0, 0);

	rpl::combine(
		button->geometryValue(),
		result->sizeValue()
	) | rpl::on_next([=](QRect geometry, QSize size) {
		const auto extend = st::defaultDropdownMenu.wrap.shadow.extend;
		geometry = geometry.marginsRemoved(extend);
		const auto out = QPoint(size.width(), size.height()) / 3;
		const auto left = (align == style::al_left)
			? (geometry.x() - out.x())
			: (geometry.x() + geometry.width() - size.width() + out.x());
		const auto top = geometry.y() - out.y();
		result->move(left, top);
	}, result->lifetime());

	struct State {
		rpl::variable<QColor> edgeColor;
		QColor buttonEdgeColor;
	};
	const auto state = result->lifetime().make_state<State>();
	state->edgeColor = std::move(edgeColor);
	state->buttonEdgeColor = gift.unique->backdrop.edgeColor;
	result->paintOn([=](QPainter &p) {
		const auto right = result->width();
		const auto bottom = result->height();
		const auto add = QPoint(right, bottom) / 3;
		const auto radius = bottom / 2.;
		auto gradient = QLinearGradient(
			(align == style::al_left) ? -add.x() : (right + add.x()),
			-add.y(),
			(align == style::al_left) ? (right + add.x()) : -add.x(),
			bottom + add.y());
		gradient.setColorAt(0, state->edgeColor.current());
		gradient.setColorAt(1, state->buttonEdgeColor);
		auto hq = PainterHighQualityEnabler(p);
		p.setPen(Qt::NoPen);
		p.setBrush(gradient);
		p.drawRoundedRect(result->rect(), radius, radius);
	});

	return result;
}

AbstractButton *MakePercentButton(
		not_null<RpWidget*> parent,
		not_null<GiftButton*> button,
		const GiftForCraft &gift,
		rpl::producer<QColor> edgeColor) {
	auto label = object_ptr<FlatLabel>(
		parent,
		FormatPercent(gift.unique->craftChancePermille),
		st::craftPercentLabel);
	label->setTextColorOverride(st::white->c);
	const auto result = MakeCornerButton(
		parent,
		button,
		std::move(label),
		style::al_left,
		gift,
		std::move(edgeColor));
	result->setAttribute(Qt::WA_TransparentForMouseEvents);
	return result;
}

AbstractButton *MakeRemoveButton(
		not_null<RpWidget*> parent,
		not_null<GiftButton*> button,
		int size,
		const GiftForCraft &gift,
		Fn<void()> onClick,
		rpl::producer<QColor> edgeColor,
		const style::icon &icon) {
	auto remove = object_ptr<RpWidget>(parent);
	const auto add = (size - icon.width()) / 2;
	remove->resize(icon.size() + QSize(add, add) * 2);
	remove->paintOn([=, &icon](QPainter &p) {
		icon.paint(p, add, add, add * 2 + icon.width(), st::white->c);
	});
	remove->setAttribute(Qt::WA_TransparentForMouseEvents);
	const auto result = MakeCornerButton(
		parent,
		button,
		std::move(remove),
		style::al_right,
		gift,
		std::move(edgeColor));
	result->setClickedCallback(std::move(onClick));
	return result;
}

[[nodiscard]] CraftingView MakeCraftingView(
		not_null<RpWidget*> parent,
		not_null<Main::Session*> session,
		rpl::producer<std::vector<GiftForCraft>> chosen,
		rpl::producer<QColor> edgeColor) {
	const auto width = st::boxWideWidth;

	const auto buttonPadding = st::craftPreviewPadding;
	const auto buttonSize = st::giftBoxGiftTiny;
	const auto height = 2
		* (buttonPadding.top() + buttonSize + buttonPadding.bottom());

	auto widget = object_ptr<FixedHeightWidget>(parent, height);
	const auto raw = widget.data();

	struct Entry {
		GiftForCraft gift;
		GiftButton *button = nullptr;
		AbstractButton *add = nullptr;
		AbstractButton *percent = nullptr;
		AbstractButton *remove = nullptr;
	};
	struct State {
		explicit State(not_null<Main::Session*> session)
		: delegate(session, GiftButtonMode::CraftPreview) {
		}

		Delegate delegate;
		std::array<Entry, 4> entries;
		Fn<void(int)> refreshButton;
		rpl::event_stream<int> editRequests;
		rpl::event_stream<int> removeRequests;
		rpl::variable<int> chancePermille;
		rpl::variable<QColor> edgeColor;
		RpWidget *forgeRadial = nullptr;
	};
	const auto state = parent->lifetime().make_state<State>(session);
	state->edgeColor = std::move(edgeColor);

	state->refreshButton = [=](int index) {
		Expects(index >= 0 && index < state->entries.size());

		auto &entry = state->entries[index];
		const auto single = state->delegate.buttonSize();
		const auto geometry = QRect(
			((index % 2)
				? (width - buttonPadding.left() - single.width())
				: buttonPadding.left()),
			((index < 2)
				? buttonPadding.top()
				: (height - buttonPadding.top() - single.height())),
			single.width(),
			single.height());
		delete base::take(entry.add);
		delete base::take(entry.button);
		delete base::take(entry.percent);
		delete base::take(entry.remove);

		if (entry.gift) {
			entry.button = CreateChild<GiftButton>(raw, &state->delegate);
			entry.button->setDescriptor(GiftTypeStars{
				.info = {
					.id = entry.gift.unique->initialGiftId,
					.unique = entry.gift.unique,
					.document = entry.gift.unique->model.document,
				},
			}, GiftButton::Mode::CraftPreview);
			entry.button->show();
			entry.button->setClickedCallback([=] {
				state->editRequests.fire_copy(index);
			});
			entry.button->setGeometry(
				geometry,
				state->delegate.buttonExtend());

			entry.percent = MakePercentButton(
				raw,
				entry.button,
				entry.gift,
				state->edgeColor.value());
		} else {
			entry.add = CreateChild<AbstractButton>(raw);
			entry.add->show();
			entry.add->paintOn([=](QPainter &p) {
				auto hq = PainterHighQualityEnabler(p);
				const auto radius = st::boxRadius;
				p.setPen(Qt::NoPen);
				p.setBrush(ForgeBgOverlay());

				const auto rect = QRect(QPoint(), geometry.size());
				p.drawRoundedRect(rect, radius, radius);

				const auto &icon = st::craftAddIcon;
				icon.paintInCenter(p, rect, st::white->c);
			});
			entry.add->setClickedCallback([=] {
				state->editRequests.fire_copy(index);
			});
			entry.add->setGeometry(geometry);
		}

		const auto count = 4 - ranges::count(
			state->entries,
			nullptr,
			&Entry::button);
		const auto canRemove = (count > 1);
		const auto secondHasAddress = state->entries[1].gift
			&& !state->entries[1].gift.unique->giftAddress.isEmpty();
		for (auto i = 0; i != 4; ++i) {
			auto &entry = state->entries[i];
			if (entry.button) {
				delete base::take(entry.remove);
				if (canRemove) {
					const auto needReplace = (i == 0) && secondHasAddress;
					const auto callback = [=] {
						if (needReplace) {
							state->editRequests.fire_copy(0);
						} else {
							state->removeRequests.fire_copy(i);
						}
					};
					const auto &icon = needReplace
						? st::craftReplaceIcon
						: st::stickerPanDeleteIconFg;
					entry.remove = MakeRemoveButton(
						raw,
						entry.button,
						entry.percent->height(),
						entry.gift,
						callback,
						state->edgeColor.value(),
						icon);
				}
			}
		}
	};

	std::move(
		chosen
	) | rpl::on_next([=](const std::vector<GiftForCraft> &gifts) {
		auto chance = 0;
		for (auto i = 0; i != 4; ++i) {
			auto &entry = state->entries[i];
			const auto gift = (i < gifts.size()) ? gifts[i] : GiftForCraft();
			chance += gift.unique ? gift.unique->craftChancePermille : 0;
			if (entry.gift == gift && (entry.button || entry.add)) {
				continue;
			}
			entry.gift = gift;
			state->refreshButton(i);
		}
		state->chancePermille = chance;
	}, raw->lifetime());

	const auto center = [&] {
		const auto buttonPadding = st::craftPreviewPadding;
		const auto buttonSize = st::giftBoxGiftTiny;
		const auto left = buttonPadding.left()
			+ buttonSize
			+ buttonPadding.right();
		const auto center = (width - 2 * left);
		const auto top = (height - center) / 2;
		return QRect(left, top, center, center);
	}();
	raw->paintOn([=](QPainter &p) {
		auto hq = PainterHighQualityEnabler(p);

		const auto radius = st::boxRadius;

		p.setPen(Qt::NoPen);
		p.setBrush(ForgeBgOverlay());

		p.drawRoundedRect(center, radius, radius);

		st::craftForge.paintInCenter(p, center, st::white->c);
	});

	state->forgeRadial = MakeRadialPercent(
		raw,
		st::craftForgePercent,
		state->chancePermille.value());
	state->forgeRadial->setGeometry(center.marginsRemoved({
		st::craftForgePadding,
		st::craftForgePadding,
		st::craftForgePadding,
		st::craftForgePadding,
	}));

	auto grabForAnimation = [=](std::shared_ptr<CraftState> craftState) {
		craftState->forgeRect = center;
		craftState->forgePercent = GrabWidgetToImage(state->forgeRadial);

		auto giftsTotal = 0;
		auto lostIndex = 0;
		for (auto i = 0; i != 4; ++i) {
			auto &entry = state->entries[i];
			auto &corner = craftState->corners[i];

			if (entry.button) {
				corner.originalRect = entry.button->geometry();
				if (entry.percent) {
					corner.percentBadge = GrabWidgetToImage(entry.percent);
				}
				if (entry.remove) {
					corner.removeButton = GrabWidgetToImage(entry.remove);
				}
				if (lostIndex < craftState->lostGifts.size()) {
					craftState->lostGifts[lostIndex].cornerIndex = i;
					++lostIndex;
				}
				corner.giftButton.reset(entry.button);
				entry.button->setParent(parent);
				base::take(entry.button)->hide();

				++giftsTotal;
			} else if (entry.add) {
				corner.addButton = GrabWidgetToImage(entry.add);
				corner.originalRect = entry.add->geometry();
			}
		}

		const auto failedCount = MakeRadialPercent(
			raw,
			st::craftForgePercent,
			rpl::single(0),
			[=](int) { return QString::number(giftsTotal); });
		failedCount->setGeometry(state->forgeRadial->geometry());
		craftState->lostRadial = GrabWidgetToImage(failedCount);
		delete failedCount;

		const auto overlayBg = craftState->forgeBgOverlay = ForgeBgOverlay();
		const auto backdrop = CraftBackdrops()[giftsTotal - 1].backdrop;
		craftState->forgeBg1 = anim::color(
			backdrop.centerColor,
			QColor(overlayBg.red(), overlayBg.green(), overlayBg.blue()),
			overlayBg.alphaF());
		craftState->forgeBg2 = anim::color(
			backdrop.edgeColor,
			QColor(overlayBg.red(), overlayBg.green(), overlayBg.blue()),
			overlayBg.alphaF());
		const auto overlayFail = ForgeFailOverlay();
		craftState->forgeFail = anim::color(
			CraftBackdrops().back().backdrop.centerColor,
			QColor(overlayFail.red(), overlayFail.green(), overlayFail.blue()),
			overlayFail.alphaF());
		for (auto i = 0; i != 6; ++i) {
			craftState->forgeSides[i] = craftState->prepareEmptySide(i);
		}
	};

	return {
		.widget = std::move(widget),
		.editRequests = state->editRequests.events(),
		.removeRequests = state->removeRequests.events(),
		.grabForAnimation = std::move(grabForAnimation),
	};
}

void AddCraftGiftsList(
		not_null<Window::SessionController*> window,
		not_null<VerticalLayout*> container,
		Data::CraftGiftsDescriptor descriptor,
		const std::vector<GiftForCraft> &selected,
		Fn<void(std::shared_ptr<Data::UniqueGift>)> chosen) {
	struct State {
		rpl::event_stream<> updated;
		Data::CraftGiftsDescriptor data;
		rpl::variable<bool> empty = true;
		rpl::lifetime loading;
	};
	const auto state = container->lifetime().make_state<State>();
	state->data = std::move(descriptor);

	using Descriptor = Info::PeerGifts::GiftDescriptor;
	using StarGift = Info::PeerGifts::GiftTypeStars;
	auto handler = crl::guard(container, [=](Descriptor descriptor) {
		Expects(v::is<StarGift>(descriptor));

		const auto unique = v::get<StarGift>(descriptor).info.unique;
		chosen(unique);
	});

	auto gifts = rpl::single(
		rpl::empty
	) | rpl::then(state->updated.events()) | rpl::map([=] {
		auto result = GiftsDescriptor();
		const auto selfId = window->session().userPeerId();
		for (const auto &gift : state->data.list) {
			result.list.push_back(Info::PeerGifts::GiftTypeStars{
				.info = gift.info,
				.resale = true,
				.mine = (gift.info.unique->ownerId == selfId),
				});
		}
		state->empty = result.list.empty();
		return result;
	});
	const auto peer = window->session().user();
	const auto loadMore = [=] {
		if (!state->data.offset.isEmpty() && !state->loading) {
			state->loading = Data::CraftGiftsSlice(
				&peer->session(),
				state->data.giftId,
				state->data.offset
			) | rpl::on_next([=](Data::CraftGiftsDescriptor &&slice) {
				state->loading.destroy();
				state->data.offset = slice.list.empty()
					? QString()
					: slice.offset;
				state->data.list.insert(
					end(state->data.list),
					std::make_move_iterator(begin(slice.list)),
					std::make_move_iterator(end(slice.list)));
				state->updated.fire({});
			});
		}
	};
	container->add(MakeGiftsList({
		.window = window,
		.mode = GiftsListMode::Craft,
		.peer = peer,
		.gifts = std::move(gifts),
		.selected = (selected
			| ranges::views::transform(&GiftForCraft::unique)
			| ranges::to_vector),
		.loadMore = loadMore,
		.handler = handler,
	}));

	const auto skip = st::defaultSubsectionTitlePadding.top();
	const auto wrap = container->add(
		object_ptr<SlideWrap<FlatLabel>>(
			container,
			object_ptr<FlatLabel>(
				container,
				tr::lng_gift_craft_select_none(),
				st::craftYourListEmpty),
			(st::boxRowPadding + QMargins(0, 0, 0, skip))),
		style::al_top);
	state->empty.value() | rpl::on_next([=](bool empty) {
		// Scroll doesn't jump up if we show before rows are cleared,
		// and we hide after rows are added.
		if (empty) {
			wrap->show(anim::type::instant);
		} else {
			crl::on_main(wrap, [=] {
				if (!state->empty.current()) {
					wrap->hide(anim::type::instant);
				}
			});
		}
	}, wrap->lifetime());
	wrap->entity()->setTryMakeSimilarLines(true);
}

void ShowSelectGiftBox(
		not_null<Window::SessionController*> controller,
		uint64 giftId,
		Fn<void(GiftForCraft)> chosen,
		std::vector<GiftForCraft> selected,
		Fn<void()> boxClosed,
		bool firstSlot) {
	struct Entry {
		Data::SavedStarGift gift;
		GiftButton *button = nullptr;
	};
	struct State {
		std::vector<Entry> entries;

		Data::CraftGiftsDescriptor craft;
		Data::ResaleGiftsDescriptor resale;

		rpl::lifetime craftLifetime;
		rpl::lifetime resaleLifetime;
	};

	const auto session = &controller->session();
	const auto state = std::make_shared<State>();

	const auto make = [=](not_null<GenericBox*> box) {
		box->setTitle(tr::lng_gift_craft_select_title());
		box->setWidth(st::boxWideWidth);

		box->boxClosing() | rpl::on_next(boxClosed, box->lifetime());

		AddSubsectionTitle(
			box->verticalLayout(),
			tr::lng_gift_craft_select_your());

		const auto got = crl::guard(box, [=](
				std::shared_ptr<Data::UniqueGift> gift) {
			if (ShowCraftLaterError(box->uiShow(), gift)
				|| (firstSlot
					&& ShowCraftAddressError(box->uiShow(), gift))) {
				return;
			}
			chosen(GiftForCraft{ .unique = gift });
			box->closeBox();
		});

		AddCraftGiftsList(
			controller,
			box->verticalLayout(),
			state->craft,
			selected,
			got);

		if (const auto count = state->resale.count) {
			AddSubsectionTitle(
				box->verticalLayout(),
				tr::lng_gift_craft_select_market(
					lt_count,
					rpl::single(count * 1.)));

			AddResaleGiftsList(
				controller,
				session->user(),
				box->verticalLayout(),
				state->resale,
				nullptr,
				got,
				true);
		}

		box->addButton(tr::lng_box_ok(), [=] {
			box->closeBox();
		});
		box->setMaxHeight(st::boxWideWidth);
	};
	const auto show = crl::guard(controller, [=] {
		controller->show(Box(make));
	});

	state->craftLifetime = Data::CraftGiftsSlice(
		session,
		giftId
	) | rpl::on_next([=](Data::CraftGiftsDescriptor &&info) {
		state->craftLifetime.destroy();

		state->craft = std::move(info);
		if (state->resale.giftId) {
			show();
		}
	});

	state->resaleLifetime = Data::ResaleGiftsSlice(
		session,
		giftId,
		{ .forCraft = true }
	) | rpl::on_next([=](Data::ResaleGiftsDescriptor &&info) {
		state->resaleLifetime.destroy();

		state->resale = std::move(info);
		if (state->craft.giftId) {
			show();
		}
	});
}

[[nodiscard]] object_ptr<RpWidget> MakeRarityExpectancyPreview(
		not_null<RpWidget*> parent,
		not_null<Main::Session*> session,
		rpl::producer<std::vector<GiftForCraft>> gifts) {
	auto result = object_ptr<RpWidget>(parent);
	const auto raw = result.data();

	struct AttributeState {
		rpl::variable<int> permille;
		bool isBackdrop = false;
		QString name;

		Animations::Simple backdropAnimation;
		Data::UniqueGiftBackdrop wasBackdrop;
		Data::UniqueGiftBackdrop nowBackdrop;
		QImage wasFrame;
		QImage nowFrame;

		Animations::Simple patternAnimation;
		DocumentData *wasPattern = nullptr;
		DocumentData *nowPattern = nullptr;
		std::unique_ptr<Text::CustomEmoji> wasEmoji;
		std::unique_ptr<Text::CustomEmoji> nowEmoji;

		AbstractButton *button = nullptr;
		RpWidget *radial = nullptr;
	};

	struct State {
		std::array<AttributeState, 8> attrs;
		int count = 0;
		ImportantTooltip *tooltip = nullptr;
	};

	const auto state = raw->lifetime().make_state<State>();

	const auto single = st::craftAttributeSize;
	const auto skip = st::craftAttributeSkip;

	for (auto i = 0; i != 8; ++i) {
		auto &attr = state->attrs[i];
		const auto btn = CreateChild<AbstractButton>(raw);
		attr.button = btn;
		btn->resize(single, single);
		btn->hide();

		const auto idx = i;
		btn->paintOn([=](QPainter &p) {
			auto &a = state->attrs[idx];
			const auto sub = st::craftAttributePadding;
			const auto inner = QRect(0, 0, single, single).marginsRemoved(
				{ sub, sub, sub, sub });
			if (a.isBackdrop) {
				const auto progress = a.backdropAnimation.value(1.);
				if (progress < 1.) {
					p.drawImage(inner.topLeft(), a.wasFrame);
					p.setOpacity(progress);
				}
				if (a.nowFrame.isNull()) {
					const auto ratio = style::DevicePixelRatio();
					a.nowFrame = QImage(
						inner.size() * ratio,
						QImage::Format_ARGB32_Premultiplied);
					a.nowFrame.fill(Qt::transparent);
					a.nowFrame.setDevicePixelRatio(ratio);
					auto q = QPainter(&a.nowFrame);
					auto hq = PainterHighQualityEnabler(q);
					auto gradient = QLinearGradient(
						QPointF(inner.width(), 0),
						QPointF(0, inner.height()));
					gradient.setColorAt(0., a.nowBackdrop.centerColor);
					gradient.setColorAt(1., a.nowBackdrop.edgeColor);
					q.setPen(Qt::NoPen);
					q.setBrush(gradient);
					q.drawEllipse(
						0,
						0,
						inner.width(),
						inner.height());

					q.setCompositionMode(
						QPainter::CompositionMode_Source);
					q.setBrush(Qt::transparent);
					const auto max = u"100%"_q;
					const auto &font = st::craftAttributePercent.font;
					const auto tw = font->width(max);
					q.drawRoundedRect(
						(inner.width() - tw) / 2,
						inner.height() + sub - font->height,
						tw,
						font->height,
						font->height / 2.,
						font->height / 2.);
				}
				p.drawImage(inner.topLeft(), a.nowFrame);
				p.setOpacity(1.);
			} else {
				const auto center = QRect(0, 0, single, single).center();
				const auto emojiShift
					= (single - Emoji::GetCustomSizeNormal()) / 2;
				const auto pos = QPoint(emojiShift, emojiShift);
				const auto progress = a.patternAnimation.value(1.);
				if (progress < 1.) {
					p.translate(center);
					p.save();
					p.setOpacity(1. - progress);
					p.scale(1. - progress, 1. - progress);
					p.translate(-center);
					a.wasEmoji->paint(p, {
						.textColor = st::white->c,
						.position = pos,
					});
					p.restore();
					p.scale(progress, progress);
					p.setOpacity(progress);
					p.translate(-center);
				}
				if (!a.nowEmoji) {
					a.nowEmoji = session->data().customEmojiManager().create(
						a.nowPattern,
						[=] { btn->update(); });
				}
				a.nowEmoji->paint(p, {
					.textColor = st::white->c,
					.position = pos,
				});
			}
		});

		attr.radial = MakeRadialPercent(
			btn,
			st::craftAttributePercent,
			attr.permille.value());
		attr.radial->setGeometry(0, 0, single, single);

		btn->setClickedCallback([=] {
			auto &a = state->attrs[idx];
			if (state->tooltip) {
				state->tooltip->toggleAnimated(false);
			}

			auto text = a.isBackdrop
				? tr::lng_gift_craft_chance_backdrop(
					tr::now,
					lt_percent,
					TextWithEntities{ FormatPercent(a.permille.current()) },
					lt_name,
					TextWithEntities{ a.name },
					Ui::Text::RichLangValue)
				: tr::lng_gift_craft_chance_symbol(
					tr::now,
					lt_percent,
					TextWithEntities{ FormatPercent(a.permille.current()) },
					lt_name,
					TextWithEntities{ a.name },
					Ui::Text::RichLangValue);
			const auto tooltip = CreateChild<ImportantTooltip>(
				parent,
				MakeNiceTooltipLabel(
					parent,
					rpl::single(std::move(text)),
					st::boxWideWidth / 2,
					st::defaultImportantTooltipLabel,
					st::defaultPopupMenu),
				st::defaultImportantTooltip);
			tooltip->toggleFast(false);

			base::install_event_filter(tooltip, qApp, [=](not_null<QEvent*> e) {
				if (e->type() == QEvent::MouseButtonPress) {
					tooltip->toggleAnimated(false);
				}
				return base::EventFilterResult::Continue;
			});

			const auto geometry = MapFrom(parent, btn, btn->rect());
			const auto countPosition = [=](QSize size) {
				const auto left = geometry.x()
					+ (geometry.width() - size.width()) / 2;
				const auto right = parent->width()
					- st::normalFont->spacew;
				return QPoint(
					std::max(std::min(left, right - size.width()), 0),
					geometry.y()
						- size.height()
						- st::normalFont->descent);
			};
			tooltip->pointAt(geometry, RectPart::Top, countPosition);
			tooltip->toggleAnimated(true);

			state->tooltip = tooltip;
			tooltip->shownValue() | rpl::filter(
				!rpl::mappers::_1
			) | rpl::on_next([=] {
				crl::on_main(tooltip, [=] {
					if (tooltip->isHidden()) {
						if (state->tooltip == tooltip) {
							state->tooltip = nullptr;
						}
						delete tooltip;
					}
				});
			}, tooltip->lifetime());

			base::timer_once(
				3000
			) | rpl::on_next([=] {
				tooltip->toggleAnimated(false);
			}, tooltip->lifetime());
		});
	}

	const auto relayout = [=](int count) {
		const auto twoRows = (count > 5);
		const auto row1 = twoRows ? ((count + 1) / 2) : count;
		const auto row2 = twoRows ? (count - row1) : 0;
		const auto rowWidth = [&](int n) {
			return n * single + (n - 1) * skip;
		};
		const auto w1 = rowWidth(row1);
		const auto w2 = row2 ? rowWidth(row2) : 0;
		const auto naturalWidth = std::max(w1, w2);
		const auto totalHeight = twoRows
			? (2 * single + st::craftAttributeRowSkip)
			: single;

		for (auto i = 0; i != 8; ++i) {
			auto &attr = state->attrs[i];
			if (i < count) {
				const auto inRow1 = (i < row1);
				const auto rowItems = inRow1 ? row1 : row2;
				const auto rowW = rowWidth(rowItems);
				const auto indexInRow = inRow1 ? i : (i - row1);
				const auto x = (naturalWidth - rowW) / 2
					+ indexInRow * (single + skip);
				const auto y = inRow1
					? 0
					: (single + st::craftAttributeRowSkip);
				attr.button->setGeometry(x, y, single, single);
				attr.button->show();
			} else {
				attr.button->hide();
			}
		}

		raw->setNaturalWidth(naturalWidth);
		raw->resize(naturalWidth, totalHeight);
	};

	const auto permilles = session->appConfig().craftAttributePermilles();
	const auto computePermille = [=](int total, int count) {
		Expects(total > 0);
		Expects(count > 0);

		const auto &list = (total <= permilles.size())
			? permilles[total - 1]
			: std::vector<int>();
		return (count <= list.size()) ? list[count - 1] : 1000;
	};

	std::move(
		gifts
	) | rpl::on_next([=](const std::vector<GiftForCraft> &list) {
		struct BackdropEntry {
			Data::UniqueGiftBackdrop fields;
			QString name;
			int count = 0;
		};
		struct PatternEntry {
			not_null<DocumentData*> document;
			QString name;
			int count = 0;
		};
		auto backdrops = std::vector<BackdropEntry>();
		auto patterns = std::vector<PatternEntry>();
		for (const auto &gift : list) {
			const auto &backdrop = gift.unique->backdrop;
			const auto &pattern = gift.unique->pattern;
			const auto proj1 = &BackdropEntry::fields;
			const auto i = ranges::find(backdrops, backdrop, proj1);
			if (i != end(backdrops)) {
				++i->count;
			} else {
				backdrops.push_back({ backdrop, backdrop.name, 1 });
			}
			const auto proj2 = &PatternEntry::document;
			const auto j = ranges::find(
				patterns,
				pattern.document,
				proj2);
			if (j != end(patterns)) {
				++j->count;
			} else {
				patterns.push_back({ pattern.document, pattern.name, 1 });
			}
		}
		ranges::sort(backdrops, ranges::greater(), &BackdropEntry::count);
		ranges::sort(patterns, ranges::greater(), &PatternEntry::count);

		const auto total = int(list.size());
		auto slotIndex = 0;
		for (const auto &b : backdrops) {
			if (slotIndex >= 8) break;
			auto &a = state->attrs[slotIndex];
			a.isBackdrop = true;
			a.name = b.name;
			a.permille = computePermille(total, b.count);
			if (a.nowBackdrop != b.fields) {
				if (!a.nowFrame.isNull()) {
					a.wasBackdrop = a.nowBackdrop;
					a.wasFrame = base::take(a.nowFrame);
					a.backdropAnimation.stop();
					a.backdropAnimation.start([=, btn = a.button] {
						btn->update();
					}, 0., 1., st::fadeWrapDuration);
				}
				a.nowBackdrop = b.fields;
				a.nowFrame = QImage();
			}
			++slotIndex;
		}
		for (const auto &pt : patterns) {
			if (slotIndex >= 8) break;
			auto &a = state->attrs[slotIndex];
			a.isBackdrop = false;
			a.name = pt.name;
			a.permille = computePermille(total, pt.count);
			if (a.nowPattern != pt.document) {
				if (a.nowEmoji) {
					a.wasPattern = a.nowPattern;
					a.wasEmoji = base::take(a.nowEmoji);
					a.patternAnimation.stop();
					a.patternAnimation.start([=, btn = a.button] {
						btn->update();
					}, 0., 1., st::fadeWrapDuration);
				}
				a.nowPattern = pt.document;
				a.nowEmoji = nullptr;
			}
			++slotIndex;
		}

		const auto newCount = slotIndex;
		if (state->count != newCount) {
			state->count = newCount;
			relayout(newCount);
		}
	}, raw->lifetime());

	return result;
}

void Craft(
		not_null<GenericBox*> box,
		not_null<Window::SessionController*> controller,
		std::shared_ptr<CraftState> state,
		const std::vector<GiftForCraft> &gifts) {
	auto show = controller->uiShow();
	auto startRequest = [=](Fn<void(CraftResult)> done) {
#if 0
		constexpr auto kDelays = std::array<crl::time, 7>{
			100, 200, 300, 400, 500, 1000, 2000
		};
		const auto delay = kDelays[base::RandomIndex(kDelays.size())];
		const auto giftsCopy = gifts;

		base::call_delayed(delay, box, [=] {
			//static auto testing = 0;
			const auto shouldSucceed = false;// (((++testing) / 4) % 2 == 0);
			const auto count = int(giftsCopy.size());
			if (shouldSucceed && count > 0) {
				const auto &chosen = giftsCopy[base::RandomIndex(count)];
				auto info = Data::StarGift{
					.id = chosen.unique->initialGiftId,
					.unique = chosen.unique,
					.document = chosen.unique->model.document,
				};
				auto result = std::make_shared<Data::GiftUpgradeResult>(
					Data::GiftUpgradeResult{
						.info = std::move(info),
						.manageId = chosen.manageId,
					});
				done(std::move(result));
			} else {
				done(nullptr);
			}
		});
#endif

		auto inputs = QVector<MTPInputSavedStarGift>();
		for (const auto &gift : gifts) {
			inputs.push_back(
				Api::InputSavedStarGiftId(gift.manageId, gift.unique));
		}
		const auto weak = base::make_weak(controller);
		const auto session = &controller->session();
		session->api().request(MTPpayments_CraftStarGift(
			MTP_vector<MTPInputSavedStarGift>(inputs)
		)).done([=](const MTPUpdates &result) {
			session->api().applyUpdates(result);
			session->data().nextForUpgradeGiftInvalidate(session->user());
			const auto gift = FindUniqueGift(session, result);
			const auto slug = gift ? gift->info.unique->slug : QString();
			for (const auto &input : gifts) {
				const auto action = (slug == input.unique->slug)
					? Data::GiftUpdate::Action::Upgraded
					: Data::GiftUpdate::Action::Delete;
				controller->session().data().notifyGiftUpdate({
					.id = input.manageId,
					.slug = input.unique->slug,
					.action = action,
				});
			}
			done(gift);
		}).fail([=](const MTP::Error &error) {
			const auto type = error.type();
			const auto waitPrefix = u"STARGIFT_CRAFT_TOO_EARLY_"_q;
			if (type.startsWith(waitPrefix)) {
				done(CraftResultWait{
					.seconds = type.mid(waitPrefix.size()).toInt(),
				});
			} else {
				done(CraftResultError{ type });
			}
		}).send();
	};
	const auto requested = std::make_shared<bool>();
	const auto giftId = gifts.front().unique->initialGiftId;
	auto retryWithNewGift = [=](Fn<void()> closeCurrent) {
		if (*requested) {
			return;
		}
		*requested = true;
		ShowSelectGiftBox(controller, giftId, [=](GiftForCraft chosen) {
			ShowGiftCraftBox(controller, { chosen }, false);
			closeCurrent();
		}, {}, [=] { *requested = false; }, true);
	};
	StartCraftAnimation(
		box,
		std::move(show),
		std::move(state),
		std::move(startRequest),
		std::move(retryWithNewGift));
}

void AddPreviewNewModels(
		not_null<VerticalLayout*> container,
		std::shared_ptr<Main::SessionShow> show,
		const QString &giftName,
		Data::UniqueGiftAttributes attributes,
		rpl::producer<bool> visible) {
	auto exceptional = std::vector<Data::UniqueGiftModel>();
	for (const auto &model : attributes.models) {
		if (Data::UniqueGiftAttributeHasSpecialRarity(model)) {
			exceptional.push_back(model);
		}
	}
	attributes.models = std::move(exceptional);

	auto emoji = TextWithEntities();
	const auto indices = RandomIndicesSubset(
		int(attributes.models.size()),
		std::min(3, int(attributes.models.size())));
	for (const auto index : indices) {
		emoji.append(Data::SingleCustomEmoji(
			attributes.models[index].document));
	}

	auto badge = object_ptr<AbstractButton>(container);
	const auto badgeRaw = badge.data();

	const auto label = CreateChild<FlatLabel>(
		badgeRaw,
		tr::lng_gift_craft_view_all(
			lt_emoji,
			rpl::single(emoji),
			lt_arrow,
			rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)),
			Text::WithEntities),
		st::uniqueGiftResalePrice,
		st::defaultPopupMenu,
		Core::TextContext({ .session = &show->session() }));
	label->setTextColorOverride(st::white->c);

	label->sizeValue() | rpl::on_next([=](QSize size) {
		const auto padding = st::craftPreviewAllPadding;
		badgeRaw->setNaturalWidth(
			padding.left() + size.width() + padding.right());
		badgeRaw->resize(
			badgeRaw->naturalWidth(),
			padding.top() + size.height() + padding.bottom());
	}, label->lifetime());

	badgeRaw->widthValue() | rpl::on_next([=](int width) {
		const auto padding = st::craftPreviewAllPadding;
		label->move(padding.left(), padding.top());
	}, badgeRaw->lifetime());

	badgeRaw->paintOn([=](QPainter &p) {
		auto hq = PainterHighQualityEnabler(p);
		const auto rect = badgeRaw->rect();
		const auto radius = rect.height() / 2.;
		p.setPen(Qt::NoPen);
		p.setBrush(ForgeBgOverlay());
		p.drawRoundedRect(rect, radius, radius);
	});

	label->setAttribute(Qt::WA_TransparentForMouseEvents);
	badgeRaw->setClickedCallback([=] {
		auto previewAttrs = attributes;
		previewAttrs.models.erase(
			ranges::remove_if(previewAttrs.models, [](const auto &m) {
				return !Data::UniqueGiftAttributeHasSpecialRarity(m);
			}),
			end(previewAttrs.models));
		show->show(Box(
			StarGiftPreviewBox,
			giftName,
			previewAttrs,
			Data::GiftAttributeIdType::Model,
			nullptr));
	});

	const auto wrap = container->add(
		object_ptr<SlideWrap<AbstractButton>>(
			container,
			std::move(badge),
			st::craftPreviewAllMargin),
		style::al_top);
	std::move(visible) | rpl::on_next([=](bool shown) {
		wrap->toggle(shown, anim::type::instant);
	}, wrap->lifetime());
}

void MakeCraftContent(
		not_null<GenericBox*> box,
		not_null<Window::SessionController*> controller,
		std::vector<GiftForCraft> gifts,
		bool autoStartCraft) {
	Expects(!gifts.empty());

	struct State {
		std::shared_ptr<CraftState> craftState;
		GradientButton *button = nullptr;

		FlatLabel *title = nullptr;
		FlatLabel *about = nullptr;
		RpWidget *attributes = nullptr;
		RpWidget *craftingView = nullptr;
		Fn<void(std::shared_ptr<CraftState>)> grabCraftingView;

		rpl::variable<std::vector<GiftForCraft>> chosen;
		rpl::variable<QString> name;
		rpl::variable<int> successPercentPermille;
		rpl::variable<QString> successPercentText;

		int requestingIndex = -1;
		bool crafting = false;
	};
	const auto session = &controller->session();
	const auto giftId = gifts.front().unique->initialGiftId;
	const auto state = box->lifetime().make_state<State>();

	const auto auctions = &controller->session().giftAuctions();
	auto attributes = auctions->attributes(giftId).value_or(
		Data::UniqueGiftAttributes());

	state->craftState = std::make_shared<CraftState>();
	state->craftState->session = session;
	state->craftState->coversAnimate = true;

	{
		auto backdrops = CraftBackdrops();
		const auto emoji = Text::IconEmoji(&st::craftPattern);
		const auto data = emoji.entities.front().data();
		for (auto i = 0; i != backdrops.size(); ++i) {
			auto &cover = state->craftState->covers[i];
			cover.backdrop.colors = backdrops[i].backdrop;
			cover.pattern.emoji = Text::TryMakeSimpleEmoji(data);
			cover.button1 = backdrops[i].button1;
			cover.button2 = backdrops[i].button2;
		}
	}

	const auto giftName = gifts.front().unique->title;
	state->chosen = std::move(gifts);
	for (auto i = 0; i != int(state->chosen.current().size()); ++i) {
		state->craftState->covers[i].shown = true;
	}

	state->name = state->chosen.value(
	) | rpl::map([=](const std::vector<GiftForCraft> &gifts) {
		return Data::UniqueGiftName(*gifts.front().unique);
	});
	state->successPercentPermille = state->chosen.value(
	) | rpl::map([=](const std::vector<GiftForCraft> &gifts) {
		auto result = 0;
		for (const auto &entry : gifts) {
			if (const auto gift = entry.unique.get()) {
				result += gift->craftChancePermille;
			}
		}
		return result;
	});
	state->successPercentText = state->successPercentPermille.value(
	) | rpl::map(FormatPercent);

	const auto raw = box->verticalLayout();

	state->chosen.value(
	) | rpl::on_next([=](const std::vector<GiftForCraft> &gifts) {
		state->craftState->updateForGiftCount(int(gifts.size()), [=] {
			raw->update();
		});
	}, box->lifetime());

	const auto title = state->title = raw->add(
		object_ptr<Ui::FlatLabel>(
			box,
			tr::lng_gift_craft_title(),
			st::uniqueGiftTitle),
		st::craftTitleMargin,
		style::al_top);
	title->setTextColorOverride(QColor(255, 255, 255));

	auto crafting = MakeCraftingView(
		raw,
		session,
		state->chosen.value(),
		state->craftState->edgeColor.value());
	const auto craftingHeight = crafting.widget->height();
	state->craftingView = crafting.widget.data();
	state->grabCraftingView = std::move(crafting.grabForAnimation);
	raw->add(std::move(crafting.widget));
	std::move(crafting.removeRequests) | rpl::on_next([=](int index) {
		auto chosen = state->chosen.current();
		if (index < chosen.size()) {
			chosen.erase(begin(chosen) + index);
			state->chosen = std::move(chosen);
		}
	}, raw->lifetime());

	std::move(
		crafting.editRequests
	) | rpl::on_next([=](int index) {
		const auto guard = base::make_weak(raw);
		if (state->requestingIndex >= 0) {
			state->requestingIndex = index;
			return;
		}
		state->requestingIndex = index;
		const auto callback = [=](GiftForCraft chosen) {
			auto copy = state->chosen.current();
			if (state->requestingIndex < copy.size()) {
				copy[state->requestingIndex] = chosen;
			} else {
				copy.push_back(chosen);
			}
			state->chosen = std::move(copy);
		};
		const auto first = (state->requestingIndex == 0);
		ShowSelectGiftBox(
			controller,
			giftId,
			crl::guard(raw, callback),
			state->chosen.current(),
			crl::guard(raw, [=] { state->requestingIndex = -1; }),
			first);
	}, raw->lifetime());

	auto fullName = state->chosen.value(
	) | rpl::map([=](const std::vector<GiftForCraft> &list) {
		const auto unique = list.front().unique.get();
		return Data::SingleCustomEmoji(
			unique->model.document
		).append(' ').append(tr::bold(Data::UniqueGiftName(*unique)));
	});
	auto aboutText = rpl::combine(
		tr::lng_gift_craft_about1(lt_gift, fullName, tr::rich),
		tr::lng_gift_craft_about2(tr::rich)
	) | rpl::map([=](TextWithEntities &&a, TextWithEntities &&b) {
		return a.append('\n').append('\n').append(b);
	});
	const auto about = state->about = raw->add(
		object_ptr<FlatLabel>(
			raw,
			std::move(aboutText),
			st::craftAbout,
			st::defaultPopupMenu,
			Core::TextContext({ .session = session })),
		st::craftAboutMargin,
		style::al_top);
	about->setTextColorOverride(st::white->c);
	about->setTryMakeSimilarLines(true);

	state->attributes = raw->add(
		MakeRarityExpectancyPreview(raw, session, state->chosen.value()),
		st::craftAttributesMargin,
		style::al_top);

	auto viewAllVisible = state->chosen.value(
	) | rpl::map([](const std::vector<GiftForCraft> &list) {
		auto backdropIds = base::flat_set<int>();
		auto patternPtrs = base::flat_set<DocumentData*>();
		for (const auto &gift : list) {
			backdropIds.emplace(gift.unique->backdrop.id);
			patternPtrs.emplace(gift.unique->pattern.document.get());
		}
		return int(backdropIds.size() + patternPtrs.size()) <= 5;
	});

	AddPreviewNewModels(
		raw,
		controller->uiShow(),
		giftName,
		std::move(attributes),
		std::move(viewAllVisible));

	raw->paintOn([=](QPainter &p) {
		const auto &cs = state->craftState;
		if (cs->craftingStarted) {
			return;
		}
		const auto wasButton1 = cs->button1;
		const auto wasButton2 = cs->button2;
		cs->paint(p, raw->size(), craftingHeight);
		if (cs->button1 != wasButton1 || cs->button2 != wasButton2) {
			state->button->setStops({
				{ 0., cs->button1 },
				{ 1., cs->button2 },
			});
		}
	});

	const auto button = state->button = raw->add(
		object_ptr<GradientButton>(raw, QGradientStops()),
		st::craftBoxButtonPadding);
	button->setFullRadius(true);
	button->startGlareAnimation();

	const auto startCrafting = [=] {
		if (state->crafting) {
			return;
		}
		const auto &gifts = state->chosen.current();
		if (!gifts.empty()
			&& ShowCraftAddressError(box->uiShow(), gifts.front().unique)) {
			return;
		}
		state->crafting = true;

		const auto &cs = state->craftState;
		cs->giftName = state->name.current();
		cs->successPermille = state->successPercentPermille.current();

		for (const auto &gift : gifts) {
			if (gift.unique) {
				cs->lostGifts.push_back({
					.number = u"#"_q + Lang::FormatCountDecimal(gift.unique->number),
				});
			}
		}

		if (state->grabCraftingView) {
			state->grabCraftingView(cs);
		}

		const auto craftingPos = state->craftingView->pos();
		cs->craftingTop = craftingPos.y();
		cs->craftingBottom = craftingPos.y() + state->craftingView->height();

		for (auto &corner : cs->corners) {
			corner.originalRect.translate(craftingPos);
		}
		cs->forgeRect.translate(craftingPos);
		cs->craftingAreaCenterY = cs->forgeRect.center().y();

		const auto aboutPos = state->about->pos();
		const auto buttonY = state->button->pos().y();
		const auto buttonBottom = buttonY + state->button->height();
		cs->originalButtonY = buttonY;
		const auto bottomRect = QRect(
			0,
			aboutPos.y(),
			raw->width(),
			buttonBottom - aboutPos.y());

		const auto ratio = style::DevicePixelRatio();
		auto bottomPart = QImage(
			bottomRect.size() * ratio,
			QImage::Format_ARGB32_Premultiplied);
		bottomPart.fill(Qt::transparent);
		bottomPart.setDevicePixelRatio(ratio);

		const auto renderWidget = [&](QWidget *widget) {
			const auto pos = widget->pos() - bottomRect.topLeft();
			widget->render(&bottomPart, pos, {}, QWidget::DrawChildren);
		};
		renderWidget(state->about);
		renderWidget(state->attributes);
		renderWidget(state->button);

		cs->bottomPart = std::move(bottomPart);
		cs->bottomPartY = aboutPos.y();
		cs->containerHeight = raw->height();

		Craft(
			box,
			controller,
			state->craftState,
			state->chosen.current());
	};
	button->setClickedCallback(startCrafting);

	SetButtonTwoLabels(
		button,
		st::giftBox.button.textTop,
		tr::lng_gift_craft_button(
			lt_gift,
			state->name.value() | rpl::map(tr::marked),
			tr::marked),
		tr::lng_gift_craft_button_chance(
			lt_percent,
			state->successPercentText.value() | rpl::map(tr::marked),
			tr::marked),
		st::resaleButtonTitle,
		st::resaleButtonSubtitle);

	raw->widthValue() | rpl::on_next([=](int width) {
		const auto padding = st::craftBoxButtonPadding;
		button->setNaturalWidth(width - padding.left() - padding.right());
		button->resize(button->naturalWidth(), st::giftBox.button.height);
	}, raw->lifetime());

	if (autoStartCraft) {
//		base::call_delayed(crl::time(1000), raw, startCrafting);
	}
}

void ShowGiftCraftBox(
		not_null<Window::SessionController*> controller,
		std::vector<GiftForCraft> gifts,
		bool autoStartCraft) {
	controller->show(Box([=](not_null<GenericBox*> box) {
		box->setStyle(st::giftCraftBox);
		box->setWidth(st::boxWideWidth);
		box->setNoContentMargin(true);
		MakeCraftContent(
			box,
			controller,
			gifts,
			autoStartCraft);
		AddUniqueCloseButton(box);

#if _DEBUG
		if (autoStartCraft) {
			static const auto full = gifts;
			box->boxClosing() | rpl::on_next([=] {
				base::call_delayed(1000, controller, [=] {
					auto copy = gifts;
					if (copy.size() > 1) {
						copy.pop_back();
					} else {
						copy = full;
					}
					ShowGiftCraftBox(controller, copy, true);
				});
			}, box->lifetime());
		}
#endif
	}));
}

} // namespace

void ShowGiftCraftInfoBox(
		not_null<Window::SessionController*> controller,
		std::shared_ptr<Data::UniqueGift> gift,
		Data::SavedStarGiftId savedId) {
	controller->show(Box([=](not_null<GenericBox*> box) {
		const auto container = box->verticalLayout();
		auto cover = tr::lng_gift_craft_info_title(
		) | rpl::map([=](const QString &title) {
			auto result = UniqueGiftCover{ *gift };
			result.values.title = title;
			return result;
		});
		AddUniqueGiftCover(container, std::move(cover), {
			.subtitle = tr::lng_gift_craft_info_about(tr::marked),
		});
		AddSkip(container);

		AddUniqueCloseButton(box);

		const auto features = std::vector<FeatureListEntry>{
			{
				st::menuIconUnique,
				tr::lng_gift_craft_rare_title(tr::now),
				tr::lng_gift_craft_rare_about(tr::now, tr::rich),
			},
			{
				st::menuIconCraftTraits,
				tr::lng_gift_craft_chance_title(tr::now),
				tr::lng_gift_craft_chance_about(tr::now, tr::marked),
			},
			{
				st::menuIconCraftChance,
				tr::lng_gift_craft_affect_title(tr::now),
				tr::lng_gift_craft_affect_about(tr::now, tr::marked),
			},
		};
		for (const auto &feature : features) {
			container->add(
				MakeFeatureListEntry(container, feature),
				st::boxRowPadding);
		}

		box->setStyle(st::giftBox);
		box->setWidth(st::boxWideWidth);
		box->setNoContentMargin(true);

		box->addButton(tr::lng_gift_craft_start_button(), [=] {
			const auto auctions = &controller->session().giftAuctions();
			const auto show = crl::guard(box, [=] {
				ShowGiftCraftBox(
					controller,
					{ GiftForCraft{ gift, savedId } },
					false);
				box->closeBox();
			});
			if (auctions->attributes(gift->initialGiftId)) {
				show();
			} else {
				auctions->requestAttributes(gift->initialGiftId, show);
			}
		});
	}));
}

void ShowTestGiftCraftBox(
		not_null<Window::SessionController*> controller,
		std::vector<GiftForCraftEntry> gifts) {
	auto converted = std::vector<GiftForCraft>();
	converted.reserve(gifts.size());
	for (auto &gift : gifts) {
		auto entry = GiftForCraft();
		entry.unique = std::move(gift.unique);
		entry.manageId = std::move(gift.manageId);
		converted.push_back(std::move(entry));
	}
	ShowGiftCraftBox(controller, std::move(converted), true);
}

bool ShowCraftLaterError(
		std::shared_ptr<Show> show,
		std::shared_ptr<Data::UniqueGift> gift) {
	const auto now = base::unixtime::now();
	if (gift->canCraftAt <= now) {
		return false;
	}
	ShowCraftLaterError(show, gift->canCraftAt);
	return true;
}

void ShowCraftLaterError(
		std::shared_ptr<Show> show,
		TimeId when) {
	const auto data = base::unixtime::parse(when);
	const auto time = QLocale().toString(data.time(), QLocale::ShortFormat);
	const auto date = langDayOfMonthShort(data.date());

	show->show(MakeInformBox({
		.text = tr::lng_gift_craft_when(
			lt_date,
			rpl::single(tr::bold(date)),
			lt_time,
			rpl::single(tr::bold(time)),
			tr::marked),
		.title = tr::lng_gift_craft_unavailable(),
	}));
}

bool ShowCraftAddressError(
		std::shared_ptr<Show> show,
		std::shared_ptr<Data::UniqueGift> gift) {
	if (gift->giftAddress.isEmpty()) {
		return false;
	}
	show->show(MakeInformBox({
		.text = tr::lng_gift_craft_address_error(tr::marked),
		.title = tr::lng_gift_craft_unavailable(),
	}));
	return true;
}

} // namespace Ui
