/*
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
*/
#pragma once

#include "chat_helpers/tabbed_selector.h"
#include "data/stickers/data_stickers.h"
#include "base/variant.h"
#include "base/timer.h"

namespace Main {
class Session;
} // namespace Main

namespace Window {
class SessionController;
} // namespace Window

namespace Ui {
class LinkButton;
class PopupMenu;
class RippleAnimation;
class BoxContent;
} // namespace Ui

namespace Lottie {
class Animation;
class MultiPlayer;
class FrameRenderer;
} // namespace Lottie

namespace Data {
class DocumentMedia;
class StickersSet;
} // namespace Data

namespace ChatHelpers {

struct StickerIcon;

class StickersListWidget
	: public TabbedSelector::Inner
	, private base::Subscriber {
public:
	StickersListWidget(
		QWidget *parent,
		not_null<Window::SessionController*> controller);

	Main::Session &session() const;

	rpl::producer<TabbedSelector::FileChosen> chosen() const;
	rpl::producer<> scrollUpdated() const;
	rpl::producer<> checkForHide() const;

	void refreshRecent() override;
	void preloadImages() override;
	void clearSelection() override;
	object_ptr<TabbedSelector::InnerFooter> createFooter() override;

	void showStickerSet(uint64 setId);
	void showMegagroupSet(ChannelData *megagroup);

	void afterShown() override;
	void beforeHiding() override;

	void refreshStickers();

	std::vector<StickerIcon> fillIcons();
	bool preventAutoHide();

	uint64 currentSet(int yOffset) const;

	void installedLocally(uint64 setId);
	void notInstalledLocally(uint64 setId);
	void clearInstalledLocally();

	void sendSearchRequest();
	void searchForSets(const QString &query);

	std::shared_ptr<Lottie::FrameRenderer> getLottieRenderer();

	void fillContextMenu(
		not_null<Ui::PopupMenu*> menu,
		SendMenu::Type type) override;

	~StickersListWidget();

protected:
	void visibleTopBottomUpdated(
		int visibleTop,
		int visibleBottom) override;

	void mousePressEvent(QMouseEvent *e) override;
	void mouseReleaseEvent(QMouseEvent *e) override;
	void mouseMoveEvent(QMouseEvent *e) override;
	void resizeEvent(QResizeEvent *e) override;
	void paintEvent(QPaintEvent *e) override;
	void leaveEventHook(QEvent *e) override;
	void leaveToChildEvent(QEvent *e, QWidget *child) override;
	void enterFromChildEvent(QEvent *e, QWidget *child) override;

	TabbedSelector::InnerFooter *getFooter() const override;
	void processHideFinished() override;
	void processPanelHideFinished() override;
	int countDesiredHeight(int newWidth) override;

private:
	class Footer;

	enum class Section {
		Featured,
		Stickers,
		Search,
	};

	struct OverSticker {
		int section = 0;
		int index = 0;
		bool overDelete = false;

		inline bool operator==(OverSticker other) const {
			return (section == other.section)
				&& (index == other.index)
				&& (overDelete == other.overDelete);
		}
		inline bool operator!=(OverSticker other) const {
			return !(*this == other);
		}
	};
	struct OverSet {
		int section = 0;

		inline bool operator==(OverSet other) const {
			return (section == other.section);
		}
		inline bool operator!=(OverSet other) const {
			return !(*this == other);
		}
	};
	struct OverButton {
		int section = 0;

		inline bool operator==(OverButton other) const {
			return (section == other.section);
		}
		inline bool operator!=(OverButton other) const {
			return !(*this == other);
		}
	};
	struct OverGroupAdd {
		inline bool operator==(OverGroupAdd other) const {
			return true;
		}
		inline bool operator!=(OverGroupAdd other) const {
			return !(*this == other);
		}
	};
	using OverState = std::variant<
		v::null_t,
		OverSticker,
		OverSet,
		OverButton,
		OverGroupAdd>;

	struct SectionInfo {
		int section = 0;
		int count = 0;
		int top = 0;
		int rowsCount = 0;
		int rowsTop = 0;
		int rowsBottom = 0;
	};

	struct Sticker {
		not_null<DocumentData*> document;
		std::shared_ptr<Data::DocumentMedia> documentMedia;
		Lottie::Animation *animated = nullptr;
		QPixmap savedFrame;

		void ensureMediaCreated();
	};

	struct Set {
		Set(
			uint64 id,
			Data::StickersSet *set,
			MTPDstickerSet::Flags flags,
			const QString &title,
			const QString &shortName,
			int count,
			bool externalLayout,
			std::vector<Sticker> &&stickers = {});
		Set(Set &&other);
		Set &operator=(Set &&other);
		~Set();

		uint64 id = 0;
		Data::StickersSet *set = nullptr;
		MTPDstickerSet::Flags flags = MTPDstickerSet::Flags();
		QString title;
		QString shortName;
		std::vector<Sticker> stickers;
		std::unique_ptr<Ui::RippleAnimation> ripple;

		std::unique_ptr<Lottie::MultiPlayer> lottiePlayer;
		rpl::lifetime lottieLifetime;

		int count = 0;
		bool externalLayout = false;
	};
	struct FeaturedSet {
		uint64 id = 0;
		MTPDstickerSet::Flags flags = MTPDstickerSet::Flags();
		std::vector<Sticker> stickers;
	};

	static std::vector<Sticker> PrepareStickers(
		const QVector<DocumentData*> &pack);

	void preloadMoreOfficial();
	QSize boundingBoxSize() const;

	template <typename Callback>
	bool enumerateSections(Callback callback) const;
	SectionInfo sectionInfo(int section) const;
	SectionInfo sectionInfoByOffset(int yOffset) const;

	void setSection(Section section);
	void displaySet(uint64 setId);
	void checkHideWithBox(QPointer<Ui::BoxContent> box);
	void installSet(uint64 setId);
	void removeMegagroupSet(bool locally);
	void removeSet(uint64 setId);
	void sendInstallRequest(
		uint64 setId,
		const MTPInputStickerSet &input);
	void refreshMySets();
	void refreshFeaturedSets();
	void refreshSearchSets();
	void refreshSearchIndex();

	bool setHasTitle(const Set &set) const;
	bool stickerHasDeleteButton(const Set &set, int index) const;
	std::vector<Sticker> collectRecentStickers();
	void refreshRecentStickers(bool resize = true);
	void refreshFavedStickers();
	enum class GroupStickersPlace {
		Visible,
		Hidden,
	};
	void refreshMegagroupStickers(GroupStickersPlace place);
	void refreshSettingsVisibility();

	void updateSelected();
	void setSelected(OverState newSelected);
	void setPressed(OverState newPressed);
	std::unique_ptr<Ui::RippleAnimation> createButtonRipple(int section);
	QPoint buttonRippleTopLeft(int section) const;

	enum class ValidateIconAnimations {
		Full,
		Scroll,
		None,
	};
	void validateSelectedIcon(ValidateIconAnimations animations);

	std::vector<Set> &shownSets();
	const std::vector<Set> &shownSets() const;
	int featuredRowHeight() const;
	void checkVisibleFeatured(int visibleTop, int visibleBottom);
	void readVisibleFeatured(int visibleTop, int visibleBottom);

	void paintStickers(Painter &p, QRect clip);
	void paintMegagroupEmptySet(Painter &p, int y, bool buttonSelected);
	void paintSticker(Painter &p, Set &set, int y, int section, int index, bool selected, bool deleteSelected);
	void paintEmptySearchResults(Painter &p);

	void ensureLottiePlayer(Set &set);
	void setupLottie(Set &set, int section, int index);
	void markLottieFrameShown(Set &set);
	void checkVisibleLottie();
	void pauseInvisibleLottieIn(const SectionInfo &info);
	void takeHeavyData(std::vector<Set> &to, std::vector<Set> &from);
	void takeHeavyData(Set &to, Set &from);
	void takeHeavyData(Sticker &to, Sticker &from);
	void clearHeavyIn(Set &set, bool clearSavedFrames = true);
	void clearHeavyData();

	int stickersRight() const;
	bool featuredHasAddButton(int index) const;
	QRect featuredAddRect(int index) const;
	bool hasRemoveButton(int index) const;
	QRect removeButtonRect(int index) const;
	int megagroupSetInfoLeft() const;
	void refreshMegagroupSetGeometry();
	QRect megagroupSetButtonRectFinal() const;

	enum class AppendSkip {
		None,
		Archived,
		Installed,
	};
	bool appendSet(
		std::vector<Set> &to,
		uint64 setId,
		bool externalLayout,
		AppendSkip skip = AppendSkip::None);

	void selectEmoji(EmojiPtr emoji);
	int stickersLeft() const;
	QRect stickerRect(int section, int sel);

	void removeRecentSticker(int section, int index);
	void removeFavedSticker(int section, int index);
	void setColumnCount(int count);
	void refreshFooterIcons();

	void showStickerSetBox(not_null<DocumentData*> document);

	void cancelSetsSearch();
	void showSearchResults();
	void searchResultsDone(const MTPmessages_FoundStickerSets &result);
	void refreshSearchRows();
	void refreshSearchRows(const std::vector<uint64> *cloudSets);
	void fillLocalSearchRows(const QString &query);
	void fillCloudSearchRows(const std::vector<uint64> &cloudSets);
	void addSearchRow(not_null<Data::StickersSet*> set);

	void showPreview();

	MTP::Sender _api;
	ChannelData *_megagroupSet = nullptr;
	uint64 _megagroupSetIdRequested = 0;
	std::vector<Set> _mySets;
	std::vector<Set> _officialSets;
	std::vector<Set> _searchSets;
	int _featuredSetsCount = 0;
	base::flat_set<uint64> _installedLocallySets;
	std::vector<bool> _custom;
	base::flat_set<not_null<DocumentData*>> _favedStickersMap;
	std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;

	mtpRequestId _officialRequestId = 0;
	int _officialOffset = 0;

	Section _section = Section::Stickers;

	bool _displayingSet = false;
	uint64 _removingSetId = 0;

	Footer *_footer = nullptr;
	int _rowsLeft = 0;
	int _columnCount = 1;
	QSize _singleSize;

	OverState _selected;
	OverState _pressed;
	QPoint _lastMousePosition;

	Ui::Text::String _megagroupSetAbout;
	QString _megagroupSetButtonText;
	int _megagroupSetButtonTextWidth = 0;
	QRect _megagroupSetButtonRect;
	std::unique_ptr<Ui::RippleAnimation> _megagroupSetButtonRipple;

	QString _addText;
	int _addWidth;

	object_ptr<Ui::LinkButton> _settings;

	base::Timer _previewTimer;
	bool _previewShown = false;

	std::map<QString, std::vector<uint64>> _searchCache;
	std::vector<std::pair<uint64, QStringList>> _searchIndex;
	base::Timer _searchRequestTimer;
	QString _searchQuery, _searchNextQuery;
	mtpRequestId _searchRequestId = 0;

	rpl::event_stream<TabbedSelector::FileChosen> _chosen;
	rpl::event_stream<> _scrollUpdated;
	rpl::event_stream<> _checkForHide;

};

} // namespace ChatHelpers
