/*
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 "ui/rp_widget.h"
#include "info/media/info_media_widget.h"
#include "data/data_shared_media.h"

class DeleteMessagesBox;

namespace Main {
class Session;
} // namespace Main

namespace HistoryView {
struct TextState;
struct StateRequest;
enum class CursorState : char;
enum class PointState : char;
} // namespace HistoryView

namespace Ui {
class PopupMenu;
} // namespace Ui

namespace Overview {
namespace Layout {
class ItemBase;
} // namespace Layout
} // namespace Overview

namespace Window {
class SessionController;
} // namespace Window

namespace Info {

class AbstractController;

namespace Media {

using BaseLayout = Overview::Layout::ItemBase;
using UniversalMsgId = int32;

class ListWidget : public Ui::RpWidget {
public:
	ListWidget(
		QWidget *parent,
		not_null<AbstractController*> controller);

	Main::Session &session() const;

	void restart();

	rpl::producer<int> scrollToRequests() const;
	rpl::producer<SelectedItems> selectedListValue() const;
	void cancelSelection() {
		clearSelected();
	}

	QRect getCurrentSongGeometry();
	rpl::producer<> checkForHide() const {
		return _checkForHide.events();
	}
	bool preventAutoHide() const;

	void saveState(not_null<Memento*> memento);
	void restoreState(not_null<Memento*> memento);

	~ListWidget();

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

	void paintEvent(QPaintEvent *e) override;
	void mouseMoveEvent(QMouseEvent *e) override;
	void mousePressEvent(QMouseEvent *e) override;
	void mouseReleaseEvent(QMouseEvent *e) override;
	void mouseDoubleClickEvent(QMouseEvent *e) override;
	void contextMenuEvent(QContextMenuEvent *e) override;
	void enterEventHook(QEvent *e) override;
	void leaveEventHook(QEvent *e) override;

private:
	struct Context;
	class Section;
	using CursorState = HistoryView::CursorState;
	using TextState = HistoryView::TextState;
	using StateRequest = HistoryView::StateRequest;
	enum class MouseAction {
		None,
		PrepareDrag,
		Dragging,
		PrepareSelect,
		Selecting,
	};
	struct CachedItem {
		CachedItem(std::unique_ptr<BaseLayout> item);
		~CachedItem();

		std::unique_ptr<BaseLayout> item;
		bool stale = false;
	};
	struct FoundItem {
		not_null<BaseLayout*> layout;
		QRect geometry;
		bool exact = false;
	};
	struct SelectionData {
		explicit SelectionData(TextSelection text) : text(text) {
		}

		TextSelection text;
		bool canDelete = false;
		bool canForward = false;
	};
	using SelectedMap = base::flat_map<
		UniversalMsgId,
		SelectionData,
		std::less<>>;
	enum class DragSelectAction {
		None,
		Selecting,
		Deselecting,
	};
	struct MouseState {
		UniversalMsgId itemId = 0;
		QSize size;
		QPoint cursor;
		bool inside = false;

		inline bool operator==(const MouseState &other) const {
			return (itemId == other.itemId)
				&& (cursor == other.cursor);
		}
		inline bool operator!=(const MouseState &other) const {
			return !(*this == other);
		}

	};
	enum class ContextMenuSource {
		Mouse,
		Touch,
		Other,
	};
	struct ScrollTopState {
		UniversalMsgId item = 0;
		int shift = 0;
	};

	void start();
	int recountHeight();
	void refreshHeight();

	QMargins padding() const;
	bool isMyItem(not_null<const HistoryItem*> item) const;
	bool isItemLayout(
		not_null<const HistoryItem*> item,
		BaseLayout *layout) const;
	bool isPossiblyMyId(FullMsgId fullId) const;
	void repaintItem(const HistoryItem *item);
	void repaintItem(UniversalMsgId msgId);
	void repaintItem(const BaseLayout *item);
	void repaintItem(QRect itemGeometry);
	void itemRemoved(not_null<const HistoryItem*> item);
	void itemLayoutChanged(not_null<const HistoryItem*> item);

	void refreshViewer();
	void invalidatePaletteCache();
	void refreshRows();
	SparseIdsMergedSlice::Key sliceKey(
		UniversalMsgId universalId) const;
	BaseLayout *getLayout(UniversalMsgId universalId);
	BaseLayout *getExistingLayout(UniversalMsgId universalId) const;
	std::unique_ptr<BaseLayout> createLayout(
		UniversalMsgId universalId,
		Type type);

	SelectedItems collectSelectedItems() const;
	MessageIdsList collectSelectedIds() const;
	void pushSelectedItems();
	FullMsgId computeFullId(UniversalMsgId universalId) const;
	bool hasSelected() const;
	bool isSelectedItem(
		const SelectedMap::const_iterator &i) const;
	void removeItemSelection(
		const SelectedMap::const_iterator &i);
	bool hasSelectedText() const;
	bool hasSelectedItems() const;
	void clearSelected();
	void forwardSelected();
	void forwardItem(UniversalMsgId universalId);
	void forwardItems(MessageIdsList &&items);
	void deleteSelected();
	void deleteItem(UniversalMsgId universalId);
	DeleteMessagesBox *deleteItems(MessageIdsList &&items);
	void applyItemSelection(
		UniversalMsgId universalId,
		TextSelection selection);
	void toggleItemSelection(
		UniversalMsgId universalId);
	SelectedMap::iterator itemUnderPressSelection();
	SelectedMap::const_iterator itemUnderPressSelection() const;
	bool isItemUnderPressSelected() const;
	bool requiredToStartDragging(not_null<BaseLayout*> layout) const;
	bool isPressInSelectedText(TextState state) const;
	void applyDragSelection();
	void applyDragSelection(SelectedMap &applyTo) const;
	bool changeItemSelection(
		SelectedMap &selected,
		UniversalMsgId universalId,
		TextSelection selection) const;

	static bool IsAfter(
		const MouseState &a,
		const MouseState &b);
	static bool SkipSelectFromItem(const MouseState &state);
	static bool SkipSelectTillItem(const MouseState &state);

	void markLayoutsStale();
	void clearStaleLayouts();
	std::vector<Section>::iterator findSectionByItem(
		UniversalMsgId universalId);
	std::vector<Section>::iterator findSectionAfterTop(int top);
	std::vector<Section>::const_iterator findSectionAfterTop(
		int top) const;
	std::vector<Section>::const_iterator findSectionAfterBottom(
		std::vector<Section>::const_iterator from,
		int bottom) const;
	FoundItem findItemByPoint(QPoint point) const;
	std::optional<FoundItem> findItemById(UniversalMsgId universalId);
	std::optional<FoundItem> findItemDetails(BaseLayout *item);
	FoundItem foundItemInSection(
		const FoundItem &item,
		const Section &section) const;

	ScrollTopState countScrollState() const;
	void saveScrollState();
	void restoreScrollState();

	QPoint clampMousePosition(QPoint position) const;
	void mouseActionStart(
		const QPoint &globalPosition,
		Qt::MouseButton button);
	void mouseActionUpdate(const QPoint &globalPosition);
	void mouseActionUpdate();
	void mouseActionFinish(
		const QPoint &globalPosition,
		Qt::MouseButton button);
	void mouseActionCancel();
	void performDrag();
	style::cursor computeMouseCursor() const;
	void showContextMenu(
		QContextMenuEvent *e,
		ContextMenuSource source);

	void updateDragSelection();
	void clearDragSelection();

	void trySwitchToWordSelection();
	void switchToWordSelection();
	void validateTrippleClickStartTime();
	void checkMoveToOtherViewer();

	void setActionBoxWeak(QPointer<Ui::RpWidget> box);

	const not_null<AbstractController*> _controller;
	const not_null<PeerData*> _peer;
	PeerData * const _migrated = nullptr;
	Type _type = Type::Photo;

	static constexpr auto kMinimalIdsLimit = 16;
	static constexpr auto kDefaultAroundId = (ServerMaxMsgId - 1);
	UniversalMsgId _universalAroundId = kDefaultAroundId;
	int _idsLimit = kMinimalIdsLimit;
	SparseIdsMergedSlice _slice;

	std::map<UniversalMsgId, CachedItem> _layouts;
	std::vector<Section> _sections;

	int _visibleTop = 0;
	int _visibleBottom = 0;
	ScrollTopState _scrollTopState;
	rpl::event_stream<int> _scrollToRequests;

	MouseAction _mouseAction = MouseAction::None;
	TextSelectType _mouseSelectType = TextSelectType::Letters;
	QPoint _mousePosition;
	MouseState _overState;
	MouseState _pressState;
	BaseLayout *_overLayout = nullptr;
	UniversalMsgId _contextUniversalId = 0;
	CursorState _mouseCursorState = CursorState();
	uint16 _mouseTextSymbol = 0;
	bool _pressWasInactive = false;
	SelectedMap _selected;
	SelectedMap _dragSelected;
	rpl::event_stream<SelectedItems> _selectedListStream;
	style::cursor _cursor = style::cur_default;
	DragSelectAction _dragSelectAction = DragSelectAction::None;
	bool _wasSelectedText = false; // was some text selected in current drag action

	base::unique_qptr<Ui::PopupMenu> _contextMenu;
	rpl::event_stream<> _checkForHide;
	QPointer<Ui::RpWidget> _actionBoxWeak;
	rpl::lifetime _actionBoxWeakLifetime;

	QPoint _trippleClickPoint;
	crl::time _trippleClickStartTime = 0;

	rpl::lifetime _viewerLifetime;

};

} // namespace Media
} // namespace Info
